mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 20:57:10 +00:00
Compare commits
346
Commits
+2
-2
@@ -9,14 +9,14 @@ AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakTemplateDeclarations: Yes
|
||||
BasedOnStyle: WebKit
|
||||
BitFieldColonSpacing: After
|
||||
BinPackParameters: false
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterFunction: false
|
||||
AfterFunction: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: true
|
||||
BeforeElse: true
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"name": "Android NDK aarch64",
|
||||
"os": "ubuntu-22.04",
|
||||
"simple_name": "android",
|
||||
"compiler": "aarch64-linux-android21-clang++",
|
||||
"compiler": "aarch64-linux-android29-clang++",
|
||||
"emu": "qemu-aarch64",
|
||||
"comp": "ndk",
|
||||
"shell": "bash",
|
||||
@@ -14,7 +14,7 @@
|
||||
"name": "Android NDK arm",
|
||||
"os": "ubuntu-22.04",
|
||||
"simple_name": "android",
|
||||
"compiler": "armv7a-linux-androideabi21-clang++",
|
||||
"compiler": "armv7a-linux-androideabi29-clang++",
|
||||
"emu": "qemu-arm",
|
||||
"comp": "ndk",
|
||||
"shell": "bash",
|
||||
@@ -26,25 +26,25 @@
|
||||
{
|
||||
"binaries": "armv8-dotprod",
|
||||
"config": {
|
||||
"compiler": "armv7a-linux-androideabi21-clang++"
|
||||
"compiler": "armv7a-linux-androideabi29-clang++"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8",
|
||||
"config": {
|
||||
"compiler": "armv7a-linux-androideabi21-clang++"
|
||||
"compiler": "armv7a-linux-androideabi29-clang++"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv7",
|
||||
"config": {
|
||||
"compiler": "aarch64-linux-android21-clang++"
|
||||
"compiler": "aarch64-linux-android29-clang++"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv7-neon",
|
||||
"config": {
|
||||
"compiler": "aarch64-linux-android21-clang++"
|
||||
"compiler": "aarch64-linux-android29-clang++"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+165
-43
@@ -8,11 +8,11 @@
|
||||
"comp": "gcc",
|
||||
"shell": "bash",
|
||||
"archive_ext": "tar",
|
||||
"sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-lin/sde -future --"
|
||||
"sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-lin/sde -future --"
|
||||
},
|
||||
{
|
||||
"name": "MacOS 13 Apple Clang",
|
||||
"os": "macos-13",
|
||||
"name": "macOS 15 Apple Clang",
|
||||
"os": "macos-15-intel",
|
||||
"simple_name": "macos",
|
||||
"compiler": "clang++",
|
||||
"comp": "clang",
|
||||
@@ -20,8 +20,8 @@
|
||||
"archive_ext": "tar"
|
||||
},
|
||||
{
|
||||
"name": "MacOS 14 Apple Clang M1",
|
||||
"os": "macos-14",
|
||||
"name": "macOS 15 Apple Clang M1",
|
||||
"os": "macos-15",
|
||||
"simple_name": "macos-m1",
|
||||
"compiler": "clang++",
|
||||
"comp": "clang",
|
||||
@@ -38,7 +38,19 @@
|
||||
"msys_env": "x86_64-gcc",
|
||||
"shell": "msys2 {0}",
|
||||
"ext": ".exe",
|
||||
"sde": "/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.27.0-2023-09-13-win/sde.exe -future --",
|
||||
"sde": "/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-win/sde.exe -future --",
|
||||
"archive_ext": "zip"
|
||||
},
|
||||
{
|
||||
"name": "Windows 11 Mingw-w64 Clang arm64",
|
||||
"os": "windows-11-arm",
|
||||
"simple_name": "windows",
|
||||
"compiler": "clang++",
|
||||
"comp": "clang",
|
||||
"msys_sys": "clangarm64",
|
||||
"msys_env": "clang-aarch64-clang",
|
||||
"shell": "msys2 {0}",
|
||||
"ext": ".exe",
|
||||
"archive_ext": "zip"
|
||||
}
|
||||
],
|
||||
@@ -49,93 +61,131 @@
|
||||
"x86-64-bmi2",
|
||||
"x86-64-avxvnni",
|
||||
"x86-64-avx512",
|
||||
"x86-64-vnni256",
|
||||
"x86-64-vnni512",
|
||||
"apple-silicon"
|
||||
"x86-64-avx512icl",
|
||||
"apple-silicon",
|
||||
"armv8",
|
||||
"armv8-dotprod"
|
||||
],
|
||||
"exclude": [
|
||||
{
|
||||
"binaries": "x86-64",
|
||||
"config": {
|
||||
"os": "macos-14"
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-sse41-popcnt",
|
||||
"config": {
|
||||
"os": "macos-14"
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx2",
|
||||
"config": {
|
||||
"os": "macos-14"
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-bmi2",
|
||||
"config": {
|
||||
"os": "macos-14"
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avxvnni",
|
||||
"config": {
|
||||
"os": "macos-14"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avxvnni",
|
||||
"config": {
|
||||
"os": "macos-14"
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx512",
|
||||
"config": {
|
||||
"os": "macos-14"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-vnni256",
|
||||
"config": {
|
||||
"os": "macos-14"
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-vnni512",
|
||||
"config": {
|
||||
"os": "macos-14"
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx512icl",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avxvnni",
|
||||
"config": {
|
||||
"ubuntu-22.04": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avxvnni",
|
||||
"config": {
|
||||
"os": "macos-13"
|
||||
"os": "macos-15-intel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx512",
|
||||
"config": {
|
||||
"os": "macos-13"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-vnni256",
|
||||
"config": {
|
||||
"os": "macos-13"
|
||||
"os": "macos-15-intel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-vnni512",
|
||||
"config": {
|
||||
"os": "macos-13"
|
||||
"os": "macos-15-intel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx512icl",
|
||||
"config": {
|
||||
"os": "macos-15-intel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64",
|
||||
"config": {
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-sse41-popcnt",
|
||||
"config": {
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx2",
|
||||
"config": {
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-bmi2",
|
||||
"config": {
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avxvnni",
|
||||
"config": {
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx512",
|
||||
"config": {
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-vnni512",
|
||||
"config": {
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx512icl",
|
||||
"config": {
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -147,7 +197,13 @@
|
||||
{
|
||||
"binaries": "apple-silicon",
|
||||
"config": {
|
||||
"os": "macos-13"
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "apple-silicon",
|
||||
"config": {
|
||||
"os": "ubuntu-20.04"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -155,6 +211,72 @@
|
||||
"config": {
|
||||
"os": "ubuntu-22.04"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "apple-silicon",
|
||||
"config": {
|
||||
"os": "macos-15-intel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8",
|
||||
"config": {
|
||||
"os": "windows-2022"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8",
|
||||
"config": {
|
||||
"os": "ubuntu-20.04"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8",
|
||||
"config": {
|
||||
"os": "ubuntu-22.04"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8",
|
||||
"config": {
|
||||
"os": "macos-15-intel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8-dotprod",
|
||||
"config": {
|
||||
"os": "windows-2022"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8-dotprod",
|
||||
"config": {
|
||||
"os": "ubuntu-20.04"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8-dotprod",
|
||||
"config": {
|
||||
"os": "ubuntu-22.04"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8-dotprod",
|
||||
"config": {
|
||||
"os": "macos-15-intel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8-dotprod",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
if [ $COMP == ndk ]; then
|
||||
NDKV="21.4.7075529"
|
||||
NDKV="27.2.12479018"
|
||||
ANDROID_ROOT=/usr/local/lib/android
|
||||
ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk
|
||||
SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager
|
||||
@@ -80,9 +80,9 @@ jobs:
|
||||
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
||||
fi
|
||||
make clean
|
||||
make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU
|
||||
make -j4 profile-build ARCH=$BINARY COMP=$COMP RUN_PREFIX=$EMU
|
||||
make strip ARCH=$BINARY COMP=$COMP
|
||||
WINE_PATH=$EMU ../tests/signature.sh $benchref
|
||||
RUN_PREFIX=$EMU ../tests/signature.sh $benchref
|
||||
mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT
|
||||
|
||||
- name: Remove non src files
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
name: AVX2 Compiler Matrix
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
avx2-compiler-matrix:
|
||||
name: avx2 (${{ matrix.name }})
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ matrix.image }}
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { name: gcc-10, comp: gcc, cxx: g++, image: "gcc:10" }
|
||||
- { name: gcc-11, comp: gcc, cxx: g++, image: "gcc:11" }
|
||||
- { name: gcc-12, comp: gcc, cxx: g++, image: "gcc:12" }
|
||||
- { name: gcc-13, comp: gcc, cxx: g++, image: "gcc:13" }
|
||||
- { name: gcc-14, comp: gcc, cxx: g++, image: "gcc:14" }
|
||||
- { name: gcc-15, comp: gcc, cxx: g++, image: "gcc:15" }
|
||||
|
||||
# Using silkeh/clang for older versions
|
||||
- { name: clang-10, comp: clang, cxx: clang++, image: "silkeh/clang:10", is_clang: true, ver: "10" }
|
||||
- { name: clang-11, comp: clang, cxx: clang++, image: "silkeh/clang:11", is_clang: true, ver: "11" }
|
||||
- { name: clang-12, comp: clang, cxx: clang++, image: "silkeh/clang:12", is_clang: true, ver: "12" }
|
||||
- { name: clang-13, comp: clang, cxx: clang++, image: "silkeh/clang:13", is_clang: true, ver: "13" }
|
||||
- { name: clang-14, comp: clang, cxx: clang++, image: "silkeh/clang:14", is_clang: true, ver: "14" }
|
||||
- { name: clang-15, comp: clang, cxx: clang++, image: "silkeh/clang:15", is_clang: true, ver: "15" }
|
||||
- { name: clang-16, comp: clang, cxx: clang++, image: "silkeh/clang:16", is_clang: true, ver: "16" }
|
||||
- { name: clang-17, comp: clang, cxx: clang++, image: "silkeh/clang:17", is_clang: true, ver: "17" }
|
||||
|
||||
- { name: clang-18, comp: clang, cxx: clang++-18, image: "ubuntu:rolling", is_clang: true, ver: "18" }
|
||||
- { name: clang-19, comp: clang, cxx: clang++-19, image: "ubuntu:rolling", is_clang: true, ver: "19" }
|
||||
- { name: clang-20, comp: clang, cxx: clang++-20, image: "ubuntu:rolling", is_clang: true, ver: "20" }
|
||||
- { name: clang-21, comp: clang, cxx: clang++-21, image: "ubuntu:rolling", is_clang: true, ver: "21" }
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
if grep -q "buster" /etc/os-release; then
|
||||
echo "Debian Buster detected. Switching to archive repositories..."
|
||||
echo "deb http://archive.debian.org/debian buster main contrib non-free" > /etc/apt/sources.list
|
||||
echo "deb http://archive.debian.org/debian-security buster/updates main contrib non-free" >> /etc/apt/sources.list
|
||||
echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99-ignore-valid-until
|
||||
fi
|
||||
|
||||
apt-get update
|
||||
apt-get install -y curl git make
|
||||
|
||||
- name: Set up Clang
|
||||
if: ${{ matrix.is_clang && matrix.image == 'ubuntu:rolling' }}
|
||||
run: |
|
||||
if [ "${{ matrix.ver }}" -le 20 ]; then
|
||||
apt-get install -y clang-${{ matrix.ver }}
|
||||
else
|
||||
apt-get install -y \
|
||||
clang-${{ matrix.ver }} \
|
||||
llvm-${{ matrix.ver }}-dev \
|
||||
llvm-${{ matrix.ver }}-linker-tools \
|
||||
lld-${{ matrix.ver }}
|
||||
fi
|
||||
|
||||
- name: Download network
|
||||
working-directory: src
|
||||
run: make net
|
||||
|
||||
- name: Build avx2 binary
|
||||
working-directory: src
|
||||
run: |
|
||||
export CXXFLAGS="-Werror"
|
||||
if [ "${{ matrix.ver }}" -ge 20 ]; then
|
||||
export LDFLAGS="-fuse-ld=lld"
|
||||
apt install -y lld
|
||||
fi
|
||||
make clean
|
||||
make -j build ARCH=x86-64-avx2 COMP=${{ matrix.comp }} COMPCXX=${{ matrix.cxx }}
|
||||
|
||||
- name: Smoke test
|
||||
working-directory: src
|
||||
run: ./stockfish bench 16 1 6
|
||||
@@ -25,11 +25,11 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Run clang-format style check
|
||||
uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0
|
||||
uses: jidicula/clang-format-action@4726374d1aa3c6aecf132e5197e498979588ebc8 # @v4.15.0
|
||||
id: clang-format
|
||||
continue-on-error: true
|
||||
with:
|
||||
clang-format-version: "18"
|
||||
clang-format-version: "20"
|
||||
exclude-regex: "incbin"
|
||||
|
||||
- name: Comment on PR
|
||||
@@ -37,9 +37,9 @@ jobs:
|
||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0
|
||||
with:
|
||||
message: |
|
||||
clang-format 18 needs to be run on this PR.
|
||||
clang-format 20 needs to be run on this PR.
|
||||
If you do not have clang-format installed, the maintainer will run it when merging.
|
||||
For the exact version please see https://packages.ubuntu.com/noble/clang-format-18.
|
||||
For the exact version please see https://packages.ubuntu.com/plucky/clang-format-20.
|
||||
|
||||
_(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
|
||||
comment_tag: execution
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
working-directory: src
|
||||
run: make -j build ARCH=x86-64-modern
|
||||
run: make -j build
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
@@ -43,10 +43,10 @@ jobs:
|
||||
|
||||
- name: Download SDE package
|
||||
if: runner.os == 'Linux' || runner.os == 'Windows'
|
||||
uses: petarpetrovt/setup-sde@91a1a03434384e064706634125a15f7446d2aafb # @v2.3
|
||||
uses: petarpetrovt/setup-sde@f0fa5971dc275704531e94264dd23250c442aa41 # @v2.4
|
||||
with:
|
||||
environmentVariableName: SDE_DIR
|
||||
sdeVersion: 9.27.0
|
||||
sdeVersion: 9.33.0
|
||||
|
||||
- name: Download the used network from the fishtest framework
|
||||
run: make net
|
||||
@@ -63,22 +63,22 @@ jobs:
|
||||
- name: Check compiler
|
||||
run: $COMPCXX -v
|
||||
|
||||
- name: Show g++ cpu info
|
||||
if: runner.os != 'macOS'
|
||||
run: g++ -Q -march=native --help=target
|
||||
|
||||
- name: Show clang++ cpu info
|
||||
if: runner.os == 'macOS'
|
||||
run: clang++ -E - -march=native -###
|
||||
- name: Show compiler cpu info
|
||||
run: |
|
||||
if [[ "$COMPCXX" == clang* ]]; then
|
||||
$COMPCXX -E - -march=native -###
|
||||
else
|
||||
$COMPCXX -Q -march=native --help=target
|
||||
fi
|
||||
|
||||
# x86-64 with newer extensions tests
|
||||
|
||||
- name: Compile ${{ matrix.config.binaries }} build
|
||||
run: |
|
||||
make clean
|
||||
make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE"
|
||||
make -j4 profile-build ARCH=$BINARY COMP=$COMP RUN_PREFIX="$SDE"
|
||||
make strip ARCH=$BINARY COMP=$COMP
|
||||
WINE_PATH="$SDE" ../tests/signature.sh $benchref
|
||||
RUN_PREFIX="$SDE" ../tests/signature.sh $benchref
|
||||
mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT
|
||||
|
||||
- name: Remove non src files
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
with:
|
||||
repository: vondele/matetrack
|
||||
path: matetrack
|
||||
ref: 4f8a80860ed8f3607f05a9195df8b40203bdc360
|
||||
ref: 2d96fa3373f90edb032b7ea7468473fb9e6f0343
|
||||
persist-credentials: false
|
||||
|
||||
- name: matetrack install deps
|
||||
@@ -47,25 +47,44 @@ jobs:
|
||||
wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-wdl/
|
||||
wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-dtz/
|
||||
|
||||
- name: Run matetrack
|
||||
- name: Run matetrack th1
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout.out
|
||||
! grep "issues were detected" matecheckout.out > /dev/null
|
||||
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout1.out
|
||||
! grep "issues were detected" matecheckout1.out > /dev/null
|
||||
|
||||
- name: Run matetrack with --syzygy50MoveRule false
|
||||
- name: Run matetrack th4
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 --threads 4 | tee matecheckout4.out
|
||||
! grep "issues were detected" matecheckout4.out > /dev/null
|
||||
|
||||
- name: Run matetrack th1 with --syzygy50MoveRule false
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
grep 5men cursed.epd > cursed5.epd
|
||||
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --syzygy50MoveRule false | tee matecheckcursed.out
|
||||
! grep "issues were detected" matecheckcursed.out > /dev/null
|
||||
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --syzygy50MoveRule false | tee matecheckcursed1.out
|
||||
! grep "issues were detected" matecheckcursed1.out > /dev/null
|
||||
|
||||
- name: Verify mate and TB win count for matecheckcursed.out
|
||||
- name: Run matetrack th4 with --syzygy50MoveRule false
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
mates=$(grep "Found mates:" matecheckcursed.out | awk '{print $3}')
|
||||
tbwins=$(grep "Found TB wins:" matecheckcursed.out | awk '{print $4}')
|
||||
grep 5men cursed.epd > cursed5.epd
|
||||
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile cursed5.epd --nodes 100000 --threads 4 --syzygy50MoveRule false | tee matecheckcursed4.out
|
||||
! grep "issues were detected" matecheckcursed4.out > /dev/null
|
||||
|
||||
- name: Verify mate and TB win count for matecheckcursed[14].out
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
mates=$(grep "Found mates:" matecheckcursed1.out | awk '{print $3}')
|
||||
tbwins=$(grep "Found TB wins:" matecheckcursed1.out | awk '{print $4}')
|
||||
if [ $(($mates + $tbwins)) -ne 32 ]; then
|
||||
echo "Sum of mates and TB wins is not 32 in matecheckcursed.out" >&2
|
||||
echo "Sum of mates and TB wins is not 32 in matecheckcursed1.out" >&2
|
||||
exit 1
|
||||
fi
|
||||
mates=$(grep "Found mates:" matecheckcursed4.out | awk '{print $3}')
|
||||
tbwins=$(grep "Found TB wins:" matecheckcursed4.out | awk '{print $4}')
|
||||
if [ $(($mates + $tbwins)) -ne 32 ]; then
|
||||
echo "Sum of mates and TB wins is not 32 in matecheckcursed4.out" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Run with glibcxx assertions
|
||||
make_option: ""
|
||||
cxx_extra_flags: -D_GLIBCXX_ASSERTIONS
|
||||
instrumented_option: non
|
||||
instrumented_option: none
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src
|
||||
|
||||
@@ -120,3 +120,5 @@ jobs:
|
||||
contents: write # For deleting/creating a (pre)release
|
||||
secrets:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
CompilerCheck:
|
||||
uses: ./.github/workflows/avx2_compilers.yml
|
||||
|
||||
+34
-21
@@ -29,24 +29,25 @@ jobs:
|
||||
shell: bash
|
||||
- name: Android NDK aarch64
|
||||
os: ubuntu-22.04
|
||||
compiler: aarch64-linux-android21-clang++
|
||||
compiler: aarch64-linux-android29-clang++
|
||||
comp: ndk
|
||||
run_armv8_tests: true
|
||||
shell: bash
|
||||
- name: Android NDK arm
|
||||
os: ubuntu-22.04
|
||||
compiler: armv7a-linux-androideabi21-clang++
|
||||
compiler: armv7a-linux-androideabi29-clang++
|
||||
comp: ndk
|
||||
run_armv7_tests: true
|
||||
shell: bash
|
||||
- name: Linux GCC riscv64
|
||||
os: ubuntu-22.04
|
||||
compiler: g++
|
||||
comp: gcc
|
||||
run_riscv64_tests: true
|
||||
base_image: "riscv64/alpine:edge"
|
||||
platform: linux/riscv64
|
||||
shell: bash
|
||||
# Currently segfaults in the CI unrelated to a Stockfish change.
|
||||
# - name: Linux GCC riscv64
|
||||
# os: ubuntu-22.04
|
||||
# compiler: g++
|
||||
# comp: gcc
|
||||
# run_riscv64_tests: true
|
||||
# base_image: "riscv64/alpine:edge"
|
||||
# platform: linux/riscv64
|
||||
# shell: bash
|
||||
- name: Linux GCC ppc64
|
||||
os: ubuntu-22.04
|
||||
compiler: g++
|
||||
@@ -55,21 +56,21 @@ jobs:
|
||||
base_image: "ppc64le/alpine:latest"
|
||||
platform: linux/ppc64le
|
||||
shell: bash
|
||||
- name: MacOS 13 Apple Clang
|
||||
os: macos-13
|
||||
- name: macOS 15 Apple Clang
|
||||
os: macos-15-intel
|
||||
compiler: clang++
|
||||
comp: clang
|
||||
run_64bit_tests: true
|
||||
shell: bash
|
||||
- name: MacOS 14 Apple Clang M1
|
||||
os: macos-14
|
||||
- name: macOS 15 Apple Clang M1
|
||||
os: macos-15
|
||||
compiler: clang++
|
||||
comp: clang
|
||||
run_64bit_tests: false
|
||||
run_m1_tests: true
|
||||
shell: bash
|
||||
- name: MacOS 13 GCC 11
|
||||
os: macos-13
|
||||
- name: macOS 15 GCC 11
|
||||
os: macos-15-intel
|
||||
compiler: g++-11
|
||||
comp: gcc
|
||||
run_64bit_tests: true
|
||||
@@ -98,6 +99,14 @@ jobs:
|
||||
msys_sys: clang64
|
||||
msys_env: clang-x86_64-clang
|
||||
shell: msys2 {0}
|
||||
- name: Windows 11 Mingw-w64 Clang arm64
|
||||
os: windows-11-arm
|
||||
compiler: clang++
|
||||
comp: clang
|
||||
run_armv8_tests: true
|
||||
msys_sys: clangarm64
|
||||
msys_env: clang-aarch64-clang
|
||||
shell: msys2 {0}
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src
|
||||
@@ -118,7 +127,7 @@ jobs:
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
if [ $COMP == ndk ]; then
|
||||
NDKV="21.4.7075529"
|
||||
NDKV="27.2.12479018"
|
||||
ANDROID_ROOT=/usr/local/lib/android
|
||||
ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk
|
||||
SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager
|
||||
@@ -302,8 +311,10 @@ jobs:
|
||||
- name: Test armv8 build
|
||||
if: matrix.config.run_armv8_tests
|
||||
run: |
|
||||
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
||||
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
||||
if [ $COMP == ndk ]; then
|
||||
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
||||
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
||||
fi
|
||||
make clean
|
||||
make -j4 ARCH=armv8 build
|
||||
../tests/signature.sh $benchref
|
||||
@@ -311,8 +322,10 @@ jobs:
|
||||
- name: Test armv8-dotprod build
|
||||
if: matrix.config.run_armv8_tests
|
||||
run: |
|
||||
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
||||
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
||||
if [ $COMP == ndk ]; then
|
||||
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
||||
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
||||
fi
|
||||
make clean
|
||||
make -j4 ARCH=armv8-dotprod build
|
||||
../tests/signature.sh $benchref
|
||||
|
||||
@@ -12,20 +12,17 @@ on:
|
||||
jobs:
|
||||
Artifacts:
|
||||
name: ${{ matrix.config.name }} ${{ matrix.binaries }}
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
COMPCXX: ${{ matrix.config.compiler }}
|
||||
COMP: ${{ matrix.config.comp }}
|
||||
EXT: ${{ matrix.config.ext }}
|
||||
NAME: ${{ matrix.config.simple_name }}
|
||||
BINARY: ${{ matrix.binaries }}
|
||||
SDE: ${{ matrix.config.sde }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJson(inputs.matrix) }}
|
||||
defaults:
|
||||
run:
|
||||
shell: ${{ matrix.config.shell }}
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
@@ -37,13 +34,6 @@ jobs:
|
||||
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
|
||||
path: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
|
||||
|
||||
- name: Setup msys and install required packages
|
||||
if: runner.os == 'Windows'
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: ${{ matrix.config.msys_sys }}
|
||||
install: mingw-w64-${{ matrix.config.msys_env }} make git zip
|
||||
|
||||
- name: Create Package
|
||||
run: |
|
||||
mkdir stockfish
|
||||
@@ -69,13 +59,13 @@ jobs:
|
||||
cp CONTRIBUTING.md ../stockfish/
|
||||
|
||||
- name: Create tar
|
||||
if: runner.os != 'Windows'
|
||||
if: ${{ !startsWith(matrix.config.os, 'windows') }}
|
||||
run: |
|
||||
chmod +x ./stockfish/stockfish-$NAME-$BINARY$EXT
|
||||
tar -cvf stockfish-$NAME-$BINARY.tar stockfish
|
||||
|
||||
- name: Create zip
|
||||
if: runner.os == 'Windows'
|
||||
if: ${{ startsWith(matrix.config.os, 'windows') }}
|
||||
run: |
|
||||
zip -r stockfish-$NAME-$BINARY.zip stockfish
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ Motohiro Isozaki (yaneurao)
|
||||
Hisayori Noda (nodchip)
|
||||
|
||||
# All other authors of Stockfish code (in alphabetical order)
|
||||
87flowers
|
||||
Aditya (absimaldata)
|
||||
Adrian Petrescu (apetresc)
|
||||
Ahmed Kerimov (wcdbmv)
|
||||
@@ -20,6 +21,7 @@ Alexander Kure
|
||||
Alexander Pagel (Lolligerhans)
|
||||
Alfredo Menezes (lonfom169)
|
||||
Ali AlZhrani (Cooffe)
|
||||
AliceRoselia
|
||||
Andreas Jan van der Meulen (Andyson007)
|
||||
Andreas Matthies (Matthies)
|
||||
Andrei Vetrov (proukornew)
|
||||
@@ -29,10 +31,13 @@ Andy Duplain
|
||||
Antoine Champion (antoinechampion)
|
||||
Aram Tumanian (atumanian)
|
||||
Arjun Temurnikar
|
||||
Aron Petkovski (fury)
|
||||
Arseniy Surkov (codedeliveryservice)
|
||||
Artem Solopiy (EntityFX)
|
||||
Auguste Pop
|
||||
Balazs Szilagyi
|
||||
Balint Pfliegel
|
||||
Baptiste Rech (breatn)
|
||||
Ben Chaney (Chaneybenjamini)
|
||||
Ben Koshy (BKSpurgeon)
|
||||
Bill Henry (VoyagerOne)
|
||||
@@ -53,10 +58,12 @@ Ciekce
|
||||
clefrks
|
||||
Clemens L. (rn5f107s2)
|
||||
Cody Ho (aesrentai)
|
||||
CSTENTOR
|
||||
Dale Weiler (graphitemaster)
|
||||
Daniel Axtens (daxtens)
|
||||
Daniel Dugovic (ddugovic)
|
||||
Daniel Monroe (Ergodice)
|
||||
Daniel Monroe (daniel-monroe)
|
||||
Daniel Samek (DanSamek)
|
||||
Dan Schmidt (dfannius)
|
||||
Dariusz Orzechowski (dorzechowski)
|
||||
David (dav1312)
|
||||
@@ -104,6 +111,7 @@ Hongzhi Cheng
|
||||
Ivan Ivec (IIvec)
|
||||
Jacques B. (Timshel)
|
||||
Jake Senne (w1wwwwww)
|
||||
Jakub Ciolek (jake-ciolek)
|
||||
Jan Ondruš (hxim)
|
||||
Jared Kish (Kurtbusch, kurt22i)
|
||||
Jarrod Torriero (DU-jdto)
|
||||
@@ -119,16 +127,21 @@ Jonathan McDermid (jonathanmcdermid)
|
||||
Joost VandeVondele (vondele)
|
||||
Joseph Ellis (jhellis3)
|
||||
Joseph R. Prostko
|
||||
Jost Triller (tsoj)
|
||||
Jörg Oster (joergoster)
|
||||
Julian Willemer (NightlyKing)
|
||||
jundery
|
||||
Justin Blanchard (UncombedCoconut)
|
||||
Kazuki Yamashita (KazApps)
|
||||
Kelly Wilson
|
||||
Ken Takusagawa
|
||||
Kenneth Lee (kennethlee33)
|
||||
kevlu8
|
||||
Kian E (KJE-98)
|
||||
Kieren Pearson (KierenP)
|
||||
kinderchocolate
|
||||
Kiran Panditrao (Krgp)
|
||||
Kirill Zaripov (kokodio)
|
||||
Kojirion
|
||||
Krisztián Peőcz
|
||||
Krystian Kuzniarek (kuzkry)
|
||||
@@ -145,6 +158,7 @@ Lucas Braesch (lucasart)
|
||||
Lyudmil Antonov (lantonov)
|
||||
Maciej Żenczykowski (zenczykowski)
|
||||
Malcolm Campbell (xoto10)
|
||||
Mark Marosi (Mapika)
|
||||
Mark Tenzer (31m059)
|
||||
marotear
|
||||
Mathias Parnaudeau (mparnaudeau)
|
||||
@@ -172,6 +186,7 @@ Nathan Rugg (nmrugg)
|
||||
Nguyen Pham (nguyenpham)
|
||||
Nicklas Persson (NicklasPersson)
|
||||
Nick Pelling (nickpelling)
|
||||
Nicolas Duhamel (nikloskoda)
|
||||
Niklas Fiekas (niklasf)
|
||||
Nikolay Kostov (NikolayIT)
|
||||
Norman Schmidt (FireFather)
|
||||
@@ -187,6 +202,7 @@ Panthee
|
||||
Pascal Romaret
|
||||
Pasquale Pigazzini (ppigazzini)
|
||||
Patrick Jansen (mibere)
|
||||
Patrick Leonhardt (Yoshie2000)
|
||||
Peter Schneider (pschneider1968)
|
||||
Peter Zsifkovits (CoffeeOne)
|
||||
PikaCat
|
||||
@@ -205,6 +221,7 @@ Ronald de Man (syzygy1, syzygy)
|
||||
Ron Britvich (Britvich)
|
||||
rqs
|
||||
Rui Coelho (ruicoelhopedro)
|
||||
rustam-cpp
|
||||
Ryan Schmitt
|
||||
Ryan Takker
|
||||
Sami Kiminki (skiminki)
|
||||
@@ -224,12 +241,15 @@ Stefano Di Martino (StefanoD)
|
||||
Steinar Gunderson (sesse)
|
||||
Stéphane Nicolet (snicolet)
|
||||
Stephen Touset (stouset)
|
||||
Stockfisher69
|
||||
Styx (styxdoto)
|
||||
Syine Mineta (MinetaS)
|
||||
Taras Vuk (TarasVuk)
|
||||
Thanar2
|
||||
thaspel
|
||||
theo77186
|
||||
TierynnB
|
||||
Timothy Herchen (anematode)
|
||||
Ting-Hsuan Huang (fffelix-huang)
|
||||
Tobias Steinmann
|
||||
Tomasz Sobczyk (Sopel97)
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ discussion._
|
||||
|
||||
Changes to Stockfish C++ code should respect our coding style defined by
|
||||
[.clang-format](.clang-format). You can format your changes by running
|
||||
`make format`. This requires clang-format version 18 to be installed on your system.
|
||||
`make format`. This requires clang-format version 20 to be installed on your system.
|
||||
|
||||
## Navigate
|
||||
|
||||
|
||||
+132
-95
@@ -1,89 +1,101 @@
|
||||
Contributors to Fishtest with >10,000 CPU hours, as of 2025-03-22.
|
||||
Contributors to Fishtest with >10,000 CPU hours, as of 2025-12-24.
|
||||
Thank you!
|
||||
|
||||
Username CPU Hours Games played
|
||||
------------------------------------------------------------------
|
||||
noobpwnftw 41712226 3294628533
|
||||
vdv 28993864 954145232
|
||||
technologov 24984442 1115931964
|
||||
linrock 11463033 741692823
|
||||
noobpwnftw 42692720 3385202467
|
||||
vdv 39922218 1277282126
|
||||
technologov 26354561 1163905856
|
||||
linrock 12002255 785641643
|
||||
olafm 3030005 197722318
|
||||
mlang 3026000 200065824
|
||||
okrout 2726068 248285678
|
||||
olafm 2420096 161297116
|
||||
pemo 1838361 62294199
|
||||
TueRens 1804847 80170868
|
||||
okrout 3020471 268364402
|
||||
pemo 2009761 66178221
|
||||
TueRens 1956328 83294326
|
||||
sebastronomy 1806628 73868874
|
||||
dew 1689162 100033738
|
||||
sebastronomy 1655637 67294942
|
||||
grandphish2 1474752 92156319
|
||||
JojoM 1130625 73666098
|
||||
rpngn 973590 59996557
|
||||
oz 921203 60370346
|
||||
grandphish2 1479778 92306101
|
||||
JojoM 1130646 73666860
|
||||
rpngn 1081976 65292619
|
||||
oz 1029329 69522328
|
||||
gvreuls 844572 59249068
|
||||
tvijlbrief 796125 51897690
|
||||
gvreuls 792215 55184194
|
||||
mibere 703840 46867607
|
||||
leszek 599745 44681421
|
||||
leszek 609538 45301765
|
||||
cw 519602 34988289
|
||||
fastgm 503862 30260818
|
||||
CSU_Dynasty 474794 31654170
|
||||
maximmasiutin 441753 28129452
|
||||
robal 437950 28869118
|
||||
ctoks 435150 28542141
|
||||
robal 503208 32703510
|
||||
maximmasiutin 500174 30818270
|
||||
CSU_Dynasty 481663 31916842
|
||||
ctoks 435431 28551199
|
||||
crunchy 427414 27371625
|
||||
bcross 415724 29061187
|
||||
mgrabiak 380202 27586936
|
||||
tolkki963 358623 26373242
|
||||
velislav 342588 22140902
|
||||
ncfish1 329039 20624527
|
||||
Fisherman 327231 21829379
|
||||
Sylvain27 317021 11494912
|
||||
Fifis 323909 16200123
|
||||
Sylvain27 320732 11671388
|
||||
marrco 310446 19587107
|
||||
Calis007 310201 18969692
|
||||
Viren6 297938 5847458
|
||||
Dantist 296386 18031762
|
||||
Fifis 289595 14969251
|
||||
tolkki963 286043 23596996
|
||||
Calis007 272677 17281620
|
||||
naclosagc 296040 13865010
|
||||
anematode 293146 3918134
|
||||
maposora 278093 20454200
|
||||
javran 271465 20506096
|
||||
cody 258835 13301710
|
||||
nordlandia 249322 16420192
|
||||
javran 212141 16507618
|
||||
Goatminola 218812 21411814
|
||||
Torom 211061 7238522
|
||||
glinscott 208125 13277240
|
||||
drabel 204167 13930674
|
||||
Wencey 203584 9943614
|
||||
mhoram 202894 12601997
|
||||
sschnee 201756 12874780
|
||||
bking_US 198894 11876016
|
||||
Wencey 198537 9606420
|
||||
Mineta 195312 10337614
|
||||
Thanar 179852 12365359
|
||||
sschnee 170521 10891112
|
||||
armo9494 168141 11177514
|
||||
armo9494 169747 11254404
|
||||
amicic 161636 11290899
|
||||
DesolatedDodo 160605 10392474
|
||||
markkulix 158320 13538874
|
||||
spams 157128 10319326
|
||||
maposora 155839 13963260
|
||||
sqrt2 147963 9724586
|
||||
vdbergh 140514 9242985
|
||||
vdbergh 141201 9308647
|
||||
jcAEie 140086 10603658
|
||||
CoffeeOne 137100 5024116
|
||||
malala 136182 8002293
|
||||
Goatminola 134893 11640524
|
||||
xoto 133759 9159372
|
||||
markkulix 132104 11000548
|
||||
naclosagc 131472 4660806
|
||||
Dubslow 129685 8527664
|
||||
Dubslow 130795 8609646
|
||||
zeryl 129154 7911565
|
||||
davar 129023 8376525
|
||||
DMBK 122960 8980062
|
||||
cuistot 122470 8393996
|
||||
megaman7de 122254 8066174
|
||||
dsmith 122059 7570238
|
||||
Wolfgang 120919 8619168
|
||||
CypressChess 120902 8683904
|
||||
amicic 119661 7938029
|
||||
cuistot 116864 7828864
|
||||
sterni1971 113754 6054022
|
||||
Spprtr 113356 8129809
|
||||
Data 113305 8220352
|
||||
BrunoBanani 112960 7436849
|
||||
megaman7de 109139 7360928
|
||||
skiminki 107583 7218170
|
||||
zeryl 104523 6618969
|
||||
MediumBerry5575 103884 7830022
|
||||
MaZePallas 102823 6633619
|
||||
YvesKn 102213 5098076
|
||||
sunu 100167 7040199
|
||||
thirdlife 99178 2246544
|
||||
thirdlife 99182 2246960
|
||||
ElbertoOne 99028 7023771
|
||||
TechiePirate 98957 1249064
|
||||
DeepnessFulled 97313 5083358
|
||||
TataneSan 97257 4239502
|
||||
romangol 95662 7784954
|
||||
bigpen0r 94825 6529241
|
||||
jojo2357 94358 7635486
|
||||
malfoy 92712 3392874
|
||||
voidedstarlight 92582 2342038
|
||||
brabos 92118 6186135
|
||||
Maxim 90818 3283364
|
||||
psk 89957 5984901
|
||||
@@ -92,26 +104,26 @@ jromang 87260 5988073
|
||||
racerschmacer 85805 6122790
|
||||
Vizvezdenec 83761 5344740
|
||||
0x3C33 82614 5271253
|
||||
Spprtr 82103 5663635
|
||||
MarcusTullius 82359 5335665
|
||||
BRAVONE 81239 5054681
|
||||
MarcusTullius 78930 5189659
|
||||
Mineta 78731 4947996
|
||||
Torom 77978 2651656
|
||||
rn 78566 6000852
|
||||
nssy 76497 5259388
|
||||
woutboat 76379 6031688
|
||||
teddybaer 75125 5407666
|
||||
Pking_cda 73776 5293873
|
||||
Viren6 73664 1356502
|
||||
yurikvelo 73611 5046822
|
||||
Zirie 71260 4602355
|
||||
Bobo1239 70579 4794999
|
||||
solarlight 70517 5028306
|
||||
dv8silencer 70287 3883992
|
||||
0x539 67147 2918044
|
||||
manap 66273 4121774
|
||||
tinker 64333 4268790
|
||||
CounterFlow 63914 3775062
|
||||
mecevdimitar 62493 3508750
|
||||
DanielMiao1 62188 1335664
|
||||
qurashee 61208 3429862
|
||||
DanielMiao1 60181 1317252
|
||||
AGI 58316 4336328
|
||||
jojo2357 57435 4944212
|
||||
robnjr 57262 4053117
|
||||
Freja 56938 3733019
|
||||
MaxKlaxxMiner 56879 3423958
|
||||
@@ -120,44 +132,52 @@ rkl 55132 4164467
|
||||
jmdana 54988 4041917
|
||||
notchris 53936 4184018
|
||||
renouve 53811 3501516
|
||||
CounterFlow 52536 3203740
|
||||
jibarbosa 53504 5110028
|
||||
somethingintheshadows 52333 4344808
|
||||
finfish 51360 3370515
|
||||
eva42 51272 3599691
|
||||
eastorwest 51117 3454811
|
||||
sylvek 50391 3765170
|
||||
rap 49985 3219146
|
||||
pb00067 49733 3298934
|
||||
GPUex 48686 3684998
|
||||
OuaisBla 48626 3445134
|
||||
lemtea 48563 1672454
|
||||
ronaldjerum 47654 3240695
|
||||
abdicj 46740 2709482
|
||||
biffhero 46564 3111352
|
||||
oryx 46141 3583236
|
||||
jibarbosa 45890 4541218
|
||||
DeepnessFulled 45734 3944282
|
||||
abdicj 45577 2631772
|
||||
oryx 46422 3607582
|
||||
VoyagerOne 45476 3452465
|
||||
mecevdimitar 44240 2584396
|
||||
rdp65536 43948 2881890
|
||||
speedycpu 43842 3003273
|
||||
jbwiebe 43305 2805433
|
||||
gopeto 43046 2821514
|
||||
YvesKn 42628 2177630
|
||||
Antihistamine 41788 2761312
|
||||
mhunt 41735 2691355
|
||||
somethingintheshadows 41502 3330418
|
||||
WoodMan777 40858 3491196
|
||||
Epic29 40771 4067404
|
||||
drauh 40419 1634770
|
||||
homyur 39893 2850481
|
||||
gri 39871 2515779
|
||||
vidar808 39774 1656372
|
||||
Gaster319 38994 3477702
|
||||
Garf 37741 2999686
|
||||
SC 37299 2731694
|
||||
Gaster319 37229 3289674
|
||||
ZacHFX 36533 2553282
|
||||
csnodgrass 36207 2688994
|
||||
ZacHFX 35528 2486328
|
||||
icewulf 34782 2415146
|
||||
icewulf 34935 2421834
|
||||
strelock 34716 2074055
|
||||
Jopo12321 33921 2531448
|
||||
xuhdev 33798 3295210
|
||||
csnodgra 33780 1446866
|
||||
EthanOConnor 33370 2090311
|
||||
slakovv 32915 2021889
|
||||
shawnxu 32144 2814668
|
||||
IslandLambda 32667 1659344
|
||||
Kataiser 32477 2688862
|
||||
shawnxu 32330 2830036
|
||||
srowen 32248 1791136
|
||||
qgluca 31941 2491622
|
||||
Gelma 31771 1551204
|
||||
srowen 31181 1732120
|
||||
kdave 31157 2198362
|
||||
manapbk 30987 1810399
|
||||
votoanthuan 30691 2460856
|
||||
@@ -168,15 +188,26 @@ spcc 29925 1901692
|
||||
hyperbolic.tom 29840 2017394
|
||||
chuckstablers 29659 2093438
|
||||
Pyafue 29650 1902349
|
||||
WoodMan777 29300 2579864
|
||||
Flopzee 29388 1899905
|
||||
hoching 29054 2067144
|
||||
belzedar94 28846 1811530
|
||||
wizardassassin 28007 2318204
|
||||
purpletree 27892 2061966
|
||||
Kyrega 27674 963872
|
||||
joendter 27193 1781570
|
||||
Danielv123 27132 1043614
|
||||
chriswk 26902 1868317
|
||||
xwziegtm 26897 2124586
|
||||
Jopo12321 26818 1816482
|
||||
spotscene 26877 2139674
|
||||
achambord 26582 1767323
|
||||
shreven 26448 1703328
|
||||
Patrick_G 26276 1801617
|
||||
yorkman 26193 1992080
|
||||
Ulysses 25517 1711634
|
||||
ols 26173 1443517
|
||||
wer 26136 793146
|
||||
Skiff84 26083 1135002
|
||||
RudyMars 25980 2211364
|
||||
Ulysses 25544 1714542
|
||||
SFTUser 25182 1675689
|
||||
nabildanial 25068 1531665
|
||||
Sharaf_DG 24765 1786697
|
||||
@@ -184,30 +215,28 @@ rodneyc 24376 1416402
|
||||
jsys14 24297 1721230
|
||||
AndreasKrug 24235 1934711
|
||||
agg177 23890 1395014
|
||||
Disservin 23768 1934576
|
||||
Ente 23752 1678188
|
||||
JanErik 23408 1703875
|
||||
Isidor 23388 1680691
|
||||
Norabor 23371 1603244
|
||||
Nullvalue 23155 2022752
|
||||
fishtester 23115 1581502
|
||||
wizardassassin 23073 1789536
|
||||
Skiff84 22984 1053680
|
||||
cisco2015 22920 1763301
|
||||
ols 22914 1322047
|
||||
Hjax 22561 1566151
|
||||
Zirie 22542 1472937
|
||||
gerbil 22435 1679842
|
||||
Serpensin 22396 1861156
|
||||
team-oh 22272 1636708
|
||||
mkstockfishtester 22253 2029566
|
||||
Roady 22220 1465606
|
||||
tsim67 22077 1353048
|
||||
MazeOfGalious 21978 1629593
|
||||
sg4032 21950 1643373
|
||||
tsim67 21939 1343944
|
||||
sev 21791 1983016
|
||||
ianh2105 21725 1632562
|
||||
Serpensin 21704 1809188
|
||||
xor12 21628 1680365
|
||||
dex 21612 1467203
|
||||
nesoneg 21494 1463031
|
||||
IslandLambda 21468 1239756
|
||||
user213718 21454 1404128
|
||||
sphinx 21211 1384728
|
||||
qoo_charly_cai 21136 1514927
|
||||
@@ -215,22 +244,20 @@ jjoshua2 21001 1423089
|
||||
Zake9298 20938 1565848
|
||||
horst.prack 20878 1465656
|
||||
0xB00B1ES 20590 1208666
|
||||
t3hf1sht3ster 20544 673134
|
||||
Dinde 20459 1292774
|
||||
t3hf1sht3ster 20456 670646
|
||||
j3corre 20405 941444
|
||||
0x539 20332 1039516
|
||||
Adrian.Schmidt123 20316 1281436
|
||||
malfoy 20313 1350694
|
||||
purpletree 20019 1461026
|
||||
wei 19973 1745989
|
||||
teenychess 19819 1762006
|
||||
RickGroszkiewicz 19749 1913986
|
||||
rstoesser 19569 1293588
|
||||
eudhan 19274 1283717
|
||||
nalanzeyu 19211 396674
|
||||
vulcan 18871 1729392
|
||||
Karpovbot 18766 1053178
|
||||
Farseer 18536 1078326
|
||||
jundery 18445 1115855
|
||||
Farseer 18281 1074642
|
||||
sebv15 18267 1262588
|
||||
whelanh 17887 347974
|
||||
ville 17883 1384026
|
||||
@@ -239,84 +266,94 @@ purplefishies 17595 1092533
|
||||
dju 17414 981289
|
||||
iisiraider 17275 1049015
|
||||
Karby 17177 1030688
|
||||
fogleman 17134 815562
|
||||
zhujianzhao 17111 1666972
|
||||
DragonLord 17014 1162790
|
||||
pirt 16991 1274215
|
||||
pirt 16993 1274363
|
||||
redstone59 16842 1461780
|
||||
Alb11747 16787 1213990
|
||||
Naven94 16414 951718
|
||||
scuzzi 16155 995347
|
||||
IgorLeMasson 16064 1147232
|
||||
micpilar 15866 1399266
|
||||
ako027ako 15671 1173203
|
||||
xuhdev 15516 1528278
|
||||
infinigon 15285 965966
|
||||
fishtrawler 15205 1436165
|
||||
Nikolay.IT 15154 1068349
|
||||
Andrew Grant 15114 895539
|
||||
OssumOpossum 14857 1007129
|
||||
LunaticBFF57 14525 1190310
|
||||
YELNAMRON 14480 1141420
|
||||
enedene 14476 905279
|
||||
YELNAMRON 14475 1141330
|
||||
RickGroszkiewicz 14272 1385984
|
||||
joendter 14269 982014
|
||||
MooTheCow 14459 1023868
|
||||
BestBoyBerlin 14353 1365584
|
||||
bpfliegel 14233 882523
|
||||
mpx86 14019 759568
|
||||
jpulman 13982 870599
|
||||
getraideBFF 13871 1172846
|
||||
crocogoat 13817 1119086
|
||||
Nesa92 13806 1116101
|
||||
joster 13710 946160
|
||||
joster 13717 946960
|
||||
mbeier 13650 1044928
|
||||
Pablohn26 13552 1088532
|
||||
wxt9861 13550 1312306
|
||||
biniek 13469 930029
|
||||
Dark_wizzie 13422 1007152
|
||||
Jackfish 13422 914984
|
||||
Hongildong 13297 699288
|
||||
Rudolphous 13244 883140
|
||||
Jackfish 13177 894206
|
||||
MooTheCow 13091 892304
|
||||
Phoenix17 13032 1124066
|
||||
Machariel 13010 863104
|
||||
mabichito 12903 749391
|
||||
FormazChar 12899 980413
|
||||
thijsk 12886 722107
|
||||
AdrianSA 12860 804972
|
||||
Flopzee 12698 894821
|
||||
szczur90 12684 977536
|
||||
Kyrega 12661 456438
|
||||
szczur90 12720 979324
|
||||
mschmidt 12644 863193
|
||||
korposzczur 12606 838168
|
||||
fatmurphy 12547 853210
|
||||
Oakwen 12532 855759
|
||||
Oakwen 12537 856257
|
||||
SapphireBrand 12416 969604
|
||||
Snuuka 12392 509082
|
||||
deflectooor 12386 579392
|
||||
modolief 12386 896470
|
||||
ckaz 12273 754644
|
||||
Hongildong 12201 648712
|
||||
pgontarz 12151 848794
|
||||
dbernier 12103 860824
|
||||
FormazChar 12051 913497
|
||||
shreven 12044 884734
|
||||
rensonthemove 11999 971993
|
||||
stocky 11954 699440
|
||||
ali-al-zhrani 11887 836126
|
||||
3cho 11842 1036786
|
||||
Craftyawesome 11736 832254
|
||||
dragon123118 11578 1044142
|
||||
ImperiumAeternum 11482 979142
|
||||
lvdv 11475 594400
|
||||
infinity 11470 727027
|
||||
kusihe 11468 468450
|
||||
vaskoul 11446 976902
|
||||
aga 11412 695127
|
||||
Def9Infinity 11408 700682
|
||||
torbjo 11395 729145
|
||||
Thomas A. Anderson 11372 732094
|
||||
savage84 11358 670860
|
||||
d64 11263 789184
|
||||
ali-al-zhrani 11245 779246
|
||||
vaskoul 11144 953906
|
||||
Poly 11172 455568
|
||||
enizor 11140 630194
|
||||
snicolet 11106 869170
|
||||
dapper 11032 771402
|
||||
Ethnikoi 10993 945906
|
||||
Snuuka 10938 435504
|
||||
Karmatron 10871 678306
|
||||
gerbil 10871 1005842
|
||||
zarthus 10773 1034536
|
||||
OliverClarke 10696 942654
|
||||
Omed 10680 669816
|
||||
cyberthink 10647 936538
|
||||
basepi 10637 744851
|
||||
michaelrpg 10624 748179
|
||||
Cubox 10621 826448
|
||||
dragon123118 10421 936506
|
||||
GBx3TV 10499 343266
|
||||
Styx 10450 867836
|
||||
OIVAS7572 10420 995586
|
||||
GBx3TV 10388 339952
|
||||
Garruk 10365 706465
|
||||
dzjp 10343 732529
|
||||
Lorenz 10311 886308
|
||||
borinot 10026 902130
|
||||
|
||||
@@ -31,7 +31,7 @@ set_arch_loongarch64() {
|
||||
if check_flags 'lasx'; then
|
||||
true_arch='loongarch64-lasx'
|
||||
elif check_flags 'lsx'; then
|
||||
true_arch='lonngarch64-lsx'
|
||||
true_arch='loongarch64-lsx'
|
||||
else
|
||||
true_arch='loongarch64'
|
||||
fi
|
||||
@@ -39,10 +39,14 @@ set_arch_loongarch64() {
|
||||
|
||||
# Set the file CPU x86_64 architecture
|
||||
set_arch_x86_64() {
|
||||
if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then
|
||||
true_arch='x86-64-vnni256'
|
||||
if check_flags 'avx512f' 'avx512cd' 'avx512vl' 'avx512dq' 'avx512bw' 'avx512ifma' 'avx512vbmi' 'avx512vbmi2' 'avx512vpopcntdq' 'avx512bitalg' 'avx512vnni' 'vpclmulqdq' 'gfni' 'vaes'; then
|
||||
true_arch='x86-64-avx512icl'
|
||||
elif check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then
|
||||
true_arch='x86-64-vnni512'
|
||||
elif check_flags 'avx512f' 'avx512bw'; then
|
||||
true_arch='x86-64-avx512'
|
||||
elif check_flags 'avxvnni'; then
|
||||
true_arch='x86-64-avxvnni'
|
||||
elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then
|
||||
true_arch='x86-64-bmi2'
|
||||
elif check_flags 'avx2'; then
|
||||
@@ -55,7 +59,7 @@ set_arch_x86_64() {
|
||||
}
|
||||
|
||||
set_arch_ppc_64() {
|
||||
if $(grep -q -w "altivec" /proc/cpuinfo); then
|
||||
if grep -q -w "altivec" /proc/cpuinfo; then
|
||||
power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo)
|
||||
if [ "0$power" -gt 7 ]; then
|
||||
# VSX started with POWER8
|
||||
@@ -81,7 +85,7 @@ case $uname_s in
|
||||
'x86_64')
|
||||
flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.')
|
||||
set_arch_x86_64
|
||||
if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then
|
||||
if [ "$true_arch" = 'x86-64-avx512' ]; then
|
||||
file_arch='x86-64-bmi2'
|
||||
fi
|
||||
;;
|
||||
@@ -130,7 +134,13 @@ case $uname_s in
|
||||
esac
|
||||
file_ext='tar'
|
||||
;;
|
||||
'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows system with POSIX compatibility layer
|
||||
'MINGW'*'ARM64'*) # Windows ARM64 system with POSIX compatibility layer
|
||||
# TODO: older chips might be armv8, but we have no good way to detect, /proc/cpuinfo shows x86 info
|
||||
file_os='windows'
|
||||
true_arch='armv8-dotprod'
|
||||
file_ext='zip'
|
||||
;;
|
||||
'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows x86_64system with POSIX compatibility layer
|
||||
get_flags
|
||||
check_znver_1_2
|
||||
set_arch_x86_64
|
||||
|
||||
+101
-60
@@ -1,5 +1,5 @@
|
||||
# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
# Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
# Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
#
|
||||
# Stockfish is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -25,6 +25,21 @@ ifeq ($(KERNEL),Linux)
|
||||
OS := $(shell uname -o)
|
||||
endif
|
||||
|
||||
### Command prefix to run the built executable (e.g. wine, sde, qemu)
|
||||
### Backward compatible alias: WINE_PATH (deprecated)
|
||||
ifneq ($(strip $(WINE_PATH)),)
|
||||
ifeq ($(strip $(RUN_PREFIX)),)
|
||||
RUN_PREFIX := $(WINE_PATH)
|
||||
endif
|
||||
ifeq ($(MAKELEVEL),0)
|
||||
ifneq ($(strip $(RUN_PREFIX)),$(strip $(WINE_PATH)))
|
||||
$(warning *** Both RUN_PREFIX and WINE_PATH are set; ignoring WINE_PATH. ***)
|
||||
else
|
||||
$(warning *** WINE_PATH is deprecated; use RUN_PREFIX instead. ***)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
### Target Windows OS
|
||||
ifeq ($(OS),Windows_NT)
|
||||
ifneq ($(COMP),ndk)
|
||||
@@ -32,8 +47,8 @@ ifeq ($(OS),Windows_NT)
|
||||
endif
|
||||
else ifeq ($(COMP),mingw)
|
||||
target_windows = yes
|
||||
ifeq ($(WINE_PATH),)
|
||||
WINE_PATH := $(shell which wine)
|
||||
ifeq ($(RUN_PREFIX),)
|
||||
RUN_PREFIX := $(shell which wine)
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -49,21 +64,22 @@ PREFIX = /usr/local
|
||||
BINDIR = $(PREFIX)/bin
|
||||
|
||||
### Built-in benchmark for pgo-builds
|
||||
PGOBENCH = $(WINE_PATH) ./$(EXE) bench
|
||||
PGOBENCH = $(RUN_PREFIX) ./$(EXE) bench
|
||||
|
||||
### Source and object files
|
||||
SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
|
||||
misc.cpp movegen.cpp movepick.cpp position.cpp \
|
||||
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
|
||||
nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp \
|
||||
nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/network.cpp \
|
||||
nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp \
|
||||
engine.cpp score.cpp memory.cpp
|
||||
|
||||
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/layers/affine_transform.h \
|
||||
nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \
|
||||
nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \
|
||||
nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \
|
||||
search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
|
||||
nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/features/full_threats.h \
|
||||
nnue/layers/affine_transform.h nnue/layers/affine_transform_sparse_input.h \
|
||||
nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h \
|
||||
nnue/nnue_architecture.h nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h \
|
||||
position.h search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
|
||||
tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h
|
||||
|
||||
OBJS = $(notdir $(SRCS:.cpp=.o))
|
||||
@@ -97,8 +113,8 @@ VPATH = syzygy:nnue:nnue/features
|
||||
# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2
|
||||
# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX
|
||||
# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512
|
||||
# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands
|
||||
# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
|
||||
# avx512icl = yes/no --- ... multiple ... --- Use All AVX-512 features available on both Intel Ice Lake and AMD Zen 4
|
||||
# altivec = yes/no --- -maltivec --- Use PowerPC Altivec SIMD extension
|
||||
# vsx = yes/no --- -mvsx --- Use POWER VSX SIMD extension
|
||||
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
|
||||
@@ -125,10 +141,10 @@ ifeq ($(ARCH), native)
|
||||
endif
|
||||
|
||||
# explicitly check for the list of supported architectures (as listed with make help),
|
||||
# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true`
|
||||
# the user can override with `make ARCH=x86-64-avx512icl SUPPORTED_ARCH=true`
|
||||
ifeq ($(ARCH), $(filter $(ARCH), \
|
||||
x86-64-vnni512 x86-64-vnni256 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-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 \
|
||||
loongarch64 loongarch64-lsx loongarch64-lasx))
|
||||
@@ -152,8 +168,8 @@ sse41 = no
|
||||
avx2 = no
|
||||
avxvnni = no
|
||||
avx512 = no
|
||||
vnni256 = no
|
||||
vnni512 = no
|
||||
avx512icl = no
|
||||
altivec = no
|
||||
vsx = no
|
||||
neon = no
|
||||
@@ -163,8 +179,8 @@ lsx = no
|
||||
lasx = no
|
||||
STRIP = strip
|
||||
|
||||
ifneq ($(shell which clang-format-18 2> /dev/null),)
|
||||
CLANG-FORMAT = clang-format-18
|
||||
ifneq ($(shell which clang-format-20 2> /dev/null),)
|
||||
CLANG-FORMAT = clang-format-20
|
||||
else
|
||||
CLANG-FORMAT = clang-format
|
||||
endif
|
||||
@@ -267,17 +283,6 @@ ifeq ($(findstring -avx512,$(ARCH)),-avx512)
|
||||
avx512 = yes
|
||||
endif
|
||||
|
||||
ifeq ($(findstring -vnni256,$(ARCH)),-vnni256)
|
||||
popcnt = yes
|
||||
sse = yes
|
||||
sse2 = yes
|
||||
ssse3 = yes
|
||||
sse41 = yes
|
||||
avx2 = yes
|
||||
pext = yes
|
||||
vnni256 = yes
|
||||
endif
|
||||
|
||||
ifeq ($(findstring -vnni512,$(ARCH)),-vnni512)
|
||||
popcnt = yes
|
||||
sse = yes
|
||||
@@ -290,6 +295,19 @@ ifeq ($(findstring -vnni512,$(ARCH)),-vnni512)
|
||||
vnni512 = yes
|
||||
endif
|
||||
|
||||
ifeq ($(findstring -avx512icl,$(ARCH)),-avx512icl)
|
||||
popcnt = yes
|
||||
sse = yes
|
||||
sse2 = yes
|
||||
ssse3 = yes
|
||||
sse41 = yes
|
||||
avx2 = yes
|
||||
pext = yes
|
||||
avx512 = yes
|
||||
vnni512 = yes
|
||||
avx512icl = yes
|
||||
endif
|
||||
|
||||
ifeq ($(sse),yes)
|
||||
prefetch = yes
|
||||
endif
|
||||
@@ -533,14 +551,12 @@ ifeq ($(KERNEL),Darwin)
|
||||
XCRUN = xcrun
|
||||
endif
|
||||
|
||||
# To cross-compile for Android, NDK version r21 or later is recommended.
|
||||
# In earlier NDK versions, you'll need to pass -fno-addrsig if using GNU binutils.
|
||||
# Currently we don't know how to make PGO builds with the NDK yet.
|
||||
# To cross-compile for Android, use NDK version r27c or later.
|
||||
ifeq ($(COMP),ndk)
|
||||
CXXFLAGS += -stdlib=libc++ -fPIE
|
||||
CXXFLAGS += -stdlib=libc++
|
||||
comp=clang
|
||||
ifeq ($(arch),armv7)
|
||||
CXX=armv7a-linux-androideabi16-clang++
|
||||
CXX=armv7a-linux-androideabi29-clang++
|
||||
CXXFLAGS += -mthumb -march=armv7-a -mfloat-abi=softfp -mfpu=neon
|
||||
ifneq ($(shell which arm-linux-androideabi-strip 2>/dev/null),)
|
||||
STRIP=arm-linux-androideabi-strip
|
||||
@@ -549,7 +565,7 @@ ifeq ($(COMP),ndk)
|
||||
endif
|
||||
endif
|
||||
ifeq ($(arch),armv8)
|
||||
CXX=aarch64-linux-android21-clang++
|
||||
CXX=aarch64-linux-android29-clang++
|
||||
ifneq ($(shell which aarch64-linux-android-strip 2>/dev/null),)
|
||||
STRIP=aarch64-linux-android-strip
|
||||
else
|
||||
@@ -557,14 +573,32 @@ ifeq ($(COMP),ndk)
|
||||
endif
|
||||
endif
|
||||
ifeq ($(arch),x86_64)
|
||||
CXX=x86_64-linux-android21-clang++
|
||||
CXX=x86_64-linux-android29-clang++
|
||||
ifneq ($(shell which x86_64-linux-android-strip 2>/dev/null),)
|
||||
STRIP=x86_64-linux-android-strip
|
||||
else
|
||||
STRIP=llvm-strip
|
||||
endif
|
||||
endif
|
||||
LDFLAGS += -static-libstdc++ -pie -lm -latomic
|
||||
LDFLAGS += -static-libstdc++
|
||||
endif
|
||||
|
||||
### Allow overwriting CXX from command line
|
||||
ifdef COMPCXX
|
||||
CXX=$(COMPCXX)
|
||||
endif
|
||||
|
||||
# llvm-profdata must be version compatible with the specified CXX (be it clang, or the gcc alias)
|
||||
# make -j profile-build CXX=clang++-20 COMP=clang
|
||||
# Locate the version in the same directory as the compiler used,
|
||||
# with fallback to a generic one if it can't be located
|
||||
LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))llvm-profdata
|
||||
# for icx
|
||||
ifeq ($(wildcard $(LLVM_PROFDATA)),)
|
||||
LLVM_PROFDATA := $(dir $(realpath $(shell which $(CXX) 2> /dev/null)))/compiler/llvm-profdata
|
||||
endif
|
||||
ifeq ($(wildcard $(LLVM_PROFDATA)),)
|
||||
LLVM_PROFDATA := llvm-profdata
|
||||
endif
|
||||
|
||||
ifeq ($(comp),icx)
|
||||
@@ -581,11 +615,6 @@ else
|
||||
endif
|
||||
endif
|
||||
|
||||
### Allow overwriting CXX from command line
|
||||
ifdef COMPCXX
|
||||
CXX=$(COMPCXX)
|
||||
endif
|
||||
|
||||
### Sometimes gcc is really clang
|
||||
ifeq ($(COMP),gcc)
|
||||
gccversion := $(shell $(CXX) --version 2>/dev/null)
|
||||
@@ -593,6 +622,8 @@ ifeq ($(COMP),gcc)
|
||||
ifneq ($(gccisclang),)
|
||||
profile_make = clang-profile-make
|
||||
profile_use = clang-profile-use
|
||||
else
|
||||
CXXFLAGS += -Wstack-usage=128000
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -605,6 +636,19 @@ ifneq ($(comp),mingw)
|
||||
ifneq ($(KERNEL),Haiku)
|
||||
ifneq ($(COMP),ndk)
|
||||
LDFLAGS += -lpthread
|
||||
|
||||
add_lrt = yes
|
||||
ifeq ($(target_windows),yes)
|
||||
add_lrt = no
|
||||
endif
|
||||
|
||||
ifeq ($(KERNEL),Darwin)
|
||||
add_lrt = no
|
||||
endif
|
||||
|
||||
ifeq ($(add_lrt),yes)
|
||||
LDFLAGS += -lrt
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -615,6 +659,7 @@ ifeq ($(debug),no)
|
||||
CXXFLAGS += -DNDEBUG
|
||||
else
|
||||
CXXFLAGS += -g
|
||||
CXXFLAGS += -D_GLIBCXX_ASSERTIONS -D_GLIBCXX_DEBUG
|
||||
endif
|
||||
|
||||
### 3.2.2 Debugging with undefined behavior sanitizers
|
||||
@@ -694,21 +739,21 @@ endif
|
||||
ifeq ($(avx512),yes)
|
||||
CXXFLAGS += -DUSE_AVX512
|
||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
|
||||
CXXFLAGS += -mavx512f -mavx512bw
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(vnni256),yes)
|
||||
CXXFLAGS += -DUSE_VNNI
|
||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
|
||||
CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256
|
||||
CXXFLAGS += -mavx512f -mavx512bw -mavx512dq -mavx512vl
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(vnni512),yes)
|
||||
CXXFLAGS += -DUSE_VNNI
|
||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
|
||||
CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512
|
||||
CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(avx512icl),yes)
|
||||
CXXFLAGS += -DUSE_AVX512 -DUSE_VNNI -DUSE_AVX512ICL
|
||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
|
||||
CXXFLAGS += -mavx512f -mavx512cd -mavx512vl -mavx512dq -mavx512bw -mavx512ifma -mavx512vbmi -mavx512vbmi2 -mavx512vpopcntdq -mavx512bitalg -mavx512vnni -mvpclmulqdq -mgfni -mvaes
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -870,8 +915,8 @@ help:
|
||||
echo "Supported archs:" && \
|
||||
echo "" && \
|
||||
echo "native > select the best architecture for the host processor (default)" && \
|
||||
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-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide" && \
|
||||
echo "x86-64-avx512 > x86 64-bit with avx512 support" && \
|
||||
echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support" && \
|
||||
echo "x86-64-bmi2 > x86 64-bit with bmi2 support" && \
|
||||
@@ -989,10 +1034,6 @@ net:
|
||||
format:
|
||||
$(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file
|
||||
|
||||
# default target
|
||||
default:
|
||||
help
|
||||
|
||||
### ==========================================================================
|
||||
### Section 5. Private Targets
|
||||
### ==========================================================================
|
||||
@@ -1020,8 +1061,8 @@ config-sanity: net
|
||||
echo "avx2: '$(avx2)'" && \
|
||||
echo "avxvnni: '$(avxvnni)'" && \
|
||||
echo "avx512: '$(avx512)'" && \
|
||||
echo "vnni256: '$(vnni256)'" && \
|
||||
echo "vnni512: '$(vnni512)'" && \
|
||||
echo "avx512icl: '$(avx512icl)'" && \
|
||||
echo "altivec: '$(altivec)'" && \
|
||||
echo "vsx: '$(vsx)'" && \
|
||||
echo "neon: '$(neon)'" && \
|
||||
@@ -1056,8 +1097,8 @@ config-sanity: net
|
||||
(test "$(sse41)" = "yes" || test "$(sse41)" = "no") && \
|
||||
(test "$(avx2)" = "yes" || test "$(avx2)" = "no") && \
|
||||
(test "$(avx512)" = "yes" || test "$(avx512)" = "no") && \
|
||||
(test "$(vnni256)" = "yes" || test "$(vnni256)" = "no") && \
|
||||
(test "$(vnni512)" = "yes" || test "$(vnni512)" = "no") && \
|
||||
(test "$(avx512icl)" = "yes" || test "$(avx512icl)" = "no") && \
|
||||
(test "$(altivec)" = "yes" || test "$(altivec)" = "no") && \
|
||||
(test "$(vsx)" = "yes" || test "$(vsx)" = "no") && \
|
||||
(test "$(neon)" = "yes" || test "$(neon)" = "no") && \
|
||||
@@ -1081,7 +1122,7 @@ clang-profile-make:
|
||||
all
|
||||
|
||||
clang-profile-use:
|
||||
$(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw
|
||||
$(XCRUN) $(LLVM_PROFDATA) merge -output=stockfish.profdata *.profraw
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
||||
EXTRACXXFLAGS='-fprofile-use=stockfish.profdata' \
|
||||
EXTRALDFLAGS='-fprofile-use ' \
|
||||
@@ -1109,7 +1150,7 @@ icx-profile-make:
|
||||
all
|
||||
|
||||
icx-profile-use:
|
||||
$(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw
|
||||
$(XCRUN) $(LLVM_PROFDATA) merge -output=stockfish.profdata *.profraw
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
||||
EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \
|
||||
EXTRALDFLAGS='-fprofile-use ' \
|
||||
@@ -1118,6 +1159,6 @@ icx-profile-use:
|
||||
.depend: $(SRCS)
|
||||
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
|
||||
|
||||
ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean config-sanity))
|
||||
ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean format config-sanity))
|
||||
-include .depend
|
||||
endif
|
||||
|
||||
+8
-5
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -65,6 +65,10 @@ const std::vector<std::string> Defaults = {
|
||||
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
|
||||
"4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
|
||||
|
||||
// Positions with high numbers of changed threats
|
||||
"k7/2n1n3/1nbNbn2/2NbRBn1/1nbRQR2/2NBRBN1/3N1N2/7K w - - 0 1",
|
||||
"K7/8/8/BNQNQNB1/N5N1/R1Q1q2r/n5n1/bnqnqnbk w - - 0 1",
|
||||
|
||||
// 5-man positions
|
||||
"8/8/8/8/5kp1/P7/8/1K1N4 w - - 0 1", // Kc2 - mate
|
||||
"8/8/8/5N2/8/p7/8/2NK3k w - - 0 1", // Na2 - mate
|
||||
@@ -450,7 +454,7 @@ BenchmarkSetup setup_benchmark(std::istream& is) {
|
||||
int desiredTimeS;
|
||||
|
||||
if (!(is >> setup.threads))
|
||||
setup.threads = get_hardware_concurrency();
|
||||
setup.threads = int(get_hardware_concurrency());
|
||||
else
|
||||
setup.originalInvocation += std::to_string(setup.threads);
|
||||
|
||||
@@ -479,11 +483,10 @@ BenchmarkSetup setup_benchmark(std::istream& is) {
|
||||
float totalTime = 0;
|
||||
for (const auto& game : BenchmarkPositions)
|
||||
{
|
||||
setup.commands.emplace_back("ucinewgame");
|
||||
int ply = 1;
|
||||
for (int i = 0; i < static_cast<int>(game.size()); ++i)
|
||||
{
|
||||
const float correctedTime = getCorrectedTime(ply);
|
||||
const float correctedTime = float(getCorrectedTime(ply));
|
||||
totalTime += correctedTime;
|
||||
ply += 1;
|
||||
}
|
||||
@@ -509,4 +512,4 @@ BenchmarkSetup setup_benchmark(std::istream& is) {
|
||||
return setup;
|
||||
}
|
||||
|
||||
} // namespace Stockfish
|
||||
} // namespace Stockfish
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
+10
-49
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -31,8 +31,7 @@ uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||
Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
||||
Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
|
||||
Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
alignas(64) Magic Magics[SQUARE_NB][2];
|
||||
|
||||
@@ -42,13 +41,6 @@ Bitboard RookTable[0x19000]; // To store rook attacks
|
||||
Bitboard BishopTable[0x1480]; // To store bishop attacks
|
||||
|
||||
void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]);
|
||||
|
||||
// Returns the bitboard of target square for the given step
|
||||
// from the given square. If the step is off the board, returns empty bitboard.
|
||||
Bitboard safe_destination(Square s, int step) {
|
||||
Square to = Square(s + step);
|
||||
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an ASCII representation of a bitboard suitable
|
||||
@@ -57,12 +49,15 @@ std::string Bitboards::pretty(Bitboard b) {
|
||||
|
||||
std::string s = "+---+---+---+---+---+---+---+---+\n";
|
||||
|
||||
for (Rank r = RANK_8; r >= RANK_1; --r)
|
||||
for (Rank r = RANK_8;; --r)
|
||||
{
|
||||
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||
s += b & make_square(f, r) ? "| X " : "| ";
|
||||
|
||||
s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
|
||||
|
||||
if (r == RANK_1)
|
||||
break;
|
||||
}
|
||||
s += " a b c d e f g h\n";
|
||||
|
||||
@@ -86,18 +81,6 @@ void Bitboards::init() {
|
||||
|
||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||
{
|
||||
PawnAttacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
|
||||
PawnAttacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
|
||||
|
||||
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9})
|
||||
PseudoAttacks[KING][s1] |= safe_destination(s1, step);
|
||||
|
||||
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17})
|
||||
PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step);
|
||||
|
||||
PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
|
||||
PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1] = attacks_bb<ROOK>(s1, 0);
|
||||
|
||||
for (PieceType pt : {BISHOP, ROOK})
|
||||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
||||
{
|
||||
@@ -106,6 +89,8 @@ void Bitboards::init() {
|
||||
LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
|
||||
BetweenBB[s1][s2] =
|
||||
(attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
|
||||
RayPassBB[s1][s2] =
|
||||
attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2);
|
||||
}
|
||||
BetweenBB[s1][s2] |= s2;
|
||||
}
|
||||
@@ -113,30 +98,6 @@ void Bitboards::init() {
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
|
||||
|
||||
Bitboard attacks = 0;
|
||||
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
|
||||
Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
|
||||
|
||||
for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
|
||||
{
|
||||
Square s = sq;
|
||||
while (safe_destination(s, d))
|
||||
{
|
||||
attacks |= (s += d);
|
||||
if (occupied & s)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attacks;
|
||||
}
|
||||
|
||||
|
||||
// Computes all rook and bishop attacks at startup. Magic
|
||||
// bitboards are used to look up attacks of sliding pieces. As a reference see
|
||||
// https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use
|
||||
@@ -165,7 +126,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) {
|
||||
// the number of 1s of the mask. Hence we deduce the size of the shift to
|
||||
// apply to the 64 or 32 bits word to get the index.
|
||||
Magic& m = magics[s][pt - BISHOP];
|
||||
m.mask = sliding_attack(pt, s, 0) & ~edges;
|
||||
m.mask = Bitboards::sliding_attack(pt, s, 0) & ~edges;
|
||||
#ifndef USE_PEXT
|
||||
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
|
||||
#endif
|
||||
@@ -182,7 +143,7 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) {
|
||||
#ifndef USE_PEXT
|
||||
occupancy[size] = b;
|
||||
#endif
|
||||
reference[size] = sliding_attack(pt, s, b);
|
||||
reference[size] = Bitboards::sliding_attack(pt, s, b);
|
||||
|
||||
if (HasPext)
|
||||
m.attacks[pext(b, m.mask)] = reference[size];
|
||||
|
||||
+165
-71
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -26,6 +26,8 @@
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <initializer_list>
|
||||
#include <array>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
@@ -61,9 +63,7 @@ extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||
extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
||||
extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
|
||||
|
||||
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
// Magic holds all magic bitboards relevant data for a single square
|
||||
struct Magic {
|
||||
@@ -103,17 +103,17 @@ constexpr Bitboard square_bb(Square s) {
|
||||
// Overloads of bitwise operators between a Bitboard and a Square for testing
|
||||
// whether a given bit is set in a bitboard, and for setting and clearing bits.
|
||||
|
||||
inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); }
|
||||
inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); }
|
||||
inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); }
|
||||
inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); }
|
||||
inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); }
|
||||
constexpr Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); }
|
||||
constexpr Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); }
|
||||
constexpr Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); }
|
||||
constexpr Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); }
|
||||
constexpr Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); }
|
||||
|
||||
inline Bitboard operator&(Square s, Bitboard b) { return b & s; }
|
||||
inline Bitboard operator|(Square s, Bitboard b) { return b | s; }
|
||||
inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; }
|
||||
constexpr Bitboard operator&(Square s, Bitboard b) { return b & s; }
|
||||
constexpr Bitboard operator|(Square s, Bitboard b) { return b | s; }
|
||||
constexpr Bitboard operator^(Square s, Bitboard b) { return b ^ s; }
|
||||
|
||||
inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; }
|
||||
constexpr Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; }
|
||||
|
||||
constexpr bool more_than_one(Bitboard b) { return b & (b - 1); }
|
||||
|
||||
@@ -155,11 +155,6 @@ constexpr Bitboard pawn_attacks_bb(Bitboard b) {
|
||||
: shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
|
||||
}
|
||||
|
||||
inline Bitboard pawn_attacks_bb(Color c, Square s) {
|
||||
|
||||
assert(is_ok(s));
|
||||
return PawnAttacks[c][s];
|
||||
}
|
||||
|
||||
// Returns a bitboard representing an entire line (from board edge
|
||||
// to board edge) that intersects the two given squares. If the given squares
|
||||
@@ -185,11 +180,6 @@ inline Bitboard between_bb(Square s1, Square s2) {
|
||||
return BetweenBB[s1][s2];
|
||||
}
|
||||
|
||||
// Returns true if the squares s1, s2 and s3 are aligned either on a
|
||||
// straight or on a diagonal line.
|
||||
inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; }
|
||||
|
||||
|
||||
// distance() functions return the distance between x and y, defined as the
|
||||
// number of steps for a king in x to reach y.
|
||||
|
||||
@@ -213,57 +203,14 @@ inline int distance<Square>(Square x, Square y) {
|
||||
|
||||
inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
|
||||
|
||||
// Returns the pseudo attacks of the given piece type
|
||||
// assuming an empty board.
|
||||
template<PieceType Pt>
|
||||
inline Bitboard attacks_bb(Square s) {
|
||||
|
||||
assert((Pt != PAWN) && (is_ok(s)));
|
||||
return PseudoAttacks[Pt][s];
|
||||
constexpr int constexpr_popcount(Bitboard b) {
|
||||
b = b - ((b >> 1) & 0x5555555555555555ULL);
|
||||
b = (b & 0x3333333333333333ULL) + ((b >> 2) & 0x3333333333333333ULL);
|
||||
b = (b + (b >> 4)) & 0x0F0F0F0F0F0F0F0FULL;
|
||||
return static_cast<int>((b * 0x0101010101010101ULL) >> 56);
|
||||
}
|
||||
|
||||
|
||||
// Returns the attacks by the given piece
|
||||
// assuming the board is occupied according to the passed Bitboard.
|
||||
// Sliding piece attacks do not continue passed an occupied square.
|
||||
template<PieceType Pt>
|
||||
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
|
||||
|
||||
assert((Pt != PAWN) && (is_ok(s)));
|
||||
|
||||
switch (Pt)
|
||||
{
|
||||
case BISHOP :
|
||||
case ROOK :
|
||||
return Magics[s][Pt - BISHOP].attacks_bb(occupied);
|
||||
case QUEEN :
|
||||
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
||||
default :
|
||||
return PseudoAttacks[Pt][s];
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the attacks by the given piece
|
||||
// assuming the board is occupied according to the passed Bitboard.
|
||||
// Sliding piece attacks do not continue passed an occupied square.
|
||||
inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
|
||||
|
||||
assert((pt != PAWN) && (is_ok(s)));
|
||||
|
||||
switch (pt)
|
||||
{
|
||||
case BISHOP :
|
||||
return attacks_bb<BISHOP>(s, occupied);
|
||||
case ROOK :
|
||||
return attacks_bb<ROOK>(s, occupied);
|
||||
case QUEEN :
|
||||
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
||||
default :
|
||||
return PseudoAttacks[pt][s];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Counts the number of non-zero bits in a bitboard.
|
||||
inline int popcount(Bitboard b) {
|
||||
|
||||
@@ -369,6 +316,153 @@ inline Square pop_lsb(Bitboard& b) {
|
||||
return s;
|
||||
}
|
||||
|
||||
namespace Bitboards {
|
||||
// Returns the bitboard of target square for the given step
|
||||
// from the given square. If the step is off the board, returns empty bitboard.
|
||||
constexpr Bitboard safe_destination(Square s, int step) {
|
||||
constexpr auto abs = [](int v) { return v < 0 ? -v : v; };
|
||||
Square to = Square(s + step);
|
||||
return is_ok(to) && abs(file_of(s) - file_of(to)) <= 2 ? square_bb(to) : Bitboard(0);
|
||||
}
|
||||
|
||||
constexpr Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
|
||||
Bitboard attacks = 0;
|
||||
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
|
||||
Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
|
||||
|
||||
for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
|
||||
{
|
||||
Square s = sq;
|
||||
while (safe_destination(s, d))
|
||||
{
|
||||
attacks |= (s += d);
|
||||
if (occupied & s)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attacks;
|
||||
}
|
||||
|
||||
constexpr Bitboard knight_attack(Square sq) {
|
||||
Bitboard b = {};
|
||||
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17})
|
||||
b |= safe_destination(sq, step);
|
||||
return b;
|
||||
}
|
||||
|
||||
constexpr Bitboard king_attack(Square sq) {
|
||||
Bitboard b = {};
|
||||
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9})
|
||||
b |= safe_destination(sq, step);
|
||||
return b;
|
||||
}
|
||||
|
||||
constexpr Bitboard pseudo_attacks(PieceType pt, Square sq) {
|
||||
switch (pt)
|
||||
{
|
||||
case PieceType::ROOK :
|
||||
case PieceType::BISHOP :
|
||||
return sliding_attack(pt, sq, 0);
|
||||
case PieceType::QUEEN :
|
||||
return sliding_attack(PieceType::ROOK, sq, 0) | sliding_attack(PieceType::BISHOP, sq, 0);
|
||||
case PieceType::KNIGHT :
|
||||
return knight_attack(sq);
|
||||
case PieceType::KING :
|
||||
return king_attack(sq);
|
||||
default :
|
||||
assert(false);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline constexpr auto PseudoAttacks = []() constexpr {
|
||||
std::array<std::array<Bitboard, SQUARE_NB>, PIECE_TYPE_NB> attacks{};
|
||||
|
||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||
{
|
||||
attacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
|
||||
attacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
|
||||
|
||||
attacks[KING][s1] = Bitboards::pseudo_attacks(KING, s1);
|
||||
attacks[KNIGHT][s1] = Bitboards::pseudo_attacks(KNIGHT, s1);
|
||||
attacks[QUEEN][s1] = attacks[BISHOP][s1] = Bitboards::pseudo_attacks(BISHOP, s1);
|
||||
attacks[QUEEN][s1] |= attacks[ROOK][s1] = Bitboards::pseudo_attacks(ROOK, s1);
|
||||
}
|
||||
|
||||
return attacks;
|
||||
}();
|
||||
|
||||
|
||||
// Returns the pseudo attacks of the given piece type
|
||||
// assuming an empty board.
|
||||
template<PieceType Pt>
|
||||
inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) {
|
||||
|
||||
assert((Pt != PAWN || c < COLOR_NB) && (is_ok(s)));
|
||||
return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s];
|
||||
}
|
||||
|
||||
|
||||
// Returns the attacks by the given piece
|
||||
// assuming the board is occupied according to the passed Bitboard.
|
||||
// Sliding piece attacks do not continue passed an occupied square.
|
||||
template<PieceType Pt>
|
||||
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
|
||||
|
||||
assert((Pt != PAWN) && (is_ok(s)));
|
||||
|
||||
switch (Pt)
|
||||
{
|
||||
case BISHOP :
|
||||
case ROOK :
|
||||
return Magics[s][Pt - BISHOP].attacks_bb(occupied);
|
||||
case QUEEN :
|
||||
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
||||
default :
|
||||
return PseudoAttacks[Pt][s];
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the attacks by the given piece
|
||||
// assuming the board is occupied according to the passed Bitboard.
|
||||
// Sliding piece attacks do not continue passed an occupied square.
|
||||
inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
|
||||
|
||||
assert((pt != PAWN) && (is_ok(s)));
|
||||
|
||||
switch (pt)
|
||||
{
|
||||
case BISHOP :
|
||||
return attacks_bb<BISHOP>(s, occupied);
|
||||
case ROOK :
|
||||
return attacks_bb<ROOK>(s, occupied);
|
||||
case QUEEN :
|
||||
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
||||
default :
|
||||
return PseudoAttacks[pt][s];
|
||||
}
|
||||
}
|
||||
|
||||
inline Bitboard attacks_bb(Piece pc, Square s) {
|
||||
if (type_of(pc) == PAWN)
|
||||
return PseudoAttacks[color_of(pc)][s];
|
||||
|
||||
return PseudoAttacks[type_of(pc)][s];
|
||||
}
|
||||
|
||||
|
||||
inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) {
|
||||
if (type_of(pc) == PAWN)
|
||||
return PseudoAttacks[color_of(pc)][s];
|
||||
|
||||
return attacks_bb(type_of(pc), s, occupied);
|
||||
}
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif // #ifndef BITBOARD_H_INCLUDED
|
||||
|
||||
+49
-11
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -33,10 +33,12 @@
|
||||
#include "misc.h"
|
||||
#include "nnue/network.h"
|
||||
#include "nnue/nnue_common.h"
|
||||
#include "nnue/nnue_misc.h"
|
||||
#include "numa.h"
|
||||
#include "perft.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
#include "shm.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
#include "types.h"
|
||||
#include "uci.h"
|
||||
@@ -50,18 +52,23 @@ constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq
|
||||
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||
int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency()));
|
||||
|
||||
// The default configuration will attempt to group L3 domains up to 32 threads.
|
||||
// This size was found to be a good balance between the Elo gain of increased
|
||||
// history sharing and the speed loss from more cross-cache accesses (see
|
||||
// PR#6526). The user can always explicitly override this behavior.
|
||||
constexpr NumaAutoPolicy DefaultNumaPolicy = BundledL3Policy{32};
|
||||
|
||||
Engine::Engine(std::optional<std::string> path) :
|
||||
binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""),
|
||||
numaContext(NumaConfig::from_system()),
|
||||
numaContext(NumaConfig::from_system(DefaultNumaPolicy)),
|
||||
states(new std::deque<StateInfo>(1)),
|
||||
threads(),
|
||||
networks(
|
||||
numaContext,
|
||||
NN::Networks(
|
||||
NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG),
|
||||
NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) {
|
||||
pos.set(StartFEN, false, &states->back());
|
||||
networks(numaContext,
|
||||
// Heap-allocate because sizeof(NN::Networks) is large
|
||||
std::make_unique<NN::Networks>(NN::EvalFile{EvalFileDefaultNameBig, "None", ""},
|
||||
NN::EvalFile{EvalFileDefaultNameSmall, "None", ""})) {
|
||||
|
||||
pos.set(StartFEN, false, &states->back());
|
||||
|
||||
options.add( //
|
||||
"Debug Log File", Option("", [](const Option& o) {
|
||||
@@ -212,12 +219,12 @@ void Engine::set_position(const std::string& fen, const std::vector<std::string>
|
||||
void Engine::set_numa_config_from_option(const std::string& o) {
|
||||
if (o == "auto" || o == "system")
|
||||
{
|
||||
numaContext.set_numa_config(NumaConfig::from_system());
|
||||
numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy));
|
||||
}
|
||||
else if (o == "hardware")
|
||||
{
|
||||
// Don't respect affinity set in the system.
|
||||
numaContext.set_numa_config(NumaConfig::from_system(false));
|
||||
numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy, false));
|
||||
}
|
||||
else if (o == "none")
|
||||
{
|
||||
@@ -235,7 +242,8 @@ void Engine::set_numa_config_from_option(const std::string& o) {
|
||||
|
||||
void Engine::resize_threads() {
|
||||
threads.wait_for_search_finished();
|
||||
threads.set(numaContext.get_numa_config(), {options, threads, tt, networks}, updateContext);
|
||||
threads.set(numaContext.get_numa_config(), {options, threads, tt, sharedHists, networks},
|
||||
updateContext);
|
||||
|
||||
// Reallocate the hash with the new threadpool size
|
||||
set_tt_size(options["Hash"]);
|
||||
@@ -254,6 +262,36 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; }
|
||||
void Engine::verify_networks() const {
|
||||
networks->big.verify(options["EvalFile"], onVerifyNetworks);
|
||||
networks->small.verify(options["EvalFileSmall"], onVerifyNetworks);
|
||||
|
||||
auto statuses = networks.get_status_and_errors();
|
||||
for (size_t i = 0; i < statuses.size(); ++i)
|
||||
{
|
||||
const auto [status, error] = statuses[i];
|
||||
std::string message = "Network replica " + std::to_string(i + 1) + ": ";
|
||||
if (status == SystemWideSharedConstantAllocationStatus::NoAllocation)
|
||||
{
|
||||
message += "No allocation.";
|
||||
}
|
||||
else if (status == SystemWideSharedConstantAllocationStatus::LocalMemory)
|
||||
{
|
||||
message += "Local memory.";
|
||||
}
|
||||
else if (status == SystemWideSharedConstantAllocationStatus::SharedMemory)
|
||||
{
|
||||
message += "Shared memory.";
|
||||
}
|
||||
else
|
||||
{
|
||||
message += "Unknown status.";
|
||||
}
|
||||
|
||||
if (error.has_value())
|
||||
{
|
||||
message += " " + *error;
|
||||
}
|
||||
|
||||
onVerifyNetworks(message);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::load_networks() {
|
||||
|
||||
+8
-5
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -22,12 +22,14 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "history.h"
|
||||
#include "nnue/network.h"
|
||||
#include "numa.h"
|
||||
#include "position.h"
|
||||
@@ -115,13 +117,14 @@ class Engine {
|
||||
Position pos;
|
||||
StateListPtr states;
|
||||
|
||||
OptionsMap options;
|
||||
ThreadPool threads;
|
||||
TranspositionTable tt;
|
||||
LazyNumaReplicated<Eval::NNUE::Networks> networks;
|
||||
OptionsMap options;
|
||||
ThreadPool threads;
|
||||
TranspositionTable tt;
|
||||
LazyNumaReplicatedSystemWide<Eval::NNUE::Networks> networks;
|
||||
|
||||
Search::SearchManager::UpdateContext updateContext;
|
||||
std::function<void(std::string_view)> onVerifyNetworks;
|
||||
std::map<NumaIndex, SharedHistories> sharedHists;
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
+20
-24
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -38,17 +38,15 @@
|
||||
namespace Stockfish {
|
||||
|
||||
// Returns a static, purely materialistic evaluation of the position from
|
||||
// the point of view of the given color. It can be divided by PawnValue to get
|
||||
// the point of view of the side to move. It can be divided by PawnValue to get
|
||||
// an approximation of the material advantage on the board in terms of pawns.
|
||||
int Eval::simple_eval(const Position& pos, Color c) {
|
||||
return PawnValue * (pos.count<PAWN>(c) - pos.count<PAWN>(~c))
|
||||
+ (pos.non_pawn_material(c) - pos.non_pawn_material(~c));
|
||||
int Eval::simple_eval(const Position& pos) {
|
||||
Color c = pos.side_to_move();
|
||||
return PawnValue * (pos.count<PAWN>(c) - pos.count<PAWN>(~c)) + pos.non_pawn_material(c)
|
||||
- pos.non_pawn_material(~c);
|
||||
}
|
||||
|
||||
bool Eval::use_smallnet(const Position& pos) {
|
||||
int simpleEval = simple_eval(pos, pos.side_to_move());
|
||||
return std::abs(simpleEval) > 962;
|
||||
}
|
||||
bool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; }
|
||||
|
||||
// Evaluate is the evaluator for the outer world. It returns a static evaluation
|
||||
// of the position from the point of view of the side to move.
|
||||
@@ -61,29 +59,29 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
|
||||
assert(!pos.checkers());
|
||||
|
||||
bool smallNet = use_smallnet(pos);
|
||||
auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, &caches.small)
|
||||
: networks.big.evaluate(pos, accumulators, &caches.big);
|
||||
auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small)
|
||||
: networks.big.evaluate(pos, accumulators, caches.big);
|
||||
|
||||
Value nnue = (125 * psqt + 131 * positional) / 128;
|
||||
|
||||
// Re-evaluate the position when higher eval accuracy is worth the time spent
|
||||
if (smallNet && (std::abs(nnue) < 236))
|
||||
if (smallNet && (std::abs(nnue) < 277))
|
||||
{
|
||||
std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big);
|
||||
std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big);
|
||||
nnue = (125 * psqt + 131 * positional) / 128;
|
||||
smallNet = false;
|
||||
}
|
||||
|
||||
// Blend optimism and eval with nnue complexity
|
||||
int nnueComplexity = std::abs(psqt - positional);
|
||||
optimism += optimism * nnueComplexity / 468;
|
||||
nnue -= nnue * nnueComplexity / 18000;
|
||||
optimism += optimism * nnueComplexity / 476;
|
||||
nnue -= nnue * nnueComplexity / 18236;
|
||||
|
||||
int material = 535 * pos.count<PAWN>() + pos.non_pawn_material();
|
||||
int v = (nnue * (77777 + material) + optimism * (7777 + material)) / 77777;
|
||||
int material = 534 * pos.count<PAWN>() + pos.non_pawn_material();
|
||||
int v = (nnue * (77871 + material) + optimism * (7191 + material)) / 77871;
|
||||
|
||||
// Damp down the evaluation linearly when shuffling
|
||||
v -= v * pos.rule50_count() / 212;
|
||||
v -= v * pos.rule50_count() / 199;
|
||||
|
||||
// Guarantee evaluation does not hit the tablebase range
|
||||
v = std::clamp(v, VALUE_TB_LOSS_IN_MAX_PLY + 1, VALUE_TB_WIN_IN_MAX_PLY - 1);
|
||||
@@ -100,10 +98,8 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) {
|
||||
if (pos.checkers())
|
||||
return "Final evaluation: none (in check)";
|
||||
|
||||
Eval::NNUE::AccumulatorStack accumulators;
|
||||
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(networks);
|
||||
|
||||
accumulators.reset(pos, networks, *caches);
|
||||
auto accumulators = std::make_unique<Eval::NNUE::AccumulatorStack>();
|
||||
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(networks);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
|
||||
@@ -111,12 +107,12 @@ std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) {
|
||||
|
||||
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
|
||||
|
||||
auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches->big);
|
||||
auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches->big);
|
||||
Value v = psqt + positional;
|
||||
v = pos.side_to_move() == WHITE ? v : -v;
|
||||
ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n";
|
||||
|
||||
v = evaluate(networks, pos, accumulators, *caches, VALUE_ZERO);
|
||||
v = evaluate(networks, pos, *accumulators, *caches, VALUE_ZERO);
|
||||
v = pos.side_to_move() == WHITE ? v : -v;
|
||||
ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)";
|
||||
ss << " [with scaled NNUE, ...]";
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -33,7 +33,7 @@ namespace Eval {
|
||||
// for the build process (profile-build and fishtest) to work. Do not change the
|
||||
// name of the macro or the location where this macro is defined, as it is used
|
||||
// in the Makefile/Fishtest.
|
||||
#define EvalFileDefaultNameBig "nn-1c0000000000.nnue"
|
||||
#define EvalFileDefaultNameBig "nn-c288c895ea92.nnue"
|
||||
#define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue"
|
||||
|
||||
namespace NNUE {
|
||||
@@ -44,7 +44,7 @@ class AccumulatorStack;
|
||||
|
||||
std::string trace(Position& pos, const Eval::NNUE::Networks& networks);
|
||||
|
||||
int simple_eval(const Position& pos, Color c);
|
||||
int simple_eval(const Position& pos);
|
||||
bool use_smallnet(const Position& pos);
|
||||
Value evaluate(const NNUE::Networks& networks,
|
||||
const Position& pos,
|
||||
|
||||
+150
-48
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
@@ -28,67 +29,58 @@
|
||||
#include <limits>
|
||||
#include <type_traits> // IWYU pragma: keep
|
||||
|
||||
#include "memory.h"
|
||||
#include "misc.h"
|
||||
#include "position.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2
|
||||
constexpr int CORRECTION_HISTORY_SIZE = 32768; // has to be a power of 2
|
||||
constexpr int PAWN_HISTORY_BASE_SIZE = 8192; // has to be a power of 2
|
||||
constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits<uint16_t>::max() + 1;
|
||||
constexpr int CORRHIST_BASE_SIZE = UINT_16_HISTORY_SIZE;
|
||||
constexpr int CORRECTION_HISTORY_LIMIT = 1024;
|
||||
constexpr int LOW_PLY_HISTORY_SIZE = 4;
|
||||
constexpr int LOW_PLY_HISTORY_SIZE = 5;
|
||||
|
||||
static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0,
|
||||
"PAWN_HISTORY_SIZE has to be a power of 2");
|
||||
static_assert((PAWN_HISTORY_BASE_SIZE & (PAWN_HISTORY_BASE_SIZE - 1)) == 0,
|
||||
"PAWN_HISTORY_BASE_SIZE has to be a power of 2");
|
||||
|
||||
static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0,
|
||||
"CORRECTION_HISTORY_SIZE has to be a power of 2");
|
||||
|
||||
enum PawnHistoryType {
|
||||
Normal,
|
||||
Correction
|
||||
};
|
||||
|
||||
template<PawnHistoryType T = Normal>
|
||||
inline int pawn_structure_index(const Position& pos) {
|
||||
return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1);
|
||||
}
|
||||
|
||||
inline int minor_piece_index(const Position& pos) {
|
||||
return pos.minor_piece_key() & (CORRECTION_HISTORY_SIZE - 1);
|
||||
}
|
||||
|
||||
template<Color c>
|
||||
inline int non_pawn_index(const Position& pos) {
|
||||
return pos.non_pawn_key(c) & (CORRECTION_HISTORY_SIZE - 1);
|
||||
}
|
||||
static_assert((CORRHIST_BASE_SIZE & (CORRHIST_BASE_SIZE - 1)) == 0,
|
||||
"CORRHIST_BASE_SIZE has to be a power of 2");
|
||||
|
||||
// StatsEntry is the container of various numerical statistics. We use a class
|
||||
// instead of a naked value to directly call history update operator<<() on
|
||||
// the entry. The first template parameter T is the base type of the array,
|
||||
// and the second template parameter D limits the range of updates in [-D, D]
|
||||
// when we update values with the << operator
|
||||
template<typename T, int D>
|
||||
class StatsEntry {
|
||||
|
||||
template<typename T, int D, bool Atomic = false>
|
||||
struct StatsEntry {
|
||||
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
|
||||
static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
|
||||
|
||||
T entry;
|
||||
private:
|
||||
std::conditional_t<Atomic, std::atomic<T>, T> entry;
|
||||
|
||||
public:
|
||||
StatsEntry& operator=(const T& v) {
|
||||
entry = v;
|
||||
return *this;
|
||||
void operator=(const T& v) {
|
||||
if constexpr (Atomic)
|
||||
entry.store(v, std::memory_order_relaxed);
|
||||
else
|
||||
entry = v;
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
if constexpr (Atomic)
|
||||
return entry.load(std::memory_order_relaxed);
|
||||
else
|
||||
return entry;
|
||||
}
|
||||
operator const T&() const { return entry; }
|
||||
|
||||
void operator<<(int bonus) {
|
||||
// Make sure that bonus is in range [-D, D]
|
||||
int clampedBonus = std::clamp(bonus, -D, D);
|
||||
entry += clampedBonus - entry * std::abs(clampedBonus) / D;
|
||||
T val = *this;
|
||||
*this = val + clampedBonus - val * std::abs(clampedBonus) / D;
|
||||
|
||||
assert(std::abs(entry) <= D);
|
||||
assert(std::abs(T(*this)) <= D);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -100,16 +92,51 @@ enum StatsType {
|
||||
template<typename T, int D, std::size_t... Sizes>
|
||||
using Stats = MultiArray<StatsEntry<T, D>, Sizes...>;
|
||||
|
||||
template<typename T, int D, std::size_t... Sizes>
|
||||
using AtomicStats = MultiArray<StatsEntry<T, D, true>, Sizes...>;
|
||||
|
||||
// DynStats is a dynamically sized array of Stats, used for thread-shared histories
|
||||
// which should scale with the total number of threads. The SizeMultiplier gives
|
||||
// the per-thread allocation count of T.
|
||||
template<typename T, int SizeMultiplier>
|
||||
struct DynStats {
|
||||
explicit DynStats(size_t s) {
|
||||
size = s * SizeMultiplier;
|
||||
data = make_unique_large_page<T[]>(size);
|
||||
}
|
||||
// Sets all values in the range to 0
|
||||
void clear_range(int value, size_t threadIdx, size_t numaTotal) {
|
||||
size_t start = uint64_t(threadIdx) * size / numaTotal;
|
||||
assert(start < size);
|
||||
size_t end = threadIdx + 1 == numaTotal ? size : uint64_t(threadIdx + 1) * size / numaTotal;
|
||||
|
||||
while (start < end)
|
||||
data[start++].fill(value);
|
||||
}
|
||||
size_t get_size() const { return size; }
|
||||
T& operator[](size_t index) {
|
||||
assert(index < size);
|
||||
return data.get()[index];
|
||||
}
|
||||
const T& operator[](size_t index) const {
|
||||
assert(index < size);
|
||||
return data.get()[index];
|
||||
}
|
||||
|
||||
private:
|
||||
size_t size;
|
||||
LargePagePtr<T[]> data;
|
||||
};
|
||||
|
||||
// ButterflyHistory records how often quiet moves have been successful or unsuccessful
|
||||
// during the current search, and is used for reduction and move ordering decisions.
|
||||
// It uses 2 tables (one for each color) indexed by the move's from and to squares,
|
||||
// see https://www.chessprogramming.org/Butterfly_Boards (~11 elo)
|
||||
using ButterflyHistory = Stats<std::int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)>;
|
||||
// see https://www.chessprogramming.org/Butterfly_Boards
|
||||
using ButterflyHistory = Stats<std::int16_t, 7183, COLOR_NB, UINT_16_HISTORY_SIZE>;
|
||||
|
||||
// LowPlyHistory is adressed by play and move's from and to squares, used
|
||||
// LowPlyHistory is addressed by ply and move's from and to squares, used
|
||||
// to improve move ordering near the root
|
||||
using LowPlyHistory =
|
||||
Stats<std::int16_t, 7183, LOW_PLY_HISTORY_SIZE, int(SQUARE_NB) * int(SQUARE_NB)>;
|
||||
using LowPlyHistory = Stats<std::int16_t, 7183, LOW_PLY_HISTORY_SIZE, UINT_16_HISTORY_SIZE>;
|
||||
|
||||
// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
|
||||
using CapturePieceToHistory = Stats<std::int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;
|
||||
@@ -120,11 +147,11 @@ using PieceToHistory = Stats<std::int16_t, 30000, PIECE_NB, SQUARE_NB>;
|
||||
// ContinuationHistory is the combined history of a given pair of moves, usually
|
||||
// the current one given a previous one. The nested history table is based on
|
||||
// PieceToHistory instead of ButterflyBoards.
|
||||
// (~63 elo)
|
||||
using ContinuationHistory = MultiArray<PieceToHistory, PIECE_NB, SQUARE_NB>;
|
||||
|
||||
// PawnHistory is addressed by the pawn structure and a move's [piece][to]
|
||||
using PawnHistory = Stats<std::int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUARE_NB>;
|
||||
using PawnHistory =
|
||||
DynStats<AtomicStats<std::int16_t, 8192, PIECE_NB, SQUARE_NB>, PAWN_HISTORY_BASE_SIZE>;
|
||||
|
||||
// Correction histories record differences between the static evaluation of
|
||||
// positions and their search score. It is used to improve the static evaluation
|
||||
@@ -138,11 +165,27 @@ enum CorrHistType {
|
||||
Continuation, // Combined history of move pairs
|
||||
};
|
||||
|
||||
template<typename T, int D>
|
||||
struct CorrectionBundle {
|
||||
StatsEntry<T, D, true> pawn;
|
||||
StatsEntry<T, D, true> minor;
|
||||
StatsEntry<T, D, true> nonPawnWhite;
|
||||
StatsEntry<T, D, true> nonPawnBlack;
|
||||
|
||||
void operator=(T val) {
|
||||
pawn = val;
|
||||
minor = val;
|
||||
nonPawnWhite = val;
|
||||
nonPawnBlack = val;
|
||||
}
|
||||
};
|
||||
|
||||
namespace Detail {
|
||||
|
||||
template<CorrHistType>
|
||||
struct CorrHistTypedef {
|
||||
using type = Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, CORRECTION_HISTORY_SIZE, COLOR_NB>;
|
||||
using type =
|
||||
DynStats<Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB>, CORRHIST_BASE_SIZE>;
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -157,15 +200,74 @@ struct CorrHistTypedef<Continuation> {
|
||||
|
||||
template<>
|
||||
struct CorrHistTypedef<NonPawn> {
|
||||
using type =
|
||||
Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, CORRECTION_HISTORY_SIZE, COLOR_NB, COLOR_NB>;
|
||||
using type = DynStats<Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, COLOR_NB>,
|
||||
CORRHIST_BASE_SIZE>;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using UnifiedCorrectionHistory =
|
||||
DynStats<MultiArray<CorrectionBundle<std::int16_t, CORRECTION_HISTORY_LIMIT>, COLOR_NB>,
|
||||
CORRHIST_BASE_SIZE>;
|
||||
|
||||
template<CorrHistType T>
|
||||
using CorrectionHistory = typename Detail::CorrHistTypedef<T>::type;
|
||||
|
||||
using TTMoveHistory = StatsEntry<std::int16_t, 8192>;
|
||||
|
||||
// Set of histories shared between groups of threads. To avoid excessive
|
||||
// cross-node data transfer, histories are shared only between threads
|
||||
// on a given NUMA node. The passed size must be a power of two to make
|
||||
// the indexing more efficient.
|
||||
struct SharedHistories {
|
||||
SharedHistories(size_t threadCount) :
|
||||
correctionHistory(threadCount),
|
||||
pawnHistory(threadCount) {
|
||||
assert((threadCount & (threadCount - 1)) == 0 && threadCount != 0);
|
||||
sizeMinus1 = correctionHistory.get_size() - 1;
|
||||
pawnHistSizeMinus1 = pawnHistory.get_size() - 1;
|
||||
}
|
||||
|
||||
size_t get_size() const { return sizeMinus1 + 1; }
|
||||
|
||||
auto& pawn_entry(const Position& pos) {
|
||||
return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1];
|
||||
}
|
||||
const auto& pawn_entry(const Position& pos) const {
|
||||
return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1];
|
||||
}
|
||||
|
||||
auto& pawn_correction_entry(const Position& pos) {
|
||||
return correctionHistory[pos.pawn_key() & sizeMinus1];
|
||||
}
|
||||
const auto& pawn_correction_entry(const Position& pos) const {
|
||||
return correctionHistory[pos.pawn_key() & sizeMinus1];
|
||||
}
|
||||
|
||||
auto& minor_piece_correction_entry(const Position& pos) {
|
||||
return correctionHistory[pos.minor_piece_key() & sizeMinus1];
|
||||
}
|
||||
const auto& minor_piece_correction_entry(const Position& pos) const {
|
||||
return correctionHistory[pos.minor_piece_key() & sizeMinus1];
|
||||
}
|
||||
|
||||
template<Color c>
|
||||
auto& nonpawn_correction_entry(const Position& pos) {
|
||||
return correctionHistory[pos.non_pawn_key(c) & sizeMinus1];
|
||||
}
|
||||
template<Color c>
|
||||
const auto& nonpawn_correction_entry(const Position& pos) const {
|
||||
return correctionHistory[pos.non_pawn_key(c) & sizeMinus1];
|
||||
}
|
||||
|
||||
UnifiedCorrectionHistory correctionHistory;
|
||||
PawnHistory pawnHistory;
|
||||
|
||||
|
||||
private:
|
||||
size_t sizeMinus1, pawnHistSizeMinus1;
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif // #ifndef HISTORY_H_INCLUDED
|
||||
|
||||
+6
-7
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -17,28 +17,27 @@
|
||||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "bitboard.h"
|
||||
#include "misc.h"
|
||||
#include "position.h"
|
||||
#include "types.h"
|
||||
#include "uci.h"
|
||||
#include "tune.h"
|
||||
#include "uci.h"
|
||||
|
||||
using namespace Stockfish;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
||||
std::cout << engine_info() << std::endl;
|
||||
|
||||
Bitboards::init();
|
||||
Position::init();
|
||||
|
||||
UCIEngine uci(argc, argv);
|
||||
auto uci = std::make_unique<UCIEngine>(argc, argv);
|
||||
|
||||
Tune::init(uci.engine_options());
|
||||
Tune::init(uci->engine_options());
|
||||
|
||||
uci.loop();
|
||||
uci->loop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+9
-78
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -55,12 +55,6 @@
|
||||
// the calls at compile time), try to load them at runtime. To do this we need
|
||||
// first to define the corresponding function pointers.
|
||||
|
||||
extern "C" {
|
||||
using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE);
|
||||
using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID);
|
||||
using AdjustTokenPrivileges_t =
|
||||
bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -106,77 +100,14 @@ void std_aligned_free(void* ptr) {
|
||||
|
||||
static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) {
|
||||
|
||||
#if !defined(_WIN64)
|
||||
return nullptr;
|
||||
#else
|
||||
|
||||
HANDLE hProcessToken{};
|
||||
LUID luid{};
|
||||
void* mem = nullptr;
|
||||
|
||||
const size_t largePageSize = GetLargePageMinimum();
|
||||
if (!largePageSize)
|
||||
return nullptr;
|
||||
|
||||
// Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges
|
||||
|
||||
HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll"));
|
||||
|
||||
if (!hAdvapi32)
|
||||
hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
|
||||
|
||||
auto OpenProcessToken_f =
|
||||
OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken"));
|
||||
if (!OpenProcessToken_f)
|
||||
return nullptr;
|
||||
auto LookupPrivilegeValueA_f =
|
||||
LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"));
|
||||
if (!LookupPrivilegeValueA_f)
|
||||
return nullptr;
|
||||
auto AdjustTokenPrivileges_f =
|
||||
AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"));
|
||||
if (!AdjustTokenPrivileges_f)
|
||||
return nullptr;
|
||||
|
||||
// We need SeLockMemoryPrivilege, so try to enable it for the process
|
||||
|
||||
if (!OpenProcessToken_f( // OpenProcessToken()
|
||||
GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
|
||||
return nullptr;
|
||||
|
||||
if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid))
|
||||
{
|
||||
TOKEN_PRIVILEGES tp{};
|
||||
TOKEN_PRIVILEGES prevTp{};
|
||||
DWORD prevTpLen = 0;
|
||||
|
||||
tp.PrivilegeCount = 1;
|
||||
tp.Privileges[0].Luid = luid;
|
||||
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges()
|
||||
// succeeds, we still need to query GetLastError() to ensure that the privileges
|
||||
// were actually obtained.
|
||||
|
||||
if (AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp,
|
||||
&prevTpLen)
|
||||
&& GetLastError() == ERROR_SUCCESS)
|
||||
{
|
||||
// Round up size to full pages and allocate
|
||||
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
|
||||
mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES,
|
||||
PAGE_READWRITE);
|
||||
|
||||
// Privilege no longer needed, restore previous state
|
||||
AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(hProcessToken);
|
||||
|
||||
return mem;
|
||||
|
||||
#endif
|
||||
return windows_try_with_large_page_priviliges(
|
||||
[&](size_t largePageSize) {
|
||||
// Round up size to full pages and allocate
|
||||
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
|
||||
return VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES,
|
||||
PAGE_READWRITE);
|
||||
},
|
||||
[]() { return (void*) nullptr; });
|
||||
}
|
||||
|
||||
void* aligned_large_pages_alloc(size_t allocSize) {
|
||||
|
||||
+106
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -29,6 +29,36 @@
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#if defined(_WIN64)
|
||||
|
||||
#if _WIN32_WINNT < 0x0601
|
||||
#undef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
|
||||
#endif
|
||||
|
||||
#if !defined(NOMINMAX)
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
// Some Windows headers (RPC/old headers) define short macros such
|
||||
// as 'small' expanding to 'char', which breaks identifiers in the code.
|
||||
// Undefine those macros immediately after including <windows.h>.
|
||||
#ifdef small
|
||||
#undef small
|
||||
#endif
|
||||
|
||||
#include <psapi.h>
|
||||
|
||||
extern "C" {
|
||||
using OpenProcessToken_t = bool (*)(HANDLE, DWORD, PHANDLE);
|
||||
using LookupPrivilegeValueA_t = bool (*)(LPCSTR, LPCSTR, PLUID);
|
||||
using AdjustTokenPrivileges_t =
|
||||
bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
void* std_aligned_alloc(size_t alignment, size_t size);
|
||||
@@ -52,7 +82,6 @@ void memory_deleter(T* ptr, FREE_FUNC free_func) {
|
||||
ptr->~T();
|
||||
|
||||
free_func(ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Frees memory which was placed there with placement new.
|
||||
@@ -212,6 +241,81 @@ T* align_ptr_up(T* ptr) {
|
||||
reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
template<typename FuncYesT, typename FuncNoT>
|
||||
auto windows_try_with_large_page_priviliges([[maybe_unused]] FuncYesT&& fyes, FuncNoT&& fno) {
|
||||
|
||||
#if !defined(_WIN64)
|
||||
return fno();
|
||||
#else
|
||||
|
||||
HANDLE hProcessToken{};
|
||||
LUID luid{};
|
||||
|
||||
const size_t largePageSize = GetLargePageMinimum();
|
||||
if (!largePageSize)
|
||||
return fno();
|
||||
|
||||
// Dynamically link OpenProcessToken, LookupPrivilegeValue and AdjustTokenPrivileges
|
||||
|
||||
HMODULE hAdvapi32 = GetModuleHandle(TEXT("advapi32.dll"));
|
||||
|
||||
if (!hAdvapi32)
|
||||
hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
|
||||
|
||||
auto OpenProcessToken_f =
|
||||
OpenProcessToken_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken"));
|
||||
if (!OpenProcessToken_f)
|
||||
return fno();
|
||||
auto LookupPrivilegeValueA_f =
|
||||
LookupPrivilegeValueA_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"));
|
||||
if (!LookupPrivilegeValueA_f)
|
||||
return fno();
|
||||
auto AdjustTokenPrivileges_f =
|
||||
AdjustTokenPrivileges_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"));
|
||||
if (!AdjustTokenPrivileges_f)
|
||||
return fno();
|
||||
|
||||
// We need SeLockMemoryPrivilege, so try to enable it for the process
|
||||
|
||||
if (!OpenProcessToken_f( // OpenProcessToken()
|
||||
GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
|
||||
return fno();
|
||||
|
||||
if (!LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid))
|
||||
return fno();
|
||||
|
||||
TOKEN_PRIVILEGES tp{};
|
||||
TOKEN_PRIVILEGES prevTp{};
|
||||
DWORD prevTpLen = 0;
|
||||
|
||||
tp.PrivilegeCount = 1;
|
||||
tp.Privileges[0].Luid = luid;
|
||||
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges()
|
||||
// succeeds, we still need to query GetLastError() to ensure that the privileges
|
||||
// were actually obtained.
|
||||
|
||||
if (!AdjustTokenPrivileges_f(hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp,
|
||||
&prevTpLen)
|
||||
|| GetLastError() != ERROR_SUCCESS)
|
||||
return fno();
|
||||
|
||||
auto&& ret = fyes(largePageSize);
|
||||
|
||||
// Privilege no longer needed, restore previous state
|
||||
AdjustTokenPrivileges_f(hProcessToken, FALSE, &prevTp, 0, nullptr, nullptr);
|
||||
|
||||
CloseHandle(hProcessToken);
|
||||
|
||||
return std::forward<decltype(ret)>(ret);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
|
||||
+7
-4
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -40,11 +40,11 @@ namespace Stockfish {
|
||||
namespace {
|
||||
|
||||
// Version number or dev.
|
||||
constexpr std::string_view version = "17.1";
|
||||
constexpr std::string_view version = "18";
|
||||
|
||||
// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
|
||||
// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
||||
// can toggle the logging of std::cout and std:cin at runtime whilst preserving
|
||||
// can toggle the logging of std::cout and std::cin at runtime whilst preserving
|
||||
// usual I/O functionality, all without changing a single line of code!
|
||||
// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
|
||||
|
||||
@@ -237,6 +237,9 @@ std::string compiler_info() {
|
||||
|
||||
compiler += "\nCompilation settings : ";
|
||||
compiler += (Is64Bit ? "64bit" : "32bit");
|
||||
#if defined(USE_AVX512ICL)
|
||||
compiler += " AVX512ICL";
|
||||
#endif
|
||||
#if defined(USE_VNNI)
|
||||
compiler += " VNNI";
|
||||
#endif
|
||||
@@ -256,12 +259,12 @@ std::string compiler_info() {
|
||||
#if defined(USE_SSE2)
|
||||
compiler += " SSE2";
|
||||
#endif
|
||||
compiler += (HasPopCnt ? " POPCNT" : "");
|
||||
#if defined(USE_NEON_DOTPROD)
|
||||
compiler += " NEON_DOTPROD";
|
||||
#elif defined(USE_NEON)
|
||||
compiler += " NEON";
|
||||
#endif
|
||||
compiler += (HasPopCnt ? " POPCNT" : "");
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
compiler += " DEBUG";
|
||||
|
||||
+156
-6
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -23,13 +23,18 @@
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <exception> // IWYU pragma: keep
|
||||
// IWYU pragma: no_include <__exception/terminate.h>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <optional>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#define stringify2(x) #x
|
||||
@@ -130,10 +135,21 @@ class ValueList {
|
||||
|
||||
public:
|
||||
std::size_t size() const { return size_; }
|
||||
void push_back(const T& value) { values_[size_++] = value; }
|
||||
const T* begin() const { return values_; }
|
||||
const T* end() const { return values_ + size_; }
|
||||
const T& operator[](int index) const { return values_[index]; }
|
||||
int ssize() const { return int(size_); }
|
||||
void push_back(const T& value) {
|
||||
assert(size_ < MaxSize);
|
||||
values_[size_++] = value;
|
||||
}
|
||||
const T* begin() const { return values_; }
|
||||
const T* end() const { return values_ + size_; }
|
||||
const T& operator[](int index) const { return values_[index]; }
|
||||
|
||||
T* make_space(size_t count) {
|
||||
T* result = &values_[size_];
|
||||
size_ += count;
|
||||
assert(size_ <= MaxSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
T values_[MaxSize];
|
||||
@@ -290,6 +306,108 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::uint64_t hash_bytes(const char* data, std::size_t size) {
|
||||
// FNV-1a 64-bit
|
||||
const char* p = data;
|
||||
std::uint64_t h = 14695981039346656037ull;
|
||||
for (std::size_t i = 0; i < size; ++i)
|
||||
h = (h ^ p[i]) * 1099511628211ull;
|
||||
return h;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline std::size_t get_raw_data_hash(const T& value) {
|
||||
// We must have no padding bytes because we're reinterpreting as char
|
||||
static_assert(std::has_unique_object_representations<T>());
|
||||
|
||||
return static_cast<std::size_t>(
|
||||
hash_bytes(reinterpret_cast<const char*>(&value), sizeof(value)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void hash_combine(std::size_t& seed, const T& v) {
|
||||
std::size_t x;
|
||||
// For primitive types we avoid using the default hasher, which may be
|
||||
// nondeterministic across program invocations
|
||||
if constexpr (std::is_integral<T>())
|
||||
x = v;
|
||||
else
|
||||
x = std::hash<T>{}(v);
|
||||
seed ^= x + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
}
|
||||
|
||||
inline std::uint64_t hash_string(const std::string& sv) { return hash_bytes(sv.data(), sv.size()); }
|
||||
|
||||
template<std::size_t Capacity>
|
||||
class FixedString {
|
||||
public:
|
||||
FixedString() :
|
||||
length_(0) {
|
||||
data_[0] = '\0';
|
||||
}
|
||||
|
||||
FixedString(const char* str) {
|
||||
size_t len = std::strlen(str);
|
||||
if (len > Capacity)
|
||||
std::terminate();
|
||||
std::memcpy(data_, str, len);
|
||||
length_ = len;
|
||||
data_[length_] = '\0';
|
||||
}
|
||||
|
||||
FixedString(const std::string& str) {
|
||||
if (str.size() > Capacity)
|
||||
std::terminate();
|
||||
std::memcpy(data_, str.data(), str.size());
|
||||
length_ = str.size();
|
||||
data_[length_] = '\0';
|
||||
}
|
||||
|
||||
std::size_t size() const { return length_; }
|
||||
std::size_t capacity() const { return Capacity; }
|
||||
|
||||
const char* c_str() const { return data_; }
|
||||
const char* data() const { return data_; }
|
||||
|
||||
char& operator[](std::size_t i) { return data_[i]; }
|
||||
|
||||
const char& operator[](std::size_t i) const { return data_[i]; }
|
||||
|
||||
FixedString& operator+=(const char* str) {
|
||||
size_t len = std::strlen(str);
|
||||
if (length_ + len > Capacity)
|
||||
std::terminate();
|
||||
std::memcpy(data_ + length_, str, len);
|
||||
length_ += len;
|
||||
data_[length_] = '\0';
|
||||
return *this;
|
||||
}
|
||||
|
||||
FixedString& operator+=(const FixedString& other) { return (*this += other.c_str()); }
|
||||
|
||||
operator std::string() const { return std::string(data_, length_); }
|
||||
|
||||
operator std::string_view() const { return std::string_view(data_, length_); }
|
||||
|
||||
template<typename T>
|
||||
bool operator==(const T& other) const noexcept {
|
||||
return (std::string_view) (*this) == other;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool operator!=(const T& other) const noexcept {
|
||||
return (std::string_view) (*this) != other;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
length_ = 0;
|
||||
data_[0] = '\0';
|
||||
}
|
||||
|
||||
private:
|
||||
char data_[Capacity + 1]; // +1 for null terminator
|
||||
std::size_t length_;
|
||||
};
|
||||
|
||||
struct CommandLine {
|
||||
public:
|
||||
@@ -317,6 +435,38 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define sf_always_inline __attribute__((always_inline))
|
||||
#elif defined(_MSC_VER)
|
||||
#define sf_always_inline __forceinline
|
||||
#else
|
||||
// do nothing for other compilers
|
||||
#define sf_always_inline
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#if __GNUC__ >= 13
|
||||
#define sf_assume(cond) __attribute__((assume(cond)))
|
||||
#else
|
||||
#define sf_assume(cond) \
|
||||
do \
|
||||
{ \
|
||||
if (!(cond)) \
|
||||
__builtin_unreachable(); \
|
||||
} while (0)
|
||||
#endif
|
||||
#else
|
||||
// do nothing for other compilers
|
||||
#define sf_assume(cond)
|
||||
#endif
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
template<std::size_t N>
|
||||
struct std::hash<Stockfish::FixedString<N>> {
|
||||
std::size_t operator()(const Stockfish::FixedString<N>& fstr) const noexcept {
|
||||
return Stockfish::hash_bytes(fstr.data(), fstr.size());
|
||||
}
|
||||
};
|
||||
|
||||
#endif // #ifndef MISC_H_INCLUDED
|
||||
|
||||
+96
-40
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -24,12 +24,89 @@
|
||||
#include "bitboard.h"
|
||||
#include "position.h"
|
||||
|
||||
#if defined(USE_AVX512ICL)
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(USE_AVX512ICL)
|
||||
|
||||
inline Move* write_moves(Move* moveList, uint32_t mask, __m512i vector) {
|
||||
// Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4
|
||||
_mm512_storeu_si512(reinterpret_cast<__m512i*>(moveList),
|
||||
_mm512_maskz_compress_epi16(mask, vector));
|
||||
return moveList + popcount(mask);
|
||||
}
|
||||
|
||||
template<Direction offset>
|
||||
inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {
|
||||
alignas(64) static constexpr auto SPLAT_TABLE = [] {
|
||||
std::array<Move, 64> table{};
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
Square from{uint8_t(std::clamp(i - offset, 0, 63))};
|
||||
table[i] = {Move(from, Square{uint8_t(i)})};
|
||||
}
|
||||
return table;
|
||||
}();
|
||||
|
||||
auto table = reinterpret_cast<const __m512i*>(SPLAT_TABLE.data());
|
||||
|
||||
moveList =
|
||||
write_moves(moveList, static_cast<uint32_t>(to_bb >> 0), _mm512_load_si512(table + 0));
|
||||
moveList =
|
||||
write_moves(moveList, static_cast<uint32_t>(to_bb >> 32), _mm512_load_si512(table + 1));
|
||||
|
||||
return moveList;
|
||||
}
|
||||
|
||||
inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) {
|
||||
alignas(64) static constexpr auto SPLAT_TABLE = [] {
|
||||
std::array<Move, 64> table{};
|
||||
for (uint8_t i = 0; i < 64; i++)
|
||||
table[i] = {Move(SQUARE_ZERO, Square{i})};
|
||||
return table;
|
||||
}();
|
||||
|
||||
__m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw());
|
||||
|
||||
auto table = reinterpret_cast<const __m512i*>(SPLAT_TABLE.data());
|
||||
|
||||
moveList = write_moves(moveList, static_cast<uint32_t>(to_bb >> 0),
|
||||
_mm512_or_si512(_mm512_load_si512(table + 0), fromVec));
|
||||
moveList = write_moves(moveList, static_cast<uint32_t>(to_bb >> 32),
|
||||
_mm512_or_si512(_mm512_load_si512(table + 1), fromVec));
|
||||
|
||||
return moveList;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template<Direction offset>
|
||||
inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {
|
||||
while (to_bb)
|
||||
{
|
||||
Square to = pop_lsb(to_bb);
|
||||
*moveList++ = Move(to - offset, to);
|
||||
}
|
||||
return moveList;
|
||||
}
|
||||
|
||||
inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) {
|
||||
while (to_bb)
|
||||
*moveList++ = Move(from, pop_lsb(to_bb));
|
||||
return moveList;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template<GenType Type, Direction D, bool Enemy>
|
||||
ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
|
||||
Move* make_promotions(Move* moveList, [[maybe_unused]] Square to) {
|
||||
|
||||
constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS;
|
||||
|
||||
@@ -48,7 +125,7 @@ ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
|
||||
|
||||
|
||||
template<Color Us, GenType Type>
|
||||
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
|
||||
Move* generate_pawn_moves(const Position& pos, Move* moveList, Bitboard target) {
|
||||
|
||||
constexpr Color Them = ~Us;
|
||||
constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
|
||||
@@ -75,17 +152,8 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta
|
||||
b2 &= target;
|
||||
}
|
||||
|
||||
while (b1)
|
||||
{
|
||||
Square to = pop_lsb(b1);
|
||||
*moveList++ = Move(to - Up, to);
|
||||
}
|
||||
|
||||
while (b2)
|
||||
{
|
||||
Square to = pop_lsb(b2);
|
||||
*moveList++ = Move(to - Up - Up, to);
|
||||
}
|
||||
moveList = splat_pawn_moves<Up>(moveList, b1);
|
||||
moveList = splat_pawn_moves<Up + Up>(moveList, b2);
|
||||
}
|
||||
|
||||
// Promotions and underpromotions
|
||||
@@ -114,17 +182,8 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta
|
||||
Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
|
||||
Bitboard b2 = shift<UpLeft>(pawnsNotOn7) & enemies;
|
||||
|
||||
while (b1)
|
||||
{
|
||||
Square to = pop_lsb(b1);
|
||||
*moveList++ = Move(to - UpRight, to);
|
||||
}
|
||||
|
||||
while (b2)
|
||||
{
|
||||
Square to = pop_lsb(b2);
|
||||
*moveList++ = Move(to - UpLeft, to);
|
||||
}
|
||||
moveList = splat_pawn_moves<UpRight>(moveList, b1);
|
||||
moveList = splat_pawn_moves<UpLeft>(moveList, b2);
|
||||
|
||||
if (pos.ep_square() != SQ_NONE)
|
||||
{
|
||||
@@ -134,7 +193,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta
|
||||
if (Type == EVASIONS && (target & (pos.ep_square() + Up)))
|
||||
return moveList;
|
||||
|
||||
b1 = pawnsNotOn7 & pawn_attacks_bb(Them, pos.ep_square());
|
||||
b1 = pawnsNotOn7 & attacks_bb<PAWN>(pos.ep_square(), Them);
|
||||
|
||||
assert(b1);
|
||||
|
||||
@@ -148,7 +207,7 @@ ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard ta
|
||||
|
||||
|
||||
template<Color Us, PieceType Pt>
|
||||
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
|
||||
Move* generate_moves(const Position& pos, Move* moveList, Bitboard target) {
|
||||
|
||||
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
|
||||
|
||||
@@ -159,8 +218,7 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target)
|
||||
Square from = pop_lsb(bb);
|
||||
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
|
||||
|
||||
while (b)
|
||||
*moveList++ = Move(from, pop_lsb(b));
|
||||
moveList = splat_moves(moveList, from, b);
|
||||
}
|
||||
|
||||
return moveList;
|
||||
@@ -168,7 +226,7 @@ ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target)
|
||||
|
||||
|
||||
template<Color Us, GenType Type>
|
||||
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
|
||||
Move* generate_all(const Position& pos, Move* moveList) {
|
||||
|
||||
static_assert(Type != LEGAL, "Unsupported type in generate_all()");
|
||||
|
||||
@@ -192,8 +250,7 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
|
||||
|
||||
Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
|
||||
|
||||
while (b)
|
||||
*moveList++ = Move(ksq, pop_lsb(b));
|
||||
moveList = splat_moves(moveList, ksq, b);
|
||||
|
||||
if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING))
|
||||
for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE})
|
||||
@@ -213,7 +270,7 @@ ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
|
||||
//
|
||||
// Returns a pointer to the end of the move list.
|
||||
template<GenType Type>
|
||||
ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
||||
Move* generate(const Position& pos, Move* moveList) {
|
||||
|
||||
static_assert(Type != LEGAL, "Unsupported type in generate()");
|
||||
assert((Type == EVASIONS) == bool(pos.checkers()));
|
||||
@@ -225,21 +282,20 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
||||
}
|
||||
|
||||
// Explicit template instantiations
|
||||
template ExtMove* generate<CAPTURES>(const Position&, ExtMove*);
|
||||
template ExtMove* generate<QUIETS>(const Position&, ExtMove*);
|
||||
template ExtMove* generate<EVASIONS>(const Position&, ExtMove*);
|
||||
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
|
||||
|
||||
template Move* generate<CAPTURES>(const Position&, Move*);
|
||||
template Move* generate<QUIETS>(const Position&, Move*);
|
||||
template Move* generate<EVASIONS>(const Position&, Move*);
|
||||
template Move* generate<NON_EVASIONS>(const Position&, Move*);
|
||||
|
||||
// generate<LEGAL> generates all the legal moves in the given position
|
||||
|
||||
template<>
|
||||
ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
|
||||
Move* generate<LEGAL>(const Position& pos, Move* moveList) {
|
||||
|
||||
Color us = pos.side_to_move();
|
||||
Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
|
||||
Square ksq = pos.square<KING>(us);
|
||||
ExtMove* cur = moveList;
|
||||
Move* cur = moveList;
|
||||
|
||||
moveList =
|
||||
pos.checkers() ? generate<EVASIONS>(pos, moveList) : generate<NON_EVASIONS>(pos, moveList);
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -49,7 +49,7 @@ struct ExtMove: public Move {
|
||||
inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; }
|
||||
|
||||
template<GenType>
|
||||
ExtMove* generate(const Position& pos, ExtMove* moveList);
|
||||
Move* generate(const Position& pos, Move* moveList);
|
||||
|
||||
// The MoveList struct wraps the generate() function and returns a convenient
|
||||
// list of moves. Using MoveList is sometimes preferable to directly calling
|
||||
@@ -59,13 +59,13 @@ struct MoveList {
|
||||
|
||||
explicit MoveList(const Position& pos) :
|
||||
last(generate<T>(pos, moveList)) {}
|
||||
const ExtMove* begin() const { return moveList; }
|
||||
const ExtMove* end() const { return last; }
|
||||
size_t size() const { return last - moveList; }
|
||||
bool contains(Move move) const { return std::find(begin(), end(), move) != end(); }
|
||||
const Move* begin() const { return moveList; }
|
||||
const Move* end() const { return last; }
|
||||
size_t size() const { return last - moveList; }
|
||||
bool contains(Move move) const { return std::find(begin(), end(), move) != end(); }
|
||||
|
||||
private:
|
||||
ExtMove moveList[MAX_MOVES], *last;
|
||||
Move moveList[MAX_MOVES], *last;
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
+74
-81
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
|
||||
#include "bitboard.h"
|
||||
#include "misc.h"
|
||||
@@ -55,6 +56,7 @@ enum Stages {
|
||||
QCAPTURE
|
||||
};
|
||||
|
||||
|
||||
// Sort moves in descending order up to and including a given limit.
|
||||
// The order of moves smaller than the limit is left unspecified.
|
||||
void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
|
||||
@@ -85,14 +87,14 @@ MovePicker::MovePicker(const Position& p,
|
||||
const LowPlyHistory* lph,
|
||||
const CapturePieceToHistory* cph,
|
||||
const PieceToHistory** ch,
|
||||
const PawnHistory* ph,
|
||||
const SharedHistories* sh,
|
||||
int pl) :
|
||||
pos(p),
|
||||
mainHistory(mh),
|
||||
lowPlyHistory(lph),
|
||||
captureHistory(cph),
|
||||
continuationHistory(ch),
|
||||
pawnHistory(ph),
|
||||
sharedHistory(sh),
|
||||
ttMove(ttm),
|
||||
depth(d),
|
||||
ply(pl) {
|
||||
@@ -113,86 +115,79 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo
|
||||
threshold(th) {
|
||||
assert(!pos.checkers());
|
||||
|
||||
stage = PROBCUT_TT
|
||||
+ !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold));
|
||||
stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm));
|
||||
}
|
||||
|
||||
// Assigns a numerical value to each move in a list, used for sorting.
|
||||
// Captures are ordered by Most Valuable Victim (MVV), preferring captures
|
||||
// with a good history. Quiets moves are ordered using the history tables.
|
||||
template<GenType Type>
|
||||
void MovePicker::score() {
|
||||
ExtMove* MovePicker::score(MoveList<Type>& ml) {
|
||||
|
||||
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
|
||||
|
||||
[[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook,
|
||||
threatenedPieces;
|
||||
Color us = pos.side_to_move();
|
||||
|
||||
[[maybe_unused]] Bitboard threatByLesser[KING + 1];
|
||||
if constexpr (Type == QUIETS)
|
||||
{
|
||||
Color us = pos.side_to_move();
|
||||
|
||||
threatenedByPawn = pos.attacks_by<PAWN>(~us);
|
||||
threatenedByMinor =
|
||||
pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
|
||||
threatenedByRook = pos.attacks_by<ROOK>(~us) | threatenedByMinor;
|
||||
|
||||
// Pieces threatened by pieces of lesser material value
|
||||
threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook)
|
||||
| (pos.pieces(us, ROOK) & threatenedByMinor)
|
||||
| (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn);
|
||||
threatByLesser[PAWN] = 0;
|
||||
threatByLesser[KNIGHT] = threatByLesser[BISHOP] = pos.attacks_by<PAWN>(~us);
|
||||
threatByLesser[ROOK] =
|
||||
pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatByLesser[KNIGHT];
|
||||
threatByLesser[QUEEN] = pos.attacks_by<ROOK>(~us) | threatByLesser[ROOK];
|
||||
threatByLesser[KING] = pos.attacks_by<QUEEN>(~us) | threatByLesser[QUEEN];
|
||||
}
|
||||
|
||||
for (auto& m : *this)
|
||||
ExtMove* it = cur;
|
||||
for (auto move : ml)
|
||||
{
|
||||
ExtMove& m = *it++;
|
||||
m = move;
|
||||
|
||||
const Square from = m.from_sq();
|
||||
const Square to = m.to_sq();
|
||||
const Piece pc = pos.moved_piece(m);
|
||||
const PieceType pt = type_of(pc);
|
||||
const Piece capturedPiece = pos.piece_on(to);
|
||||
|
||||
if constexpr (Type == CAPTURES)
|
||||
m.value =
|
||||
7 * int(PieceValue[pos.piece_on(m.to_sq())])
|
||||
+ (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))];
|
||||
m.value = (*captureHistory)[pc][to][type_of(capturedPiece)]
|
||||
+ 7 * int(PieceValue[capturedPiece]);
|
||||
|
||||
else if constexpr (Type == QUIETS)
|
||||
{
|
||||
Piece pc = pos.moved_piece(m);
|
||||
PieceType pt = type_of(pc);
|
||||
Square from = m.from_sq();
|
||||
Square to = m.to_sq();
|
||||
|
||||
// histories
|
||||
m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()];
|
||||
m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to];
|
||||
m.value = 2 * (*mainHistory)[us][m.raw()];
|
||||
m.value += 2 * sharedHistory->pawn_entry(pos)[pc][to];
|
||||
m.value += (*continuationHistory[0])[pc][to];
|
||||
m.value += (*continuationHistory[1])[pc][to];
|
||||
m.value += (*continuationHistory[2])[pc][to];
|
||||
m.value += (*continuationHistory[3])[pc][to];
|
||||
m.value += (*continuationHistory[4])[pc][to] / 3;
|
||||
m.value += (*continuationHistory[5])[pc][to];
|
||||
|
||||
// bonus for checks
|
||||
m.value += bool(pos.check_squares(pt) & to) * 16384;
|
||||
m.value += (bool(pos.check_squares(pt) & to) && pos.see_ge(m, -75)) * 16384;
|
||||
|
||||
// bonus for escaping from capture
|
||||
m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51700
|
||||
: pt == ROOK && !(to & threatenedByMinor) ? 25600
|
||||
: !(to & threatenedByPawn) ? 14450
|
||||
: 0)
|
||||
: 0;
|
||||
// penalty for moving to a square threatened by a lesser piece
|
||||
// or bonus for escaping an attack by a lesser piece.
|
||||
int v = threatByLesser[pt] & to ? -19 : 20 * bool(threatByLesser[pt] & from);
|
||||
m.value += PieceValue[pt] * v;
|
||||
|
||||
// malus for putting piece en prise
|
||||
m.value -= (pt == QUEEN ? bool(to & threatenedByRook) * 49000
|
||||
: pt == ROOK && bool(to & threatenedByMinor) ? 24335
|
||||
: 0);
|
||||
|
||||
if (ply < LOW_PLY_HISTORY_SIZE)
|
||||
m.value += 8 * (*lowPlyHistory)[ply][m.from_to()] / (1 + 2 * ply);
|
||||
m.value += 8 * (*lowPlyHistory)[ply][m.raw()] / (1 + ply);
|
||||
}
|
||||
|
||||
else // Type == EVASIONS
|
||||
{
|
||||
if (pos.capture_stage(m))
|
||||
m.value = PieceValue[pos.piece_on(m.to_sq())] + (1 << 28);
|
||||
m.value = PieceValue[capturedPiece] + (1 << 28);
|
||||
else
|
||||
m.value = (*mainHistory)[pos.side_to_move()][m.from_to()]
|
||||
+ (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()]
|
||||
+ (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][m.to_sq()];
|
||||
m.value = (*mainHistory)[us][m.raw()] + (*continuationHistory[0])[pc][to];
|
||||
}
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
// Returns the next move satisfying a predicate function.
|
||||
@@ -200,7 +195,7 @@ void MovePicker::score() {
|
||||
template<typename Pred>
|
||||
Move MovePicker::select(Pred filter) {
|
||||
|
||||
for (; cur < endMoves; ++cur)
|
||||
for (; cur < endCur; ++cur)
|
||||
if (*cur != ttMove && filter())
|
||||
return *cur++;
|
||||
|
||||
@@ -212,8 +207,7 @@ Move MovePicker::select(Pred filter) {
|
||||
// picking the move with the highest score from a list of generated moves.
|
||||
Move MovePicker::next_move() {
|
||||
|
||||
auto quiet_threshold = [](Depth d) { return -3560 * d; };
|
||||
|
||||
constexpr int goodQuietThreshold = -14000;
|
||||
top:
|
||||
switch (stage)
|
||||
{
|
||||
@@ -227,20 +221,23 @@ top:
|
||||
|
||||
case CAPTURE_INIT :
|
||||
case PROBCUT_INIT :
|
||||
case QCAPTURE_INIT :
|
||||
cur = endBadCaptures = moves;
|
||||
endMoves = generate<CAPTURES>(pos, cur);
|
||||
case QCAPTURE_INIT : {
|
||||
MoveList<CAPTURES> ml(pos);
|
||||
|
||||
score<CAPTURES>();
|
||||
partial_insertion_sort(cur, endMoves, std::numeric_limits<int>::min());
|
||||
cur = endBadCaptures = moves;
|
||||
endCur = endCaptures = score<CAPTURES>(ml);
|
||||
|
||||
partial_insertion_sort(cur, endCur, std::numeric_limits<int>::min());
|
||||
++stage;
|
||||
goto top;
|
||||
}
|
||||
|
||||
case GOOD_CAPTURE :
|
||||
if (select([&]() {
|
||||
// Move losing capture to endBadCaptures to be tried later
|
||||
return pos.see_ge(*cur, -cur->value / 18) ? true
|
||||
: (*endBadCaptures++ = *cur, false);
|
||||
if (pos.see_ge(*cur, -cur->value / 18))
|
||||
return true;
|
||||
std::swap(*endBadCaptures++, *cur);
|
||||
return false;
|
||||
}))
|
||||
return *(cur - 1);
|
||||
|
||||
@@ -250,29 +247,23 @@ top:
|
||||
case QUIET_INIT :
|
||||
if (!skipQuiets)
|
||||
{
|
||||
cur = endBadCaptures;
|
||||
endMoves = beginBadQuiets = endBadQuiets = generate<QUIETS>(pos, cur);
|
||||
MoveList<QUIETS> ml(pos);
|
||||
|
||||
score<QUIETS>();
|
||||
partial_insertion_sort(cur, endMoves, quiet_threshold(depth));
|
||||
endCur = endGenerated = score<QUIETS>(ml);
|
||||
|
||||
partial_insertion_sort(cur, endCur, -3560 * depth);
|
||||
}
|
||||
|
||||
++stage;
|
||||
[[fallthrough]];
|
||||
|
||||
case GOOD_QUIET :
|
||||
if (!skipQuiets && select([]() { return true; }))
|
||||
{
|
||||
if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth))
|
||||
return *(cur - 1);
|
||||
|
||||
// Remaining quiets are bad
|
||||
beginBadQuiets = cur - 1;
|
||||
}
|
||||
if (!skipQuiets && select([&]() { return cur->value > goodQuietThreshold; }))
|
||||
return *(cur - 1);
|
||||
|
||||
// Prepare the pointers to loop over the bad captures
|
||||
cur = moves;
|
||||
endMoves = endBadCaptures;
|
||||
cur = moves;
|
||||
endCur = endBadCaptures;
|
||||
|
||||
++stage;
|
||||
[[fallthrough]];
|
||||
@@ -281,27 +272,29 @@ top:
|
||||
if (select([]() { return true; }))
|
||||
return *(cur - 1);
|
||||
|
||||
// Prepare the pointers to loop over the bad quiets
|
||||
cur = beginBadQuiets;
|
||||
endMoves = endBadQuiets;
|
||||
// Prepare the pointers to loop over quiets again
|
||||
cur = endCaptures;
|
||||
endCur = endGenerated;
|
||||
|
||||
++stage;
|
||||
[[fallthrough]];
|
||||
|
||||
case BAD_QUIET :
|
||||
if (!skipQuiets)
|
||||
return select([]() { return true; });
|
||||
return select([&]() { return cur->value <= goodQuietThreshold; });
|
||||
|
||||
return Move::none();
|
||||
|
||||
case EVASION_INIT :
|
||||
cur = moves;
|
||||
endMoves = generate<EVASIONS>(pos, cur);
|
||||
case EVASION_INIT : {
|
||||
MoveList<EVASIONS> ml(pos);
|
||||
|
||||
score<EVASIONS>();
|
||||
partial_insertion_sort(cur, endMoves, std::numeric_limits<int>::min());
|
||||
cur = moves;
|
||||
endCur = endGenerated = score<EVASIONS>(ml);
|
||||
|
||||
partial_insertion_sort(cur, endCur, std::numeric_limits<int>::min());
|
||||
++stage;
|
||||
[[fallthrough]];
|
||||
}
|
||||
|
||||
case EVASION :
|
||||
case QCAPTURE :
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -45,7 +45,7 @@ class MovePicker {
|
||||
const LowPlyHistory*,
|
||||
const CapturePieceToHistory*,
|
||||
const PieceToHistory**,
|
||||
const PawnHistory*,
|
||||
const SharedHistories*,
|
||||
int);
|
||||
MovePicker(const Position&, Move, int, const CapturePieceToHistory*);
|
||||
Move next_move();
|
||||
@@ -54,19 +54,19 @@ class MovePicker {
|
||||
private:
|
||||
template<typename Pred>
|
||||
Move select(Pred);
|
||||
template<GenType>
|
||||
void score();
|
||||
template<GenType T>
|
||||
ExtMove* score(MoveList<T>&);
|
||||
ExtMove* begin() { return cur; }
|
||||
ExtMove* end() { return endMoves; }
|
||||
ExtMove* end() { return endCur; }
|
||||
|
||||
const Position& pos;
|
||||
const ButterflyHistory* mainHistory;
|
||||
const LowPlyHistory* lowPlyHistory;
|
||||
const CapturePieceToHistory* captureHistory;
|
||||
const PieceToHistory** continuationHistory;
|
||||
const PawnHistory* pawnHistory;
|
||||
const SharedHistories* sharedHistory;
|
||||
Move ttMove;
|
||||
ExtMove * cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets;
|
||||
ExtMove * cur, *endCur, *endBadCaptures, *endCaptures, *endGenerated;
|
||||
int stage;
|
||||
int threshold;
|
||||
Depth depth;
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Stockfish is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//Definition of input features FullThreats of NNUE evaluation function
|
||||
|
||||
#include "full_threats.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
|
||||
#include "../../bitboard.h"
|
||||
#include "../../misc.h"
|
||||
#include "../../position.h"
|
||||
#include "../../types.h"
|
||||
#include "../nnue_common.h"
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Features {
|
||||
|
||||
struct HelperOffsets {
|
||||
int cumulativePieceOffset, cumulativeOffset;
|
||||
};
|
||||
|
||||
constexpr std::array<Piece, 12> AllPieces = {
|
||||
W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
|
||||
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
|
||||
};
|
||||
|
||||
template<PieceType PT>
|
||||
constexpr auto make_piece_indices_type() {
|
||||
static_assert(PT != PieceType::PAWN);
|
||||
|
||||
std::array<std::array<uint8_t, SQUARE_NB>, SQUARE_NB> out{};
|
||||
|
||||
for (Square from = SQ_A1; from <= SQ_H8; ++from)
|
||||
{
|
||||
Bitboard attacks = PseudoAttacks[PT][from];
|
||||
|
||||
for (Square to = SQ_A1; to <= SQ_H8; ++to)
|
||||
{
|
||||
out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template<Piece P>
|
||||
constexpr auto make_piece_indices_piece() {
|
||||
static_assert(type_of(P) == PieceType::PAWN);
|
||||
|
||||
std::array<std::array<uint8_t, SQUARE_NB>, SQUARE_NB> out{};
|
||||
|
||||
constexpr Color C = color_of(P);
|
||||
|
||||
for (Square from = SQ_A1; from <= SQ_H8; ++from)
|
||||
{
|
||||
Bitboard attacks = PseudoAttacks[C][from];
|
||||
|
||||
for (Square to = SQ_A1; to <= SQ_H8; ++to)
|
||||
{
|
||||
out[from][to] = constexpr_popcount(((1ULL << to) - 1) & attacks);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
constexpr auto index_lut2_array() {
|
||||
constexpr auto KNIGHT_ATTACKS = make_piece_indices_type<PieceType::KNIGHT>();
|
||||
constexpr auto BISHOP_ATTACKS = make_piece_indices_type<PieceType::BISHOP>();
|
||||
constexpr auto ROOK_ATTACKS = make_piece_indices_type<PieceType::ROOK>();
|
||||
constexpr auto QUEEN_ATTACKS = make_piece_indices_type<PieceType::QUEEN>();
|
||||
constexpr auto KING_ATTACKS = make_piece_indices_type<PieceType::KING>();
|
||||
|
||||
std::array<std::array<std::array<uint8_t, SQUARE_NB>, SQUARE_NB>, PIECE_NB> indices{};
|
||||
|
||||
indices[W_PAWN] = make_piece_indices_piece<W_PAWN>();
|
||||
indices[B_PAWN] = make_piece_indices_piece<B_PAWN>();
|
||||
|
||||
indices[W_KNIGHT] = KNIGHT_ATTACKS;
|
||||
indices[B_KNIGHT] = KNIGHT_ATTACKS;
|
||||
|
||||
indices[W_BISHOP] = BISHOP_ATTACKS;
|
||||
indices[B_BISHOP] = BISHOP_ATTACKS;
|
||||
|
||||
indices[W_ROOK] = ROOK_ATTACKS;
|
||||
indices[B_ROOK] = ROOK_ATTACKS;
|
||||
|
||||
indices[W_QUEEN] = QUEEN_ATTACKS;
|
||||
indices[B_QUEEN] = QUEEN_ATTACKS;
|
||||
|
||||
indices[W_KING] = KING_ATTACKS;
|
||||
indices[B_KING] = KING_ATTACKS;
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
constexpr auto init_threat_offsets() {
|
||||
std::array<HelperOffsets, PIECE_NB> indices{};
|
||||
std::array<std::array<IndexType, SQUARE_NB>, PIECE_NB> offsets{};
|
||||
|
||||
int cumulativeOffset = 0;
|
||||
for (Piece piece : AllPieces)
|
||||
{
|
||||
int pieceIdx = piece;
|
||||
int cumulativePieceOffset = 0;
|
||||
|
||||
for (Square from = SQ_A1; from <= SQ_H8; ++from)
|
||||
{
|
||||
offsets[pieceIdx][from] = cumulativePieceOffset;
|
||||
|
||||
if (type_of(piece) != PAWN)
|
||||
{
|
||||
Bitboard attacks = PseudoAttacks[type_of(piece)][from];
|
||||
cumulativePieceOffset += constexpr_popcount(attacks);
|
||||
}
|
||||
|
||||
else if (from >= SQ_A2 && from <= SQ_H7)
|
||||
{
|
||||
Bitboard attacks = (pieceIdx < 8) ? pawn_attacks_bb<WHITE>(square_bb(from))
|
||||
: pawn_attacks_bb<BLACK>(square_bb(from));
|
||||
cumulativePieceOffset += constexpr_popcount(attacks);
|
||||
}
|
||||
}
|
||||
|
||||
indices[pieceIdx] = {cumulativePieceOffset, cumulativeOffset};
|
||||
|
||||
cumulativeOffset += numValidTargets[pieceIdx] * cumulativePieceOffset;
|
||||
}
|
||||
|
||||
return std::pair{indices, offsets};
|
||||
}
|
||||
|
||||
constexpr auto helper_offsets = init_threat_offsets().first;
|
||||
// Lookup array for indexing threats
|
||||
constexpr auto offsets = init_threat_offsets().second;
|
||||
|
||||
constexpr auto init_index_luts() {
|
||||
std::array<std::array<std::array<uint32_t, 2>, PIECE_NB>, PIECE_NB> indices{};
|
||||
|
||||
for (Piece attacker : AllPieces)
|
||||
{
|
||||
for (Piece attacked : AllPieces)
|
||||
{
|
||||
bool enemy = (attacker ^ attacked) == 8;
|
||||
PieceType attackerType = type_of(attacker);
|
||||
PieceType attackedType = type_of(attacked);
|
||||
|
||||
int map = FullThreats::map[attackerType - 1][attackedType - 1];
|
||||
bool semi_excluded = attackerType == attackedType && (enemy || attackerType != PAWN);
|
||||
IndexType feature = helper_offsets[attacker].cumulativeOffset
|
||||
+ (color_of(attacked) * (numValidTargets[attacker] / 2) + map)
|
||||
* helper_offsets[attacker].cumulativePieceOffset;
|
||||
|
||||
bool excluded = map < 0;
|
||||
indices[attacker][attacked][0] = excluded ? FullThreats::Dimensions : feature;
|
||||
indices[attacker][attacked][1] =
|
||||
excluded || semi_excluded ? FullThreats::Dimensions : feature;
|
||||
}
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
// The final index is calculated from summing data found in these two LUTs, as well
|
||||
// as offsets[attacker][from]
|
||||
|
||||
// [attacker][attacked][from < to]
|
||||
constexpr auto index_lut1 = init_index_luts();
|
||||
// [attacker][from][to]
|
||||
constexpr auto index_lut2 = index_lut2_array();
|
||||
|
||||
// Index of a feature for a given king position and another piece on some square
|
||||
inline sf_always_inline IndexType FullThreats::make_index(
|
||||
Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) {
|
||||
const std::int8_t orientation = OrientTBL[ksq] ^ (56 * perspective);
|
||||
unsigned from_oriented = uint8_t(from) ^ orientation;
|
||||
unsigned to_oriented = uint8_t(to) ^ orientation;
|
||||
|
||||
std::int8_t swap = 8 * perspective;
|
||||
unsigned attacker_oriented = attacker ^ swap;
|
||||
unsigned attacked_oriented = attacked ^ swap;
|
||||
|
||||
return index_lut1[attacker_oriented][attacked_oriented][from_oriented < to_oriented]
|
||||
+ offsets[attacker_oriented][from_oriented]
|
||||
+ index_lut2[attacker_oriented][from_oriented][to_oriented];
|
||||
}
|
||||
|
||||
// Get a list of indices for active features in ascending order
|
||||
|
||||
void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) {
|
||||
Square ksq = pos.square<KING>(perspective);
|
||||
Bitboard occupied = pos.pieces();
|
||||
|
||||
for (Color color : {WHITE, BLACK})
|
||||
{
|
||||
for (PieceType pt = PAWN; pt <= KING; ++pt)
|
||||
{
|
||||
Color c = Color(perspective ^ color);
|
||||
Piece attacker = make_piece(c, pt);
|
||||
Bitboard bb = pos.pieces(c, pt);
|
||||
|
||||
if (pt == PAWN)
|
||||
{
|
||||
auto right = (c == WHITE) ? NORTH_EAST : SOUTH_WEST;
|
||||
auto left = (c == WHITE) ? NORTH_WEST : SOUTH_EAST;
|
||||
auto attacks_left =
|
||||
((c == WHITE) ? shift<NORTH_EAST>(bb) : shift<SOUTH_WEST>(bb)) & occupied;
|
||||
auto attacks_right =
|
||||
((c == WHITE) ? shift<NORTH_WEST>(bb) : shift<SOUTH_EAST>(bb)) & occupied;
|
||||
|
||||
while (attacks_left)
|
||||
{
|
||||
Square to = pop_lsb(attacks_left);
|
||||
Square from = to - right;
|
||||
Piece attacked = pos.piece_on(to);
|
||||
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
|
||||
|
||||
if (index < Dimensions)
|
||||
active.push_back(index);
|
||||
}
|
||||
|
||||
while (attacks_right)
|
||||
{
|
||||
Square to = pop_lsb(attacks_right);
|
||||
Square from = to - left;
|
||||
Piece attacked = pos.piece_on(to);
|
||||
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
|
||||
|
||||
if (index < Dimensions)
|
||||
active.push_back(index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (bb)
|
||||
{
|
||||
Square from = pop_lsb(bb);
|
||||
Bitboard attacks = (attacks_bb(pt, from, occupied)) & occupied;
|
||||
|
||||
while (attacks)
|
||||
{
|
||||
Square to = pop_lsb(attacks);
|
||||
Piece attacked = pos.piece_on(to);
|
||||
IndexType index =
|
||||
make_index(perspective, attacker, from, to, attacked, ksq);
|
||||
|
||||
if (index < Dimensions)
|
||||
active.push_back(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of indices for recently changed features
|
||||
|
||||
void FullThreats::append_changed_indices(Color perspective,
|
||||
Square ksq,
|
||||
const DiffType& diff,
|
||||
IndexList& removed,
|
||||
IndexList& added,
|
||||
FusedUpdateData* fusedData,
|
||||
bool first) {
|
||||
|
||||
for (const auto& dirty : diff.list)
|
||||
{
|
||||
auto attacker = dirty.pc();
|
||||
auto attacked = dirty.threatened_pc();
|
||||
auto from = dirty.pc_sq();
|
||||
auto to = dirty.threatened_sq();
|
||||
auto add = dirty.add();
|
||||
|
||||
if (fusedData)
|
||||
{
|
||||
if (from == fusedData->dp2removed)
|
||||
{
|
||||
if (add)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
fusedData->dp2removedOriginBoard |= to;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (fusedData->dp2removedOriginBoard & to)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (to != SQ_NONE && to == fusedData->dp2removed)
|
||||
{
|
||||
if (add)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
fusedData->dp2removedTargetBoard |= from;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (fusedData->dp2removedTargetBoard & from)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto& insert = add ? added : removed;
|
||||
const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
|
||||
|
||||
if (index < Dimensions)
|
||||
insert.push_back(index);
|
||||
}
|
||||
}
|
||||
|
||||
bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) {
|
||||
return perspective == diff.us && (int8_t(diff.ksq) & 0b100) != (int8_t(diff.prevKsq) & 0b100);
|
||||
}
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE::Features
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
Stockfish is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//Definition of input features Simplified_Threats of NNUE evaluation function
|
||||
|
||||
#ifndef NNUE_FEATURES_FULL_THREATS_INCLUDED
|
||||
#define NNUE_FEATURES_FULL_THREATS_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../misc.h"
|
||||
#include "../../types.h"
|
||||
#include "../nnue_common.h"
|
||||
|
||||
namespace Stockfish {
|
||||
class Position;
|
||||
}
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Features {
|
||||
|
||||
static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0,
|
||||
0, 6, 12, 10, 10, 12, 8, 0};
|
||||
|
||||
class FullThreats {
|
||||
public:
|
||||
// Feature name
|
||||
static constexpr const char* Name = "Full_Threats(Friend)";
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t HashValue = 0x8f234cb8u;
|
||||
|
||||
// Number of feature dimensions
|
||||
static constexpr IndexType Dimensions = 79856;
|
||||
|
||||
// clang-format off
|
||||
// Orient a square according to perspective (rotates by 180 for black)
|
||||
static constexpr std::int8_t OrientTBL[SQUARE_NB] = {
|
||||
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
|
||||
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
|
||||
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
|
||||
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
|
||||
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
|
||||
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
|
||||
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
|
||||
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
|
||||
};
|
||||
|
||||
static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = {
|
||||
{0, 1, -1, 2, -1, -1},
|
||||
{0, 1, 2, 3, 4, 5},
|
||||
{0, 1, 2, 3, -1, 4},
|
||||
{0, 1, 2, 3, -1, 4},
|
||||
{0, 1, 2, 3, 4, 5},
|
||||
{0, 1, 2, 3, -1, -1}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
struct FusedUpdateData {
|
||||
Bitboard dp2removedOriginBoard = 0;
|
||||
Bitboard dp2removedTargetBoard = 0;
|
||||
|
||||
Square dp2removed;
|
||||
};
|
||||
|
||||
// Maximum number of simultaneously active features.
|
||||
static constexpr IndexType MaxActiveDimensions = 128;
|
||||
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
|
||||
using DiffType = DirtyThreats;
|
||||
|
||||
static IndexType
|
||||
make_index(Color perspective, Piece attkr, Square from, Square to, Piece attkd, Square ksq);
|
||||
|
||||
// Get a list of indices for active features
|
||||
static void append_active_indices(Color perspective, const Position& pos, IndexList& active);
|
||||
|
||||
// Get a list of indices for recently changed features
|
||||
static void append_changed_indices(Color perspective,
|
||||
Square ksq,
|
||||
const DiffType& diff,
|
||||
IndexList& removed,
|
||||
IndexList& added,
|
||||
FusedUpdateData* fd = nullptr,
|
||||
bool first = false);
|
||||
|
||||
// Returns whether the change stored in this DirtyPiece means
|
||||
// that a full accumulator refresh is required.
|
||||
static bool requires_refresh(const DiffType& diff, Color perspective);
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE::Features
|
||||
|
||||
#endif // #ifndef NNUE_FEATURES_FULL_THREATS_INCLUDED
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -23,62 +23,47 @@
|
||||
#include "../../bitboard.h"
|
||||
#include "../../position.h"
|
||||
#include "../../types.h"
|
||||
#include "../nnue_accumulator.h"
|
||||
#include "../nnue_common.h"
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Features {
|
||||
|
||||
// Index of a feature for a given king position and another piece on some square
|
||||
template<Color Perspective>
|
||||
inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) {
|
||||
return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc]
|
||||
+ KingBuckets[Perspective][ksq]);
|
||||
|
||||
IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) {
|
||||
const IndexType flip = 56 * perspective;
|
||||
return (IndexType(s) ^ OrientTBL[ksq] ^ flip) + PieceSquareIndex[perspective][pc]
|
||||
+ KingBuckets[int(ksq) ^ flip];
|
||||
}
|
||||
|
||||
// Get a list of indices for active features
|
||||
template<Color Perspective>
|
||||
void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) {
|
||||
Square ksq = pos.square<KING>(Perspective);
|
||||
|
||||
void HalfKAv2_hm::append_active_indices(Color perspective, const Position& pos, IndexList& active) {
|
||||
Square ksq = pos.square<KING>(perspective);
|
||||
Bitboard bb = pos.pieces();
|
||||
while (bb)
|
||||
{
|
||||
Square s = pop_lsb(bb);
|
||||
active.push_back(make_index<Perspective>(s, pos.piece_on(s), ksq));
|
||||
active.push_back(make_index(perspective, s, pos.piece_on(s), ksq));
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit template instantiations
|
||||
template void HalfKAv2_hm::append_active_indices<WHITE>(const Position& pos, IndexList& active);
|
||||
template void HalfKAv2_hm::append_active_indices<BLACK>(const Position& pos, IndexList& active);
|
||||
template IndexType HalfKAv2_hm::make_index<WHITE>(Square s, Piece pc, Square ksq);
|
||||
template IndexType HalfKAv2_hm::make_index<BLACK>(Square s, Piece pc, Square ksq);
|
||||
|
||||
// Get a list of indices for recently changed features
|
||||
template<Color Perspective>
|
||||
void HalfKAv2_hm::append_changed_indices(Square ksq,
|
||||
const DirtyPiece& dp,
|
||||
IndexList& removed,
|
||||
IndexList& added) {
|
||||
for (int i = 0; i < dp.dirty_num; ++i)
|
||||
{
|
||||
if (dp.from[i] != SQ_NONE)
|
||||
removed.push_back(make_index<Perspective>(dp.from[i], dp.piece[i], ksq));
|
||||
if (dp.to[i] != SQ_NONE)
|
||||
added.push_back(make_index<Perspective>(dp.to[i], dp.piece[i], ksq));
|
||||
}
|
||||
|
||||
void HalfKAv2_hm::append_changed_indices(
|
||||
Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added) {
|
||||
removed.push_back(make_index(perspective, diff.from, diff.pc, ksq));
|
||||
if (diff.to != SQ_NONE)
|
||||
added.push_back(make_index(perspective, diff.to, diff.pc, ksq));
|
||||
|
||||
if (diff.remove_sq != SQ_NONE)
|
||||
removed.push_back(make_index(perspective, diff.remove_sq, diff.remove_pc, ksq));
|
||||
|
||||
if (diff.add_sq != SQ_NONE)
|
||||
added.push_back(make_index(perspective, diff.add_sq, diff.add_pc, ksq));
|
||||
}
|
||||
|
||||
// Explicit template instantiations
|
||||
template void HalfKAv2_hm::append_changed_indices<WHITE>(Square ksq,
|
||||
const DirtyPiece& dp,
|
||||
IndexList& removed,
|
||||
IndexList& added);
|
||||
template void HalfKAv2_hm::append_changed_indices<BLACK>(Square ksq,
|
||||
const DirtyPiece& dp,
|
||||
IndexList& removed,
|
||||
IndexList& added);
|
||||
|
||||
bool HalfKAv2_hm::requires_refresh(const DirtyPiece& dirtyPiece, Color perspective) {
|
||||
return dirtyPiece.piece[0] == make_piece(perspective, KING);
|
||||
bool HalfKAv2_hm::requires_refresh(const DiffType& diff, Color perspective) {
|
||||
return diff.pc == make_piece(perspective, KING);
|
||||
}
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE::Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -75,68 +75,52 @@ class HalfKAv2_hm {
|
||||
|
||||
#define B(v) (v * PS_NB)
|
||||
// clang-format off
|
||||
static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = {
|
||||
{ B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28),
|
||||
static constexpr IndexType KingBuckets[SQUARE_NB] = {
|
||||
B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28),
|
||||
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
|
||||
B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
|
||||
B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
|
||||
B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
|
||||
B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
|
||||
B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
|
||||
B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) },
|
||||
{ B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0),
|
||||
B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
|
||||
B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
|
||||
B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
|
||||
B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
|
||||
B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
|
||||
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
|
||||
B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) }
|
||||
B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0),
|
||||
};
|
||||
// clang-format on
|
||||
#undef B
|
||||
// clang-format off
|
||||
// Orient a square according to perspective (rotates by 180 for black)
|
||||
static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = {
|
||||
{ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
||||
static constexpr IndexType OrientTBL[SQUARE_NB] = {
|
||||
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
||||
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
||||
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
||||
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
||||
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
||||
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
||||
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 },
|
||||
{ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
||||
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
||||
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
||||
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
||||
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
||||
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
||||
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
|
||||
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 }
|
||||
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
|
||||
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 ,
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Maximum number of simultaneously active features.
|
||||
static constexpr IndexType MaxActiveDimensions = 32;
|
||||
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
|
||||
using DiffType = DirtyPiece;
|
||||
|
||||
// Index of a feature for a given king position and another piece on some square
|
||||
template<Color Perspective>
|
||||
static IndexType make_index(Square s, Piece pc, Square ksq);
|
||||
|
||||
static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq);
|
||||
|
||||
// Get a list of indices for active features
|
||||
template<Color Perspective>
|
||||
static void append_active_indices(const Position& pos, IndexList& active);
|
||||
|
||||
static void append_active_indices(Color perspective, const Position& pos, IndexList& active);
|
||||
|
||||
// Get a list of indices for recently changed features
|
||||
template<Color Perspective>
|
||||
static void
|
||||
append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
|
||||
static void append_changed_indices(
|
||||
Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added);
|
||||
|
||||
// Returns whether the change stored in this DirtyPiece means
|
||||
// that a full accumulator refresh is required.
|
||||
static bool requires_refresh(const DirtyPiece& dirtyPiece, Color perspective);
|
||||
static bool requires_refresh(const DiffType& diff, Color perspective);
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE::Features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <iostream>
|
||||
|
||||
#include "../nnue_common.h"
|
||||
#include "simd.h"
|
||||
#include "../simd.h"
|
||||
|
||||
/*
|
||||
This file contains the definition for a fully connected layer (aka affine transform).
|
||||
@@ -102,7 +102,7 @@ static void affine_transform_non_ssse3(std::int32_t* output,
|
||||
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
|
||||
sum = vpadalq_s16(sum, product);
|
||||
}
|
||||
output[i] = Simd::neon_m128_reduce_add_epi32(sum);
|
||||
output[i] = SIMD::neon_m128_reduce_add_epi32(sum);
|
||||
|
||||
#endif
|
||||
}
|
||||
@@ -181,6 +181,15 @@ class AffineTransform {
|
||||
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
hash_combine(h, get_raw_data_hash(biases));
|
||||
hash_combine(h, get_raw_data_hash(weights));
|
||||
hash_combine(h, get_hash_value(0));
|
||||
return h;
|
||||
}
|
||||
|
||||
// Forward propagation
|
||||
void propagate(const InputType* input, OutputType* output) const {
|
||||
|
||||
@@ -191,20 +200,20 @@ class AffineTransform {
|
||||
#if defined(USE_AVX512)
|
||||
using vec_t = __m512i;
|
||||
#define vec_set_32 _mm512_set1_epi32
|
||||
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32
|
||||
#elif defined(USE_AVX2)
|
||||
using vec_t = __m256i;
|
||||
#define vec_set_32 _mm256_set1_epi32
|
||||
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32
|
||||
#elif defined(USE_SSSE3)
|
||||
using vec_t = __m128i;
|
||||
#define vec_set_32 _mm_set1_epi32
|
||||
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32
|
||||
#elif defined(USE_NEON_DOTPROD)
|
||||
using vec_t = int32x4_t;
|
||||
#define vec_set_32 vdupq_n_s32
|
||||
#define vec_add_dpbusd_32(acc, a, b) \
|
||||
Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \
|
||||
SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \
|
||||
vreinterpretq_s8_s32(b))
|
||||
#endif
|
||||
|
||||
@@ -245,23 +254,20 @@ class AffineTransform {
|
||||
#if defined(USE_AVX2)
|
||||
using vec_t = __m256i;
|
||||
#define vec_setzero() _mm256_setzero_si256()
|
||||
#define vec_set_32 _mm256_set1_epi32
|
||||
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
||||
#define vec_hadd Simd::m256_hadd
|
||||
#define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32
|
||||
#define vec_hadd SIMD::m256_hadd
|
||||
#elif defined(USE_SSSE3)
|
||||
using vec_t = __m128i;
|
||||
#define vec_setzero() _mm_setzero_si128()
|
||||
#define vec_set_32 _mm_set1_epi32
|
||||
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
||||
#define vec_hadd Simd::m128_hadd
|
||||
#define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32
|
||||
#define vec_hadd SIMD::m128_hadd
|
||||
#elif defined(USE_NEON_DOTPROD)
|
||||
using vec_t = int32x4_t;
|
||||
#define vec_setzero() vdupq_n_s32(0)
|
||||
#define vec_set_32 vdupq_n_s32
|
||||
#define vec_add_dpbusd_32(acc, a, b) \
|
||||
Simd::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \
|
||||
SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \
|
||||
vreinterpretq_s8_s32(b))
|
||||
#define vec_hadd Simd::neon_m128_hadd
|
||||
#define vec_hadd SIMD::neon_m128_hadd
|
||||
#endif
|
||||
|
||||
const auto inputVector = reinterpret_cast<const vec_t*>(input);
|
||||
@@ -282,7 +288,6 @@ class AffineTransform {
|
||||
output[0] = vec_hadd(sum0, biases[0]);
|
||||
|
||||
#undef vec_setzero
|
||||
#undef vec_set_32
|
||||
#undef vec_add_dpbusd_32
|
||||
#undef vec_hadd
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -22,14 +22,13 @@
|
||||
#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#include "../../bitboard.h"
|
||||
#include "../simd.h"
|
||||
#include "../nnue_common.h"
|
||||
#include "affine_transform.h"
|
||||
#include "simd.h"
|
||||
|
||||
/*
|
||||
This file contains the definition for a fully connected layer (aka affine transform) with block sparse input.
|
||||
@@ -51,11 +50,7 @@ constexpr int constexpr_lsb(uint64_t bb) {
|
||||
|
||||
alignas(CacheLineSize) static constexpr struct OffsetIndices {
|
||||
|
||||
#if (USE_SSE41)
|
||||
std::uint8_t offset_indices[256][8];
|
||||
#else
|
||||
std::uint16_t offset_indices[256][8];
|
||||
#endif
|
||||
|
||||
constexpr OffsetIndices() :
|
||||
offset_indices() {
|
||||
@@ -74,56 +69,83 @@ alignas(CacheLineSize) static constexpr struct OffsetIndices {
|
||||
|
||||
} Lookup;
|
||||
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define RESTRICT __restrict__
|
||||
#elif defined(_MSC_VER)
|
||||
#define RESTRICT __restrict
|
||||
#else
|
||||
#define RESTRICT
|
||||
#endif
|
||||
|
||||
// Find indices of nonzero numbers in an int32_t array
|
||||
template<const IndexType InputDimensions>
|
||||
void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) {
|
||||
#if defined(USE_SSSE3)
|
||||
#if defined(USE_AVX512)
|
||||
using vec_t = __m512i;
|
||||
#define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
|
||||
#elif defined(USE_AVX2)
|
||||
using vec_t = __m256i;
|
||||
#if defined(USE_VNNI) && !defined(USE_AVXVNNI)
|
||||
#define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256())
|
||||
#else
|
||||
#define vec_nnz(a) \
|
||||
_mm256_movemask_ps( \
|
||||
_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
|
||||
#endif
|
||||
#elif defined(USE_SSSE3)
|
||||
using vec_t = __m128i;
|
||||
#define vec_nnz(a) \
|
||||
_mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128())))
|
||||
#endif
|
||||
using vec128_t = __m128i;
|
||||
#define vec128_zero _mm_setzero_si128()
|
||||
#define vec128_set_16(a) _mm_set1_epi16(a)
|
||||
#if (USE_SSE41)
|
||||
#define vec128_load(a) _mm_cvtepu8_epi16(_mm_loadl_epi64(a))
|
||||
#else
|
||||
#define vec128_load(a) _mm_load_si128(a)
|
||||
#endif
|
||||
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
|
||||
#define vec128_add(a, b) _mm_add_epi16(a, b)
|
||||
#elif defined(USE_NEON)
|
||||
using vec_t = uint32x4_t;
|
||||
static const std::uint32_t Mask[4] = {1, 2, 4, 8};
|
||||
#define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask)))
|
||||
using vec128_t = uint16x8_t;
|
||||
#define vec128_zero vdupq_n_u16(0)
|
||||
#define vec128_set_16(a) vdupq_n_u16(a)
|
||||
#define vec128_load(a) vld1q_u16(reinterpret_cast<const std::uint16_t*>(a))
|
||||
#define vec128_storeu(a, b) vst1q_u16(reinterpret_cast<std::uint16_t*>(a), b)
|
||||
#define vec128_add(a, b) vaddq_u16(a, b)
|
||||
#endif
|
||||
constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t);
|
||||
// Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8)
|
||||
constexpr IndexType ChunkSize = std::max<IndexType>(InputSimdWidth, 8);
|
||||
constexpr IndexType NumChunks = InputDimensions / ChunkSize;
|
||||
constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth;
|
||||
constexpr IndexType OutputsPerChunk = ChunkSize / 8;
|
||||
void find_nnz(const std::int32_t* RESTRICT input,
|
||||
std::uint16_t* RESTRICT out,
|
||||
IndexType& count_out) {
|
||||
|
||||
const auto inputVector = reinterpret_cast<const vec_t*>(input);
|
||||
#if defined(USE_AVX512ICL)
|
||||
|
||||
constexpr IndexType SimdWidthIn = 16; // 512 bits / 32 bits
|
||||
constexpr IndexType SimdWidthOut = 32; // 512 bits / 16 bits
|
||||
constexpr IndexType NumChunks = InputDimensions / SimdWidthOut;
|
||||
const __m512i increment = _mm512_set1_epi16(SimdWidthOut);
|
||||
__m512i base = _mm512_set_epi16( // Same permute order as _mm512_packus_epi32()
|
||||
31, 30, 29, 28, 15, 14, 13, 12, 27, 26, 25, 24, 11, 10, 9, 8, 23, 22, 21, 20, 7, 6, 5, 4, 19,
|
||||
18, 17, 16, 3, 2, 1, 0);
|
||||
|
||||
IndexType count = 0;
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m512i inputV0 = _mm512_load_si512(input + i * 2 * SimdWidthIn);
|
||||
const __m512i inputV1 = _mm512_load_si512(input + i * 2 * SimdWidthIn + SimdWidthIn);
|
||||
|
||||
// Get a bitmask and gather non zero indices
|
||||
const __m512i inputV01 = _mm512_packus_epi32(inputV0, inputV1);
|
||||
const __mmask32 nnzMask = _mm512_test_epi16_mask(inputV01, inputV01);
|
||||
|
||||
// Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4
|
||||
__m512i nnz = _mm512_maskz_compress_epi16(nnzMask, base);
|
||||
_mm512_storeu_si512(out + count, nnz);
|
||||
|
||||
count += popcount(nnzMask);
|
||||
base = _mm512_add_epi16(base, increment);
|
||||
}
|
||||
count_out = count;
|
||||
|
||||
#elif defined(USE_AVX512)
|
||||
|
||||
constexpr IndexType SimdWidth = 16; // 512 bits / 32 bits
|
||||
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||
const __m512i increment = _mm512_set1_epi32(SimdWidth);
|
||||
__m512i base = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
|
||||
|
||||
IndexType count = 0;
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m512i inputV = _mm512_load_si512(input + i * SimdWidth);
|
||||
|
||||
// Get a bitmask and gather non zero indices
|
||||
const __mmask16 nnzMask = _mm512_test_epi32_mask(inputV, inputV);
|
||||
const __m512i nnzV = _mm512_maskz_compress_epi32(nnzMask, base);
|
||||
_mm512_mask_cvtepi32_storeu_epi16(out + count, 0xFFFF, nnzV);
|
||||
count += popcount(nnzMask);
|
||||
base = _mm512_add_epi32(base, increment);
|
||||
}
|
||||
count_out = count;
|
||||
|
||||
#else
|
||||
|
||||
using namespace SIMD;
|
||||
|
||||
constexpr IndexType InputSimdWidth = sizeof(vec_uint_t) / sizeof(std::int32_t);
|
||||
// Outputs are processed 8 elements at a time, even if the SIMD width is narrower
|
||||
constexpr IndexType ChunkSize = 8;
|
||||
constexpr IndexType NumChunks = InputDimensions / ChunkSize;
|
||||
constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth;
|
||||
|
||||
static_assert(InputsPerChunk > 0 && "SIMD width too wide");
|
||||
|
||||
const auto inputVector = reinterpret_cast<const vec_uint_t*>(input);
|
||||
IndexType count = 0;
|
||||
vec128_t base = vec128_zero;
|
||||
const vec128_t increment = vec128_set_16(8);
|
||||
@@ -133,27 +155,19 @@ void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_ou
|
||||
unsigned nnz = 0;
|
||||
for (IndexType j = 0; j < InputsPerChunk; ++j)
|
||||
{
|
||||
const vec_t inputChunk = inputVector[i * InputsPerChunk + j];
|
||||
const vec_uint_t inputChunk = inputVector[i * InputsPerChunk + j];
|
||||
nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth);
|
||||
}
|
||||
for (IndexType j = 0; j < OutputsPerChunk; ++j)
|
||||
{
|
||||
const unsigned lookup = (nnz >> (j * 8)) & 0xFF;
|
||||
const vec128_t offsets =
|
||||
vec128_load(reinterpret_cast<const vec128_t*>(&Lookup.offset_indices[lookup]));
|
||||
vec128_storeu(reinterpret_cast<vec128_t*>(out + count), vec128_add(base, offsets));
|
||||
count += popcount(lookup);
|
||||
base = vec128_add(base, increment);
|
||||
}
|
||||
const vec128_t offsets =
|
||||
vec128_load(reinterpret_cast<const vec128_t*>(&Lookup.offset_indices[nnz]));
|
||||
vec128_storeu(reinterpret_cast<vec128_t*>(out + count), vec128_add(base, offsets));
|
||||
count += popcount(nnz);
|
||||
base = vec128_add(base, increment);
|
||||
}
|
||||
count_out = count;
|
||||
#endif
|
||||
}
|
||||
#undef vec_nnz
|
||||
#undef vec128_zero
|
||||
#undef vec128_set_16
|
||||
#undef vec128_load
|
||||
#undef vec128_storeu
|
||||
#undef vec128_add
|
||||
|
||||
#endif
|
||||
|
||||
// Sparse input implementation
|
||||
@@ -224,6 +238,15 @@ class AffineTransformSparseInput {
|
||||
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
hash_combine(h, get_raw_data_hash(biases));
|
||||
hash_combine(h, get_raw_data_hash(weights));
|
||||
hash_combine(h, get_hash_value(0));
|
||||
return h;
|
||||
}
|
||||
|
||||
// Forward propagation
|
||||
void propagate(const InputType* input, OutputType* output) const {
|
||||
|
||||
@@ -231,35 +254,44 @@ class AffineTransformSparseInput {
|
||||
#if defined(USE_AVX512)
|
||||
using invec_t = __m512i;
|
||||
using outvec_t = __m512i;
|
||||
#define vec_add_32 _mm512_add_epi32
|
||||
#define vec_set_32 _mm512_set1_epi32
|
||||
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32
|
||||
#elif defined(USE_AVX2)
|
||||
using invec_t = __m256i;
|
||||
using outvec_t = __m256i;
|
||||
#define vec_add_32 _mm256_add_epi32
|
||||
#define vec_set_32 _mm256_set1_epi32
|
||||
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32
|
||||
#elif defined(USE_SSSE3)
|
||||
using invec_t = __m128i;
|
||||
using outvec_t = __m128i;
|
||||
#define vec_set_32 _mm_set1_epi32
|
||||
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32 SIMD::m128_add_dpbusd_epi32
|
||||
#elif defined(USE_NEON_DOTPROD)
|
||||
using invec_t = int8x16_t;
|
||||
using outvec_t = int32x4_t;
|
||||
#define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
|
||||
#define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32 SIMD::dotprod_m128_add_dpbusd_epi32
|
||||
#elif defined(USE_NEON)
|
||||
using invec_t = int8x16_t;
|
||||
using outvec_t = int32x4_t;
|
||||
#define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
|
||||
#define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32
|
||||
#define vec_add_dpbusd_32 SIMD::neon_m128_add_dpbusd_epi32
|
||||
#endif
|
||||
static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType);
|
||||
|
||||
constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType);
|
||||
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;
|
||||
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
|
||||
std::uint16_t nnz[NumChunks];
|
||||
IndexType count;
|
||||
constexpr IndexType NumAccums = OutputDimensions / OutputSimdWidth;
|
||||
// If we're using high-latency dot product instructions, split the accumulators
|
||||
// to create 3 separate dependency chains and merge at the end
|
||||
constexpr IndexType NumRegs =
|
||||
#if defined(USE_VNNI)
|
||||
3 * NumAccums;
|
||||
#else
|
||||
NumAccums;
|
||||
#endif
|
||||
std::uint16_t nnz[NumChunks];
|
||||
IndexType count;
|
||||
|
||||
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
|
||||
|
||||
@@ -268,24 +300,61 @@ class AffineTransformSparseInput {
|
||||
|
||||
const outvec_t* biasvec = reinterpret_cast<const outvec_t*>(biases);
|
||||
outvec_t acc[NumRegs];
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
acc[k] = biasvec[k];
|
||||
|
||||
for (IndexType j = 0; j < count; ++j)
|
||||
const auto* start = nnz;
|
||||
const auto* end = nnz + count;
|
||||
|
||||
// convince GCC to not do weird pointer arithmetic in the following loop
|
||||
const std::int8_t* weights_cp = weights;
|
||||
#if defined(USE_VNNI)
|
||||
for (IndexType k = NumAccums; k < NumRegs; ++k)
|
||||
acc[k] = vec_zero();
|
||||
|
||||
while (start < end - 2)
|
||||
{
|
||||
const auto i = nnz[j];
|
||||
const invec_t in = vec_set_32(input32[i]);
|
||||
const auto col =
|
||||
reinterpret_cast<const invec_t*>(&weights[i * OutputDimensions * ChunkSize]);
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
const std::ptrdiff_t i0 = *start++;
|
||||
const std::ptrdiff_t i1 = *start++;
|
||||
const std::ptrdiff_t i2 = *start++;
|
||||
const invec_t in0 = vec_set_32(input32[i0]);
|
||||
const invec_t in1 = vec_set_32(input32[i1]);
|
||||
const invec_t in2 = vec_set_32(input32[i2]);
|
||||
const auto col0 =
|
||||
reinterpret_cast<const invec_t*>(&weights_cp[i0 * OutputDimensions * ChunkSize]);
|
||||
const auto col1 =
|
||||
reinterpret_cast<const invec_t*>(&weights_cp[i1 * OutputDimensions * ChunkSize]);
|
||||
const auto col2 =
|
||||
reinterpret_cast<const invec_t*>(&weights_cp[i2 * OutputDimensions * ChunkSize]);
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
{
|
||||
vec_add_dpbusd_32(acc[k], in0, col0[k]);
|
||||
vec_add_dpbusd_32(acc[k + NumAccums], in1, col1[k]);
|
||||
vec_add_dpbusd_32(acc[k + 2 * NumAccums], in2, col2[k]);
|
||||
}
|
||||
}
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
acc[k] = vec_add_32(vec_add_32(acc[k], acc[k + NumAccums]), acc[k + 2 * NumAccums]);
|
||||
#endif
|
||||
while (start < end)
|
||||
{
|
||||
const std::ptrdiff_t i = *start++;
|
||||
const invec_t in = vec_set_32(input32[i]);
|
||||
const auto col =
|
||||
reinterpret_cast<const invec_t*>(&weights_cp[i * OutputDimensions * ChunkSize]);
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
vec_add_dpbusd_32(acc[k], in, col[k]);
|
||||
}
|
||||
|
||||
outvec_t* outptr = reinterpret_cast<outvec_t*>(output);
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
outptr[k] = acc[k];
|
||||
|
||||
#undef vec_set_32
|
||||
#undef vec_add_dpbusd_32
|
||||
#ifdef vec_add_32
|
||||
#undef vec_add_32
|
||||
#endif
|
||||
#else
|
||||
// Use dense implementation for the other architectures.
|
||||
affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -58,6 +58,12 @@ class ClippedReLU {
|
||||
// Write network parameters
|
||||
bool write_parameters(std::ostream&) const { return true; }
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
hash_combine(h, get_hash_value(0));
|
||||
return h;
|
||||
}
|
||||
|
||||
// Forward propagation
|
||||
void propagate(const InputType* input, OutputType* output) const {
|
||||
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Stockfish is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef STOCKFISH_SIMD_H_INCLUDED
|
||||
#define STOCKFISH_SIMD_H_INCLUDED
|
||||
|
||||
#if defined(USE_AVX2)
|
||||
#include <immintrin.h>
|
||||
|
||||
#elif defined(USE_SSE41)
|
||||
#include <smmintrin.h>
|
||||
|
||||
#elif defined(USE_SSSE3)
|
||||
#include <tmmintrin.h>
|
||||
|
||||
#elif defined(USE_SSE2)
|
||||
#include <emmintrin.h>
|
||||
|
||||
#elif defined(USE_NEON)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
namespace Stockfish::Simd {
|
||||
|
||||
#if defined(USE_AVX512)
|
||||
|
||||
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
|
||||
return _mm512_reduce_add_epi32(sum) + bias;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) {
|
||||
|
||||
#if defined(USE_VNNI)
|
||||
acc = _mm512_dpbusd_epi32(acc, a, b);
|
||||
#else
|
||||
__m512i product0 = _mm512_maddubs_epi16(a, b);
|
||||
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
|
||||
acc = _mm512_add_epi32(acc, product0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(USE_AVX2)
|
||||
|
||||
[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
|
||||
__m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
|
||||
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
|
||||
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
|
||||
return _mm_cvtsi128_si32(sum128) + bias;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) {
|
||||
|
||||
#if defined(USE_VNNI)
|
||||
acc = _mm256_dpbusd_epi32(acc, a, b);
|
||||
#else
|
||||
__m256i product0 = _mm256_maddubs_epi16(a, b);
|
||||
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
|
||||
acc = _mm256_add_epi32(acc, product0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(USE_SSSE3)
|
||||
|
||||
[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
|
||||
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
|
||||
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
|
||||
return _mm_cvtsi128_si32(sum) + bias;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
|
||||
|
||||
__m128i product0 = _mm_maddubs_epi16(a, b);
|
||||
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
|
||||
acc = _mm_add_epi32(acc, product0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(USE_NEON_DOTPROD)
|
||||
|
||||
[[maybe_unused]] static void
|
||||
dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
|
||||
|
||||
acc = vdotq_s32(acc, a, b);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_NEON)
|
||||
|
||||
[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
|
||||
#if USE_NEON >= 8
|
||||
return vaddvq_s32(s);
|
||||
#else
|
||||
return s[0] + s[1] + s[2] + s[3];
|
||||
#endif
|
||||
}
|
||||
|
||||
[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {
|
||||
return neon_m128_reduce_add_epi32(sum) + bias;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if USE_NEON >= 8
|
||||
[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
|
||||
|
||||
int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b));
|
||||
int16x8_t product1 = vmull_high_s8(a, b);
|
||||
int16x8_t sum = vpaddq_s16(product0, product1);
|
||||
acc = vpadalq_s16(acc, sum);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // STOCKFISH_SIMD_H_INCLUDED
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -58,6 +58,12 @@ class SqrClippedReLU {
|
||||
// Write network parameters
|
||||
bool write_parameters(std::ostream&) const { return true; }
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
hash_combine(h, get_hash_value(0));
|
||||
return h;
|
||||
}
|
||||
|
||||
// Forward propagation
|
||||
void propagate(const InputType* input, OutputType* output) const {
|
||||
|
||||
|
||||
+40
-87
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -21,7 +21,6 @@
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
@@ -30,7 +29,6 @@
|
||||
#include "../incbin/incbin.h"
|
||||
|
||||
#include "../evaluate.h"
|
||||
#include "../memory.h"
|
||||
#include "../misc.h"
|
||||
#include "../position.h"
|
||||
#include "../types.h"
|
||||
@@ -101,7 +99,7 @@ bool read_parameters(std::istream& stream, T& reference) {
|
||||
|
||||
// Write evaluation function parameters
|
||||
template<typename T>
|
||||
bool write_parameters(std::ostream& stream, T& reference) {
|
||||
bool write_parameters(std::ostream& stream, const T& reference) {
|
||||
|
||||
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
|
||||
return reference.write_parameters(stream);
|
||||
@@ -109,43 +107,6 @@ bool write_parameters(std::ostream& stream, T& reference) {
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
Network<Arch, Transformer>::Network(const Network<Arch, Transformer>& other) :
|
||||
evalFile(other.evalFile),
|
||||
embeddedType(other.embeddedType) {
|
||||
|
||||
if (other.featureTransformer)
|
||||
featureTransformer = make_unique_large_page<Transformer>(*other.featureTransformer);
|
||||
|
||||
network = make_unique_aligned<Arch[]>(LayerStacks);
|
||||
|
||||
if (!other.network)
|
||||
return;
|
||||
|
||||
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||
network[i] = other.network[i];
|
||||
}
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
Network<Arch, Transformer>&
|
||||
Network<Arch, Transformer>::operator=(const Network<Arch, Transformer>& other) {
|
||||
evalFile = other.evalFile;
|
||||
embeddedType = other.embeddedType;
|
||||
|
||||
if (other.featureTransformer)
|
||||
featureTransformer = make_unique_large_page<Transformer>(*other.featureTransformer);
|
||||
|
||||
network = make_unique_aligned<Arch[]>(LayerStacks);
|
||||
|
||||
if (!other.network)
|
||||
return *this;
|
||||
|
||||
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||
network[i] = other.network[i];
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
void Network<Arch, Transformer>::load(const std::string& rootDirectory, std::string evalfilePath) {
|
||||
#if defined(DEFAULT_NNUE_DIRECTORY)
|
||||
@@ -160,14 +121,14 @@ void Network<Arch, Transformer>::load(const std::string& rootDirectory, std::str
|
||||
|
||||
for (const auto& directory : dirs)
|
||||
{
|
||||
if (evalFile.current != evalfilePath)
|
||||
if (std::string(evalFile.current) != evalfilePath)
|
||||
{
|
||||
if (directory != "<internal>")
|
||||
{
|
||||
load_user_net(directory, evalfilePath);
|
||||
}
|
||||
|
||||
if (directory == "<internal>" && evalfilePath == evalFile.defaultName)
|
||||
if (directory == "<internal>" && evalfilePath == std::string(evalFile.defaultName))
|
||||
{
|
||||
load_internal();
|
||||
}
|
||||
@@ -185,7 +146,7 @@ bool Network<Arch, Transformer>::save(const std::optional<std::string>& filename
|
||||
actualFilename = filename.value();
|
||||
else
|
||||
{
|
||||
if (evalFile.current != evalFile.defaultName)
|
||||
if (std::string(evalFile.current) != std::string(evalFile.defaultName))
|
||||
{
|
||||
msg = "Failed to export a net. "
|
||||
"A non-embedded net can only be saved if the filename is specified";
|
||||
@@ -211,28 +172,18 @@ template<typename Arch, typename Transformer>
|
||||
NetworkOutput
|
||||
Network<Arch, Transformer>::evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const {
|
||||
// We manually align the arrays on the stack because with gcc < 9.3
|
||||
// overaligning stack variables with alignas() doesn't work correctly.
|
||||
AccumulatorCaches::Cache<FTDimensions>& cache) const {
|
||||
|
||||
constexpr uint64_t alignment = CacheLineSize;
|
||||
|
||||
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
|
||||
TransformedFeatureType
|
||||
transformedFeaturesUnaligned[FeatureTransformer<FTDimensions, nullptr>::BufferSize
|
||||
+ alignment / sizeof(TransformedFeatureType)];
|
||||
|
||||
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
||||
#else
|
||||
alignas(alignment) TransformedFeatureType
|
||||
transformedFeatures[FeatureTransformer<FTDimensions, nullptr>::BufferSize];
|
||||
#endif
|
||||
alignas(alignment)
|
||||
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
|
||||
|
||||
ASSERT_ALIGNED(transformedFeatures, alignment);
|
||||
|
||||
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
||||
const auto psqt =
|
||||
featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
|
||||
featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
|
||||
const auto positional = network[bucket].propagate(transformedFeatures);
|
||||
return {static_cast<Value>(psqt / OutputScale), static_cast<Value>(positional / OutputScale)};
|
||||
}
|
||||
@@ -244,7 +195,7 @@ void Network<Arch, Transformer>::verify(std::string
|
||||
if (evalfilePath.empty())
|
||||
evalfilePath = evalFile.defaultName;
|
||||
|
||||
if (evalFile.current != evalfilePath)
|
||||
if (std::string(evalFile.current) != evalfilePath)
|
||||
{
|
||||
if (f)
|
||||
{
|
||||
@@ -255,7 +206,7 @@ void Network<Arch, Transformer>::verify(std::string
|
||||
"including the directory name, to the network file.";
|
||||
std::string msg4 = "The default net can be downloaded from: "
|
||||
"https://tests.stockfishchess.org/api/nn/"
|
||||
+ evalFile.defaultName;
|
||||
+ std::string(evalFile.defaultName);
|
||||
std::string msg5 = "The engine will be terminated now.";
|
||||
|
||||
std::string msg = "ERROR: " + msg1 + '\n' + "ERROR: " + msg2 + '\n' + "ERROR: " + msg3
|
||||
@@ -269,9 +220,9 @@ void Network<Arch, Transformer>::verify(std::string
|
||||
|
||||
if (f)
|
||||
{
|
||||
size_t size = sizeof(*featureTransformer) + sizeof(Arch) * LayerStacks;
|
||||
size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks;
|
||||
f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024))
|
||||
+ "MiB, (" + std::to_string(featureTransformer->InputDimensions) + ", "
|
||||
+ "MiB, (" + std::to_string(featureTransformer.TotalInputDimensions) + ", "
|
||||
+ std::to_string(network[0].TransformedFeatureDimensions) + ", "
|
||||
+ std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS)
|
||||
+ ", 1))");
|
||||
@@ -283,21 +234,12 @@ template<typename Arch, typename Transformer>
|
||||
NnueEvalTrace
|
||||
Network<Arch, Transformer>::trace_evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const {
|
||||
// We manually align the arrays on the stack because with gcc < 9.3
|
||||
// overaligning stack variables with alignas() doesn't work correctly.
|
||||
AccumulatorCaches::Cache<FTDimensions>& cache) const {
|
||||
|
||||
constexpr uint64_t alignment = CacheLineSize;
|
||||
|
||||
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
|
||||
TransformedFeatureType
|
||||
transformedFeaturesUnaligned[FeatureTransformer<FTDimensions, nullptr>::BufferSize
|
||||
+ alignment / sizeof(TransformedFeatureType)];
|
||||
|
||||
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
||||
#else
|
||||
alignas(alignment) TransformedFeatureType
|
||||
transformedFeatures[FeatureTransformer<FTDimensions, nullptr>::BufferSize];
|
||||
#endif
|
||||
alignas(alignment)
|
||||
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
|
||||
|
||||
ASSERT_ALIGNED(transformedFeatures, alignment);
|
||||
|
||||
@@ -306,7 +248,7 @@ Network<Arch, Transformer>::trace_evaluate(const Position&
|
||||
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)
|
||||
{
|
||||
const auto materialist =
|
||||
featureTransformer->transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
|
||||
featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
|
||||
const auto positional = network[bucket].propagate(transformedFeatures);
|
||||
|
||||
t.psqt[bucket] = static_cast<Value>(materialist / OutputScale);
|
||||
@@ -360,8 +302,7 @@ void Network<Arch, Transformer>::load_internal() {
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
void Network<Arch, Transformer>::initialize() {
|
||||
featureTransformer = make_unique_large_page<Transformer>();
|
||||
network = make_unique_aligned<Arch[]>(LayerStacks);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -385,6 +326,20 @@ std::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
std::size_t Network<Arch, Transformer>::get_content_hash() const {
|
||||
if (!initialized)
|
||||
return 0;
|
||||
|
||||
std::size_t h = 0;
|
||||
hash_combine(h, featureTransformer);
|
||||
for (auto&& layerstack : network)
|
||||
hash_combine(h, layerstack);
|
||||
hash_combine(h, evalFile);
|
||||
hash_combine(h, static_cast<int>(embeddedType));
|
||||
return h;
|
||||
}
|
||||
|
||||
// Read network header
|
||||
template<typename Arch, typename Transformer>
|
||||
bool Network<Arch, Transformer>::read_header(std::istream& stream,
|
||||
@@ -418,13 +373,13 @@ bool Network<Arch, Transformer>::write_header(std::ostream& stream,
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
bool Network<Arch, Transformer>::read_parameters(std::istream& stream,
|
||||
std::string& netDescription) const {
|
||||
std::string& netDescription) {
|
||||
std::uint32_t hashValue;
|
||||
if (!read_header(stream, &hashValue, &netDescription))
|
||||
return false;
|
||||
if (hashValue != Network::hash)
|
||||
return false;
|
||||
if (!Detail::read_parameters(stream, *featureTransformer))
|
||||
if (!Detail::read_parameters(stream, featureTransformer))
|
||||
return false;
|
||||
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||
{
|
||||
@@ -440,7 +395,7 @@ bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
|
||||
const std::string& netDescription) const {
|
||||
if (!write_header(stream, Network::hash, netDescription))
|
||||
return false;
|
||||
if (!Detail::write_parameters(stream, *featureTransformer))
|
||||
if (!Detail::write_parameters(stream, featureTransformer))
|
||||
return false;
|
||||
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||
{
|
||||
@@ -452,12 +407,10 @@ bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
|
||||
|
||||
// Explicit template instantiations
|
||||
|
||||
template class Network<
|
||||
NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>,
|
||||
FeatureTransformer<TransformedFeatureDimensionsBig, &AccumulatorState::accumulatorBig>>;
|
||||
template class Network<NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>,
|
||||
FeatureTransformer<TransformedFeatureDimensionsBig>>;
|
||||
|
||||
template class Network<
|
||||
NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>,
|
||||
FeatureTransformer<TransformedFeatureDimensionsSmall, &AccumulatorState::accumulatorSmall>>;
|
||||
template class Network<NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>,
|
||||
FeatureTransformer<TransformedFeatureDimensionsSmall>>;
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
+44
-21
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -19,19 +19,21 @@
|
||||
#ifndef NETWORK_H_INCLUDED
|
||||
#define NETWORK_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "../memory.h"
|
||||
#include "../misc.h"
|
||||
#include "../types.h"
|
||||
#include "nnue_accumulator.h"
|
||||
#include "nnue_architecture.h"
|
||||
#include "nnue_common.h"
|
||||
#include "nnue_feature_transformer.h"
|
||||
#include "nnue_misc.h"
|
||||
|
||||
@@ -48,6 +50,9 @@ enum class EmbeddedNNUEType {
|
||||
|
||||
using NetworkOutput = std::tuple<Value, Value>;
|
||||
|
||||
// The network must be a trivial type, i.e. the memory must be in-line.
|
||||
// This is required to allow sharing the network via shared memory, as
|
||||
// there is no way to run destructors.
|
||||
template<typename Arch, typename Transformer>
|
||||
class Network {
|
||||
static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions;
|
||||
@@ -57,24 +62,26 @@ class Network {
|
||||
evalFile(file),
|
||||
embeddedType(type) {}
|
||||
|
||||
Network(const Network& other);
|
||||
Network(Network&& other) = default;
|
||||
Network(const Network& other) = default;
|
||||
Network(Network&& other) = default;
|
||||
|
||||
Network& operator=(const Network& other);
|
||||
Network& operator=(Network&& other) = default;
|
||||
Network& operator=(const Network& other) = default;
|
||||
Network& operator=(Network&& other) = default;
|
||||
|
||||
void load(const std::string& rootDirectory, std::string evalfilePath);
|
||||
bool save(const std::optional<std::string>& filename) const;
|
||||
|
||||
std::size_t get_content_hash() const;
|
||||
|
||||
NetworkOutput evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const;
|
||||
AccumulatorCaches::Cache<FTDimensions>& cache) const;
|
||||
|
||||
|
||||
void verify(std::string evalfilePath, const std::function<void(std::string_view)>&) const;
|
||||
NnueEvalTrace trace_evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches::Cache<FTDimensions>* cache) const;
|
||||
AccumulatorCaches::Cache<FTDimensions>& cache) const;
|
||||
|
||||
private:
|
||||
void load_user_net(const std::string&, const std::string&);
|
||||
@@ -88,35 +95,33 @@ class Network {
|
||||
bool read_header(std::istream&, std::uint32_t*, std::string*) const;
|
||||
bool write_header(std::ostream&, std::uint32_t, const std::string&) const;
|
||||
|
||||
bool read_parameters(std::istream&, std::string&) const;
|
||||
bool read_parameters(std::istream&, std::string&);
|
||||
bool write_parameters(std::ostream&, const std::string&) const;
|
||||
|
||||
// Input feature converter
|
||||
LargePagePtr<Transformer> featureTransformer;
|
||||
Transformer featureTransformer;
|
||||
|
||||
// Evaluation function
|
||||
AlignedPtr<Arch[]> network;
|
||||
Arch network[LayerStacks];
|
||||
|
||||
EvalFile evalFile;
|
||||
EmbeddedNNUEType embeddedType;
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
// Hash value of evaluation function structure
|
||||
static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value();
|
||||
|
||||
template<IndexType Size>
|
||||
friend struct AccumulatorCaches::Cache;
|
||||
|
||||
friend class AccumulatorStack;
|
||||
};
|
||||
|
||||
// Definitions of the network types
|
||||
using SmallFeatureTransformer =
|
||||
FeatureTransformer<TransformedFeatureDimensionsSmall, &AccumulatorState::accumulatorSmall>;
|
||||
using SmallFeatureTransformer = FeatureTransformer<TransformedFeatureDimensionsSmall>;
|
||||
using SmallNetworkArchitecture =
|
||||
NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>;
|
||||
|
||||
using BigFeatureTransformer =
|
||||
FeatureTransformer<TransformedFeatureDimensionsBig, &AccumulatorState::accumulatorBig>;
|
||||
using BigFeatureTransformer = FeatureTransformer<TransformedFeatureDimensionsBig>;
|
||||
using BigNetworkArchitecture = NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>;
|
||||
|
||||
using NetworkBig = Network<BigNetworkArchitecture, BigFeatureTransformer>;
|
||||
@@ -124,9 +129,9 @@ using NetworkSmall = Network<SmallNetworkArchitecture, SmallFeatureTransformer>;
|
||||
|
||||
|
||||
struct Networks {
|
||||
Networks(NetworkBig&& nB, NetworkSmall&& nS) :
|
||||
big(std::move(nB)),
|
||||
small(std::move(nS)) {}
|
||||
Networks(EvalFile bigFile, EvalFile smallFile) :
|
||||
big(bigFile, EmbeddedNNUEType::BIG),
|
||||
small(smallFile, EmbeddedNNUEType::SMALL) {}
|
||||
|
||||
NetworkBig big;
|
||||
NetworkSmall small;
|
||||
@@ -135,4 +140,22 @@ struct Networks {
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
template<typename ArchT, typename FeatureTransformerT>
|
||||
struct std::hash<Stockfish::Eval::NNUE::Network<ArchT, FeatureTransformerT>> {
|
||||
std::size_t operator()(
|
||||
const Stockfish::Eval::NNUE::Network<ArchT, FeatureTransformerT>& network) const noexcept {
|
||||
return network.get_content_hash();
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct std::hash<Stockfish::Eval::NNUE::Networks> {
|
||||
std::size_t operator()(const Stockfish::Eval::NNUE::Networks& networks) const noexcept {
|
||||
std::size_t h = 0;
|
||||
Stockfish::hash_combine(h, networks.big);
|
||||
Stockfish::hash_combine(h, networks.small);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
+700
-393
File diff suppressed because it is too large
Load Diff
+90
-67
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -25,7 +25,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
|
||||
#include "../types.h"
|
||||
#include "nnue_architecture.h"
|
||||
@@ -37,27 +37,18 @@ class Position;
|
||||
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
using BiasType = std::int16_t;
|
||||
using PSQTWeightType = std::int32_t;
|
||||
using IndexType = std::uint32_t;
|
||||
|
||||
struct Networks;
|
||||
|
||||
template<IndexType Size>
|
||||
struct alignas(CacheLineSize) Accumulator;
|
||||
|
||||
struct AccumulatorState;
|
||||
|
||||
template<IndexType TransformedFeatureDimensions,
|
||||
Accumulator<TransformedFeatureDimensions> AccumulatorState::*accPtr>
|
||||
template<IndexType TransformedFeatureDimensions>
|
||||
class FeatureTransformer;
|
||||
|
||||
// Class that holds the result of affine transformation of input features
|
||||
template<IndexType Size>
|
||||
struct alignas(CacheLineSize) Accumulator {
|
||||
std::int16_t accumulation[COLOR_NB][Size];
|
||||
std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets];
|
||||
std::array<bool, COLOR_NB> computed;
|
||||
std::array<std::array<std::int16_t, Size>, COLOR_NB> accumulation;
|
||||
std::array<std::array<std::int32_t, PSQTBuckets>, COLOR_NB> psqtAccumulation;
|
||||
std::array<bool, COLOR_NB> computed = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -78,18 +69,17 @@ struct AccumulatorCaches {
|
||||
struct alignas(CacheLineSize) Cache {
|
||||
|
||||
struct alignas(CacheLineSize) Entry {
|
||||
BiasType accumulation[Size];
|
||||
PSQTWeightType psqtAccumulation[PSQTBuckets];
|
||||
Bitboard byColorBB[COLOR_NB];
|
||||
Bitboard byTypeBB[PIECE_TYPE_NB];
|
||||
std::array<BiasType, Size> accumulation;
|
||||
std::array<PSQTWeightType, PSQTBuckets> psqtAccumulation;
|
||||
std::array<Piece, SQUARE_NB> pieces;
|
||||
Bitboard pieceBB;
|
||||
|
||||
// To initialize a refresh entry, we set all its bitboards empty,
|
||||
// so we put the biases in the accumulation, without any weights on top
|
||||
void clear(const BiasType* biases) {
|
||||
|
||||
std::memcpy(accumulation, biases, sizeof(accumulation));
|
||||
std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0,
|
||||
sizeof(Entry) - offsetof(Entry, psqtAccumulation));
|
||||
void clear(const std::array<BiasType, Size>& biases) {
|
||||
accumulation = biases;
|
||||
std::memset(reinterpret_cast<std::byte*>(this) + offsetof(Entry, psqtAccumulation),
|
||||
0, sizeof(Entry) - offsetof(Entry, psqtAccumulation));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -97,7 +87,7 @@ struct AccumulatorCaches {
|
||||
void clear(const Network& network) {
|
||||
for (auto& entries1D : entries)
|
||||
for (auto& entry : entries1D)
|
||||
entry.clear(network.featureTransformer->biases);
|
||||
entry.clear(network.featureTransformer.biases);
|
||||
}
|
||||
|
||||
std::array<Entry, COLOR_NB>& operator[](Square sq) { return entries[sq]; }
|
||||
@@ -116,66 +106,99 @@ struct AccumulatorCaches {
|
||||
};
|
||||
|
||||
|
||||
template<typename FeatureSet>
|
||||
struct AccumulatorState {
|
||||
Accumulator<TransformedFeatureDimensionsBig> accumulatorBig;
|
||||
Accumulator<TransformedFeatureDimensionsSmall> accumulatorSmall;
|
||||
DirtyPiece dirtyPiece;
|
||||
typename FeatureSet::DiffType diff;
|
||||
|
||||
void reset(const DirtyPiece& dp) noexcept;
|
||||
template<IndexType Size>
|
||||
auto& acc() noexcept {
|
||||
static_assert(Size == TransformedFeatureDimensionsBig
|
||||
|| Size == TransformedFeatureDimensionsSmall,
|
||||
"Invalid size for accumulator");
|
||||
|
||||
if constexpr (Size == TransformedFeatureDimensionsBig)
|
||||
return accumulatorBig;
|
||||
else if constexpr (Size == TransformedFeatureDimensionsSmall)
|
||||
return accumulatorSmall;
|
||||
}
|
||||
|
||||
template<IndexType Size>
|
||||
const auto& acc() const noexcept {
|
||||
static_assert(Size == TransformedFeatureDimensionsBig
|
||||
|| Size == TransformedFeatureDimensionsSmall,
|
||||
"Invalid size for accumulator");
|
||||
|
||||
if constexpr (Size == TransformedFeatureDimensionsBig)
|
||||
return accumulatorBig;
|
||||
else if constexpr (Size == TransformedFeatureDimensionsSmall)
|
||||
return accumulatorSmall;
|
||||
}
|
||||
|
||||
void reset(const typename FeatureSet::DiffType& dp) noexcept {
|
||||
diff = dp;
|
||||
accumulatorBig.computed.fill(false);
|
||||
accumulatorSmall.computed.fill(false);
|
||||
}
|
||||
|
||||
typename FeatureSet::DiffType& reset() noexcept {
|
||||
accumulatorBig.computed.fill(false);
|
||||
accumulatorSmall.computed.fill(false);
|
||||
return diff;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class AccumulatorStack {
|
||||
public:
|
||||
AccumulatorStack() :
|
||||
m_accumulators(MAX_PLY + 1),
|
||||
m_current_idx{} {}
|
||||
static constexpr std::size_t MaxSize = MAX_PLY + 1;
|
||||
|
||||
[[nodiscard]] const AccumulatorState& latest() const noexcept;
|
||||
template<typename T>
|
||||
[[nodiscard]] const AccumulatorState<T>& latest() const noexcept;
|
||||
|
||||
void
|
||||
reset(const Position& rootPos, const Networks& networks, AccumulatorCaches& caches) noexcept;
|
||||
void push(const DirtyPiece& dirtyPiece) noexcept;
|
||||
void pop() noexcept;
|
||||
void reset() noexcept;
|
||||
std::pair<DirtyPiece&, DirtyThreats&> push() noexcept;
|
||||
void pop() noexcept;
|
||||
|
||||
template<IndexType Dimensions, Accumulator<Dimensions> AccumulatorState::*accPtr>
|
||||
void evaluate(const Position& pos,
|
||||
const FeatureTransformer<Dimensions, accPtr>& featureTransformer,
|
||||
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
|
||||
template<IndexType Dimensions>
|
||||
void evaluate(const Position& pos,
|
||||
const FeatureTransformer<Dimensions>& featureTransformer,
|
||||
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] AccumulatorState& mut_latest() noexcept;
|
||||
template<typename T>
|
||||
[[nodiscard]] AccumulatorState<T>& mut_latest() noexcept;
|
||||
|
||||
template<Color Perspective,
|
||||
IndexType Dimensions,
|
||||
Accumulator<Dimensions> AccumulatorState::*accPtr>
|
||||
void evaluate_side(const Position& pos,
|
||||
const FeatureTransformer<Dimensions, accPtr>& featureTransformer,
|
||||
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
|
||||
template<typename T>
|
||||
[[nodiscard]] const std::array<AccumulatorState<T>, MaxSize>& accumulators() const noexcept;
|
||||
|
||||
template<Color Perspective,
|
||||
IndexType Dimensions,
|
||||
Accumulator<Dimensions> AccumulatorState::*accPtr>
|
||||
[[nodiscard]] std::size_t find_last_usable_accumulator() const noexcept;
|
||||
template<typename T>
|
||||
[[nodiscard]] std::array<AccumulatorState<T>, MaxSize>& mut_accumulators() noexcept;
|
||||
|
||||
template<Color Perspective,
|
||||
IndexType Dimensions,
|
||||
Accumulator<Dimensions> AccumulatorState::*accPtr>
|
||||
void
|
||||
forward_update_incremental(const Position& pos,
|
||||
const FeatureTransformer<Dimensions, accPtr>& featureTransformer,
|
||||
const std::size_t begin) noexcept;
|
||||
template<typename FeatureSet, IndexType Dimensions>
|
||||
void evaluate_side(Color perspective,
|
||||
const Position& pos,
|
||||
const FeatureTransformer<Dimensions>& featureTransformer,
|
||||
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
|
||||
|
||||
template<Color Perspective,
|
||||
IndexType Dimensions,
|
||||
Accumulator<Dimensions> AccumulatorState::*accPtr>
|
||||
void
|
||||
backward_update_incremental(const Position& pos,
|
||||
const FeatureTransformer<Dimensions, accPtr>& featureTransformer,
|
||||
const std::size_t end) noexcept;
|
||||
template<typename FeatureSet, IndexType Dimensions>
|
||||
[[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept;
|
||||
|
||||
std::vector<AccumulatorState> m_accumulators;
|
||||
std::size_t m_current_idx;
|
||||
template<typename FeatureSet, IndexType Dimensions>
|
||||
void forward_update_incremental(Color perspective,
|
||||
const Position& pos,
|
||||
const FeatureTransformer<Dimensions>& featureTransformer,
|
||||
const std::size_t begin) noexcept;
|
||||
|
||||
template<typename FeatureSet, IndexType Dimensions>
|
||||
void backward_update_incremental(Color perspective,
|
||||
const Position& pos,
|
||||
const FeatureTransformer<Dimensions>& featureTransformer,
|
||||
const std::size_t end) noexcept;
|
||||
|
||||
std::array<AccumulatorState<PSQFeatureSet>, MaxSize> psq_accumulators;
|
||||
std::array<AccumulatorState<ThreatFeatureSet>, MaxSize> threat_accumulators;
|
||||
std::size_t size = 1;
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <iosfwd>
|
||||
|
||||
#include "features/half_ka_v2_hm.h"
|
||||
#include "features/full_threats.h"
|
||||
#include "layers/affine_transform.h"
|
||||
#include "layers/affine_transform_sparse_input.h"
|
||||
#include "layers/clipped_relu.h"
|
||||
@@ -35,10 +36,11 @@
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
// Input features used in evaluation function
|
||||
using FeatureSet = Features::HalfKAv2_hm;
|
||||
using ThreatFeatureSet = Features::FullThreats;
|
||||
using PSQFeatureSet = Features::HalfKAv2_hm;
|
||||
|
||||
// Number of input feature dimensions after conversion
|
||||
constexpr IndexType TransformedFeatureDimensionsBig = 3072;
|
||||
constexpr IndexType TransformedFeatureDimensionsBig = 1024;
|
||||
constexpr int L2Big = 15;
|
||||
constexpr int L3Big = 32;
|
||||
|
||||
@@ -49,6 +51,12 @@ constexpr int L3Small = 32;
|
||||
constexpr IndexType PSQTBuckets = 8;
|
||||
constexpr IndexType LayerStacks = 8;
|
||||
|
||||
// If vector instructions are enabled, we update and refresh the
|
||||
// accumulator tile by tile such that each tile fits in the CPU's
|
||||
// vector registers.
|
||||
static_assert(PSQTBuckets % 8 == 0,
|
||||
"Per feature PSQT values cannot be processed at granularity lower than 8 at a time.");
|
||||
|
||||
template<IndexType L1, int L2, int L3>
|
||||
struct NetworkArchitecture {
|
||||
static constexpr IndexType TransformedFeatureDimensions = L1;
|
||||
@@ -91,7 +99,7 @@ struct NetworkArchitecture {
|
||||
&& fc_2.write_parameters(stream);
|
||||
}
|
||||
|
||||
std::int32_t propagate(const TransformedFeatureType* transformedFeatures) {
|
||||
std::int32_t propagate(const TransformedFeatureType* transformedFeatures) const {
|
||||
struct alignas(CacheLineSize) Buffer {
|
||||
alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out;
|
||||
alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType
|
||||
@@ -130,8 +138,28 @@ struct NetworkArchitecture {
|
||||
|
||||
return outputValue;
|
||||
}
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
hash_combine(h, fc_0.get_content_hash());
|
||||
hash_combine(h, ac_sqr_0.get_content_hash());
|
||||
hash_combine(h, ac_0.get_content_hash());
|
||||
hash_combine(h, fc_1.get_content_hash());
|
||||
hash_combine(h, ac_1.get_content_hash());
|
||||
hash_combine(h, fc_2.get_content_hash());
|
||||
hash_combine(h, get_hash_value());
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
template<Stockfish::Eval::NNUE::IndexType L1, int L2, int L3>
|
||||
struct std::hash<Stockfish::Eval::NNUE::NetworkArchitecture<L1, L2, L3>> {
|
||||
std::size_t
|
||||
operator()(const Stockfish::Eval::NNUE::NetworkArchitecture<L1, L2, L3>& arch) const noexcept {
|
||||
return arch.get_content_hash();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
||||
|
||||
+53
-49
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -48,6 +48,12 @@
|
||||
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
using BiasType = std::int16_t;
|
||||
using ThreatWeightType = std::int8_t;
|
||||
using WeightType = std::int16_t;
|
||||
using PSQTWeightType = std::int32_t;
|
||||
using IndexType = std::uint32_t;
|
||||
|
||||
// Version of the evaluation file
|
||||
constexpr std::uint32_t Version = 0x7AF32F20u;
|
||||
|
||||
@@ -76,7 +82,6 @@ constexpr std::size_t MaxSimdWidth = 32;
|
||||
|
||||
// Type of input feature after conversion
|
||||
using TransformedFeatureType = std::uint8_t;
|
||||
using IndexType = std::uint32_t;
|
||||
|
||||
// Round n up to be a multiple of base
|
||||
template<typename IntType>
|
||||
@@ -164,52 +169,56 @@ inline void write_little_endian(std::ostream& stream, const IntType* values, std
|
||||
write_little_endian<IntType>(stream, values[i]);
|
||||
}
|
||||
|
||||
|
||||
// Read N signed integers from the stream s, putting them in the array out.
|
||||
// The stream is assumed to be compressed using the signed LEB128 format.
|
||||
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
|
||||
template<typename IntType>
|
||||
inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) {
|
||||
template<typename BufType, typename IntType, std::size_t Count>
|
||||
inline void read_leb_128_detail(std::istream& stream,
|
||||
std::array<IntType, Count>& out,
|
||||
std::uint32_t& bytes_left,
|
||||
BufType& buf,
|
||||
std::uint32_t& buf_pos) {
|
||||
|
||||
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
|
||||
static_assert(sizeof(IntType) <= 4, "Not implemented for types larger than 32 bit");
|
||||
|
||||
IntType result = 0;
|
||||
size_t shift = 0, i = 0;
|
||||
while (i < Count)
|
||||
{
|
||||
if (buf_pos == buf.size())
|
||||
{
|
||||
stream.read(reinterpret_cast<char*>(buf.data()),
|
||||
std::min(std::size_t(bytes_left), buf.size()));
|
||||
buf_pos = 0;
|
||||
}
|
||||
|
||||
std::uint8_t byte = buf[buf_pos++];
|
||||
--bytes_left;
|
||||
result |= (byte & 0x7f) << (shift % 32);
|
||||
shift += 7;
|
||||
|
||||
if ((byte & 0x80) == 0)
|
||||
{
|
||||
out[i++] = (shift >= 32 || (byte & 0x40) == 0) ? result : result | ~((1 << shift) - 1);
|
||||
result = 0;
|
||||
shift = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Arrays>
|
||||
inline void read_leb_128(std::istream& stream, Arrays&... outs) {
|
||||
// Check the presence of our LEB128 magic string
|
||||
char leb128MagicString[Leb128MagicStringSize];
|
||||
stream.read(leb128MagicString, Leb128MagicStringSize);
|
||||
assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);
|
||||
|
||||
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
|
||||
auto bytes_left = read_little_endian<std::uint32_t>(stream);
|
||||
std::array<std::uint8_t, 8192> buf;
|
||||
std::uint32_t buf_pos = buf.size();
|
||||
|
||||
const std::uint32_t BUF_SIZE = 4096;
|
||||
std::uint8_t buf[BUF_SIZE];
|
||||
|
||||
auto bytes_left = read_little_endian<std::uint32_t>(stream);
|
||||
|
||||
std::uint32_t buf_pos = BUF_SIZE;
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
IntType result = 0;
|
||||
size_t shift = 0;
|
||||
do
|
||||
{
|
||||
if (buf_pos == BUF_SIZE)
|
||||
{
|
||||
stream.read(reinterpret_cast<char*>(buf), std::min(bytes_left, BUF_SIZE));
|
||||
buf_pos = 0;
|
||||
}
|
||||
|
||||
std::uint8_t byte = buf[buf_pos++];
|
||||
--bytes_left;
|
||||
result |= (byte & 0x7f) << shift;
|
||||
shift += 7;
|
||||
|
||||
if ((byte & 0x80) == 0)
|
||||
{
|
||||
out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0)
|
||||
? result
|
||||
: result | ~((1 << shift) - 1);
|
||||
break;
|
||||
}
|
||||
} while (shift < sizeof(IntType) * 8);
|
||||
}
|
||||
(read_leb_128_detail(stream, outs, bytes_left, buf, buf_pos), ...);
|
||||
|
||||
assert(bytes_left == 0);
|
||||
}
|
||||
@@ -219,8 +228,8 @@ inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count)
|
||||
// This takes N integers from array values, compresses them with
|
||||
// the LEB128 algorithm and writes the result on the stream s.
|
||||
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
|
||||
template<typename IntType>
|
||||
inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) {
|
||||
template<typename IntType, std::size_t Count>
|
||||
inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>& values) {
|
||||
|
||||
// Write our LEB128 magic string
|
||||
stream.write(Leb128MagicString, Leb128MagicStringSize);
|
||||
@@ -228,7 +237,7 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size
|
||||
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
|
||||
|
||||
std::uint32_t byte_count = 0;
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
for (std::size_t i = 0; i < Count; ++i)
|
||||
{
|
||||
IntType value = values[i];
|
||||
std::uint8_t byte;
|
||||
@@ -254,13 +263,13 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size
|
||||
}
|
||||
};
|
||||
|
||||
auto write = [&](std::uint8_t byte) {
|
||||
buf[buf_pos++] = byte;
|
||||
auto write = [&](std::uint8_t b) {
|
||||
buf[buf_pos++] = b;
|
||||
if (buf_pos == BUF_SIZE)
|
||||
flush();
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
for (std::size_t i = 0; i < Count; ++i)
|
||||
{
|
||||
IntType value = values[i];
|
||||
while (true)
|
||||
@@ -279,11 +288,6 @@ inline void write_leb_128(std::ostream& stream, const IntType* values, std::size
|
||||
flush();
|
||||
}
|
||||
|
||||
enum IncUpdateDirection {
|
||||
FORWARD,
|
||||
BACKWARDS
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
#endif // #ifndef NNUE_COMMON_H_INCLUDED
|
||||
|
||||
+179
-218
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -25,124 +25,17 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iosfwd>
|
||||
#include <iterator>
|
||||
|
||||
#include "../position.h"
|
||||
#include "../types.h"
|
||||
#include "nnue_accumulator.h"
|
||||
#include "nnue_architecture.h"
|
||||
#include "nnue_common.h"
|
||||
#include "simd.h"
|
||||
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
using BiasType = std::int16_t;
|
||||
using WeightType = std::int16_t;
|
||||
using PSQTWeightType = std::int32_t;
|
||||
|
||||
// If vector instructions are enabled, we update and refresh the
|
||||
// accumulator tile by tile such that each tile fits in the CPU's
|
||||
// vector registers.
|
||||
#define VECTOR
|
||||
|
||||
static_assert(PSQTBuckets % 8 == 0,
|
||||
"Per feature PSQT values cannot be processed at granularity lower than 8 at a time.");
|
||||
|
||||
#ifdef USE_AVX512
|
||||
using vec_t = __m512i;
|
||||
using psqt_vec_t = __m256i;
|
||||
#define vec_load(a) _mm512_load_si512(a)
|
||||
#define vec_store(a, b) _mm512_store_si512(a, b)
|
||||
#define vec_add_16(a, b) _mm512_add_epi16(a, b)
|
||||
#define vec_sub_16(a, b) _mm512_sub_epi16(a, b)
|
||||
#define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b)
|
||||
#define vec_zero() _mm512_setzero_epi32()
|
||||
#define vec_set_16(a) _mm512_set1_epi16(a)
|
||||
#define vec_max_16(a, b) _mm512_max_epi16(a, b)
|
||||
#define vec_min_16(a, b) _mm512_min_epi16(a, b)
|
||||
#define vec_slli_16(a, b) _mm512_slli_epi16(a, b)
|
||||
// Inverse permuted at load time
|
||||
#define vec_packus_16(a, b) _mm512_packus_epi16(a, b)
|
||||
#define vec_load_psqt(a) _mm256_load_si256(a)
|
||||
#define vec_store_psqt(a, b) _mm256_store_si256(a, b)
|
||||
#define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)
|
||||
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
|
||||
#define vec_zero_psqt() _mm256_setzero_si256()
|
||||
#define NumRegistersSIMD 16
|
||||
#define MaxChunkSize 64
|
||||
|
||||
#elif USE_AVX2
|
||||
using vec_t = __m256i;
|
||||
using psqt_vec_t = __m256i;
|
||||
#define vec_load(a) _mm256_load_si256(a)
|
||||
#define vec_store(a, b) _mm256_store_si256(a, b)
|
||||
#define vec_add_16(a, b) _mm256_add_epi16(a, b)
|
||||
#define vec_sub_16(a, b) _mm256_sub_epi16(a, b)
|
||||
#define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b)
|
||||
#define vec_zero() _mm256_setzero_si256()
|
||||
#define vec_set_16(a) _mm256_set1_epi16(a)
|
||||
#define vec_max_16(a, b) _mm256_max_epi16(a, b)
|
||||
#define vec_min_16(a, b) _mm256_min_epi16(a, b)
|
||||
#define vec_slli_16(a, b) _mm256_slli_epi16(a, b)
|
||||
// Inverse permuted at load time
|
||||
#define vec_packus_16(a, b) _mm256_packus_epi16(a, b)
|
||||
#define vec_load_psqt(a) _mm256_load_si256(a)
|
||||
#define vec_store_psqt(a, b) _mm256_store_si256(a, b)
|
||||
#define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)
|
||||
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
|
||||
#define vec_zero_psqt() _mm256_setzero_si256()
|
||||
#define NumRegistersSIMD 16
|
||||
#define MaxChunkSize 32
|
||||
|
||||
#elif USE_SSE2
|
||||
using vec_t = __m128i;
|
||||
using psqt_vec_t = __m128i;
|
||||
#define vec_load(a) (*(a))
|
||||
#define vec_store(a, b) *(a) = (b)
|
||||
#define vec_add_16(a, b) _mm_add_epi16(a, b)
|
||||
#define vec_sub_16(a, b) _mm_sub_epi16(a, b)
|
||||
#define vec_mulhi_16(a, b) _mm_mulhi_epi16(a, b)
|
||||
#define vec_zero() _mm_setzero_si128()
|
||||
#define vec_set_16(a) _mm_set1_epi16(a)
|
||||
#define vec_max_16(a, b) _mm_max_epi16(a, b)
|
||||
#define vec_min_16(a, b) _mm_min_epi16(a, b)
|
||||
#define vec_slli_16(a, b) _mm_slli_epi16(a, b)
|
||||
#define vec_packus_16(a, b) _mm_packus_epi16(a, b)
|
||||
#define vec_load_psqt(a) (*(a))
|
||||
#define vec_store_psqt(a, b) *(a) = (b)
|
||||
#define vec_add_psqt_32(a, b) _mm_add_epi32(a, b)
|
||||
#define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b)
|
||||
#define vec_zero_psqt() _mm_setzero_si128()
|
||||
#define NumRegistersSIMD (Is64Bit ? 16 : 8)
|
||||
#define MaxChunkSize 16
|
||||
|
||||
#elif USE_NEON
|
||||
using vec_t = int16x8_t;
|
||||
using psqt_vec_t = int32x4_t;
|
||||
#define vec_load(a) (*(a))
|
||||
#define vec_store(a, b) *(a) = (b)
|
||||
#define vec_add_16(a, b) vaddq_s16(a, b)
|
||||
#define vec_sub_16(a, b) vsubq_s16(a, b)
|
||||
#define vec_mulhi_16(a, b) vqdmulhq_s16(a, b)
|
||||
#define vec_zero() \
|
||||
vec_t { 0 }
|
||||
#define vec_set_16(a) vdupq_n_s16(a)
|
||||
#define vec_max_16(a, b) vmaxq_s16(a, b)
|
||||
#define vec_min_16(a, b) vminq_s16(a, b)
|
||||
#define vec_slli_16(a, b) vshlq_s16(a, vec_set_16(b))
|
||||
#define vec_packus_16(a, b) reinterpret_cast<vec_t>(vcombine_u8(vqmovun_s16(a), vqmovun_s16(b)))
|
||||
#define vec_load_psqt(a) (*(a))
|
||||
#define vec_store_psqt(a, b) *(a) = (b)
|
||||
#define vec_add_psqt_32(a, b) vaddq_s32(a, b)
|
||||
#define vec_sub_psqt_32(a, b) vsubq_s32(a, b)
|
||||
#define vec_zero_psqt() \
|
||||
psqt_vec_t { 0 }
|
||||
#define NumRegistersSIMD 16
|
||||
#define MaxChunkSize 16
|
||||
|
||||
#else
|
||||
#undef VECTOR
|
||||
|
||||
#endif
|
||||
|
||||
// Returns the inverse of a permutation
|
||||
template<std::size_t Len>
|
||||
constexpr std::array<std::size_t, Len>
|
||||
@@ -156,7 +49,7 @@ invert_permutation(const std::array<std::size_t, Len>& order) {
|
||||
// Divide a byte region of size TotalSize to chunks of size
|
||||
// BlockSize, and permute the blocks by a given order
|
||||
template<std::size_t BlockSize, typename T, std::size_t N, std::size_t OrderSize>
|
||||
void permute(T (&data)[N], const std::array<std::size_t, OrderSize>& order) {
|
||||
void permute(std::array<T, N>& data, const std::array<std::size_t, OrderSize>& order) {
|
||||
constexpr std::size_t TotalSize = N * sizeof(T);
|
||||
|
||||
static_assert(TotalSize % (BlockSize * OrderSize) == 0,
|
||||
@@ -166,7 +59,7 @@ void permute(T (&data)[N], const std::array<std::size_t, OrderSize>& order) {
|
||||
|
||||
std::array<std::byte, ProcessChunkSize> buffer{};
|
||||
|
||||
std::byte* const bytes = reinterpret_cast<std::byte*>(data);
|
||||
std::byte* const bytes = reinterpret_cast<std::byte*>(data.data());
|
||||
|
||||
for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize)
|
||||
{
|
||||
@@ -184,66 +77,11 @@ void permute(T (&data)[N], const std::array<std::size_t, OrderSize>& order) {
|
||||
}
|
||||
}
|
||||
|
||||
// Compute optimal SIMD register count for feature transformer accumulation.
|
||||
template<IndexType TransformedFeatureWidth, IndexType HalfDimensions>
|
||||
class SIMDTiling {
|
||||
#ifdef VECTOR
|
||||
// We use __m* types as template arguments, which causes GCC to emit warnings
|
||||
// about losing some attribute information. This is irrelevant to us as we
|
||||
// only take their size, so the following pragma are harmless.
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
||||
#endif
|
||||
|
||||
template<typename SIMDRegisterType, typename LaneType, int NumLanes, int MaxRegisters>
|
||||
static constexpr int BestRegisterCount() {
|
||||
constexpr std::size_t RegisterSize = sizeof(SIMDRegisterType);
|
||||
constexpr std::size_t LaneSize = sizeof(LaneType);
|
||||
|
||||
static_assert(RegisterSize >= LaneSize);
|
||||
static_assert(MaxRegisters <= NumRegistersSIMD);
|
||||
static_assert(MaxRegisters > 0);
|
||||
static_assert(NumRegistersSIMD > 0);
|
||||
static_assert(RegisterSize % LaneSize == 0);
|
||||
static_assert((NumLanes * LaneSize) % RegisterSize == 0);
|
||||
|
||||
const int ideal = (NumLanes * LaneSize) / RegisterSize;
|
||||
if (ideal <= MaxRegisters)
|
||||
return ideal;
|
||||
|
||||
// Look for the largest divisor of the ideal register count that is smaller than MaxRegisters
|
||||
for (int divisor = MaxRegisters; divisor > 1; --divisor)
|
||||
if (ideal % divisor == 0)
|
||||
return divisor;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
public:
|
||||
static constexpr int NumRegs =
|
||||
BestRegisterCount<vec_t, WeightType, TransformedFeatureWidth, NumRegistersSIMD>();
|
||||
static constexpr int NumPsqtRegs =
|
||||
BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
|
||||
|
||||
static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
|
||||
static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;
|
||||
|
||||
static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions");
|
||||
static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets");
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
// Input feature converter
|
||||
template<IndexType TransformedFeatureDimensions,
|
||||
Accumulator<TransformedFeatureDimensions> AccumulatorState::*accPtr>
|
||||
template<IndexType TransformedFeatureDimensions>
|
||||
class FeatureTransformer {
|
||||
|
||||
static constexpr bool UseThreats =
|
||||
(TransformedFeatureDimensions == TransformedFeatureDimensionsBig);
|
||||
// Number of output dimensions for one side
|
||||
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
|
||||
|
||||
@@ -252,7 +90,10 @@ class FeatureTransformer {
|
||||
using OutputType = TransformedFeatureType;
|
||||
|
||||
// Number of input/output dimensions
|
||||
static constexpr IndexType InputDimensions = FeatureSet::Dimensions;
|
||||
static constexpr IndexType InputDimensions = PSQFeatureSet::Dimensions;
|
||||
static constexpr IndexType ThreatInputDimensions = ThreatFeatureSet::Dimensions;
|
||||
static constexpr IndexType TotalInputDimensions =
|
||||
InputDimensions + (UseThreats ? ThreatInputDimensions : 0);
|
||||
static constexpr IndexType OutputDimensions = HalfDimensions;
|
||||
|
||||
// Size of forward propagation buffer
|
||||
@@ -283,75 +124,138 @@ class FeatureTransformer {
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t get_hash_value() {
|
||||
return FeatureSet::HashValue ^ (OutputDimensions * 2);
|
||||
return (UseThreats ? ThreatFeatureSet::HashValue : PSQFeatureSet::HashValue)
|
||||
^ (OutputDimensions * 2);
|
||||
}
|
||||
|
||||
void permute_weights() {
|
||||
permute<16>(biases, PackusEpi16Order);
|
||||
permute<16>(weights, PackusEpi16Order);
|
||||
|
||||
if constexpr (UseThreats)
|
||||
permute<8>(threatWeights, PackusEpi16Order);
|
||||
}
|
||||
|
||||
void unpermute_weights() {
|
||||
permute<16>(biases, InversePackusEpi16Order);
|
||||
permute<16>(weights, InversePackusEpi16Order);
|
||||
|
||||
if constexpr (UseThreats)
|
||||
permute<8>(threatWeights, InversePackusEpi16Order);
|
||||
}
|
||||
|
||||
inline void scale_weights(bool read) {
|
||||
for (IndexType j = 0; j < InputDimensions; ++j)
|
||||
{
|
||||
WeightType* w = &weights[j * HalfDimensions];
|
||||
for (IndexType i = 0; i < HalfDimensions; ++i)
|
||||
w[i] = read ? w[i] * 2 : w[i] / 2;
|
||||
}
|
||||
|
||||
for (IndexType i = 0; i < HalfDimensions; ++i)
|
||||
biases[i] = read ? biases[i] * 2 : biases[i] / 2;
|
||||
for (auto& w : weights)
|
||||
w = read ? w * 2 : w / 2;
|
||||
for (auto& b : biases)
|
||||
b = read ? b * 2 : b / 2;
|
||||
}
|
||||
|
||||
// Read network parameters
|
||||
bool read_parameters(std::istream& stream) {
|
||||
read_leb_128(stream, biases);
|
||||
|
||||
read_leb_128<BiasType>(stream, biases, HalfDimensions);
|
||||
read_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
|
||||
read_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
read_little_endian<ThreatWeightType>(stream, threatWeights.data(),
|
||||
ThreatInputDimensions * HalfDimensions);
|
||||
read_leb_128(stream, weights);
|
||||
|
||||
read_leb_128(stream, threatPsqtWeights, psqtWeights);
|
||||
}
|
||||
else
|
||||
{
|
||||
read_leb_128(stream, weights);
|
||||
read_leb_128(stream, psqtWeights);
|
||||
}
|
||||
|
||||
permute_weights();
|
||||
scale_weights(true);
|
||||
|
||||
if constexpr (!UseThreats)
|
||||
scale_weights(true);
|
||||
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
// Write network parameters
|
||||
bool write_parameters(std::ostream& stream) {
|
||||
bool write_parameters(std::ostream& stream) const {
|
||||
std::unique_ptr<FeatureTransformer> copy = std::make_unique<FeatureTransformer>(*this);
|
||||
|
||||
unpermute_weights();
|
||||
scale_weights(false);
|
||||
copy->unpermute_weights();
|
||||
|
||||
write_leb_128<BiasType>(stream, biases, HalfDimensions);
|
||||
write_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
|
||||
write_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
|
||||
if constexpr (!UseThreats)
|
||||
copy->scale_weights(false);
|
||||
|
||||
write_leb_128<BiasType>(stream, copy->biases);
|
||||
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
write_little_endian<ThreatWeightType>(stream, copy->threatWeights.data(),
|
||||
ThreatInputDimensions * HalfDimensions);
|
||||
write_leb_128<WeightType>(stream, copy->weights);
|
||||
|
||||
auto combinedPsqtWeights =
|
||||
std::make_unique<std::array<PSQTWeightType, TotalInputDimensions * PSQTBuckets>>();
|
||||
|
||||
std::copy(std::begin(copy->threatPsqtWeights),
|
||||
std::begin(copy->threatPsqtWeights) + ThreatInputDimensions * PSQTBuckets,
|
||||
combinedPsqtWeights->begin());
|
||||
|
||||
std::copy(std::begin(copy->psqtWeights),
|
||||
std::begin(copy->psqtWeights) + InputDimensions * PSQTBuckets,
|
||||
combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets);
|
||||
|
||||
write_leb_128<PSQTWeightType>(stream, *combinedPsqtWeights);
|
||||
}
|
||||
else
|
||||
{
|
||||
write_leb_128<WeightType>(stream, copy->weights);
|
||||
write_leb_128<PSQTWeightType>(stream, copy->psqtWeights);
|
||||
}
|
||||
|
||||
permute_weights();
|
||||
scale_weights(true);
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
hash_combine(h, get_raw_data_hash(biases));
|
||||
hash_combine(h, get_raw_data_hash(weights));
|
||||
hash_combine(h, get_raw_data_hash(psqtWeights));
|
||||
hash_combine(h, get_hash_value());
|
||||
return h;
|
||||
}
|
||||
|
||||
// Convert input features
|
||||
std::int32_t transform(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches::Cache<HalfDimensions>* cache,
|
||||
AccumulatorCaches::Cache<HalfDimensions>& cache,
|
||||
OutputType* output,
|
||||
int bucket) const {
|
||||
|
||||
accumulatorStack.evaluate(pos, *this, *cache);
|
||||
const auto& accumulatorState = accumulatorStack.latest();
|
||||
using namespace SIMD;
|
||||
accumulatorStack.evaluate(pos, *this, cache);
|
||||
const auto& accumulatorState = accumulatorStack.latest<PSQFeatureSet>();
|
||||
const auto& threatAccumulatorState = accumulatorStack.latest<ThreatFeatureSet>();
|
||||
|
||||
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
|
||||
const auto& psqtAccumulation = (accumulatorState.*accPtr).psqtAccumulation;
|
||||
const auto psqt =
|
||||
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket])
|
||||
/ 2;
|
||||
const auto& psqtAccumulation = (accumulatorState.acc<HalfDimensions>()).psqtAccumulation;
|
||||
auto psqt =
|
||||
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]);
|
||||
|
||||
const auto& accumulation = (accumulatorState.*accPtr).accumulation;
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
const auto& threatPsqtAccumulation =
|
||||
(threatAccumulatorState.acc<HalfDimensions>()).psqtAccumulation;
|
||||
psqt = (psqt + threatPsqtAccumulation[perspectives[0]][bucket]
|
||||
- threatPsqtAccumulation[perspectives[1]][bucket])
|
||||
/ 2;
|
||||
}
|
||||
else
|
||||
psqt /= 2;
|
||||
|
||||
const auto& accumulation = (accumulatorState.acc<HalfDimensions>()).accumulation;
|
||||
const auto& threatAccumulation =
|
||||
(threatAccumulatorState.acc<HalfDimensions>()).accumulation;
|
||||
|
||||
for (IndexType p = 0; p < 2; ++p)
|
||||
{
|
||||
@@ -364,7 +268,7 @@ class FeatureTransformer {
|
||||
constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize;
|
||||
|
||||
const vec_t Zero = vec_zero();
|
||||
const vec_t One = vec_set_16(127 * 2);
|
||||
const vec_t One = vec_set_16(UseThreats ? 255 : 127 * 2);
|
||||
|
||||
const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
|
||||
const vec_t* in1 =
|
||||
@@ -430,20 +334,48 @@ class FeatureTransformer {
|
||||
#else
|
||||
6;
|
||||
#endif
|
||||
|
||||
for (IndexType j = 0; j < NumOutputChunks; ++j)
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
const vec_t sum0a =
|
||||
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift);
|
||||
const vec_t sum0b =
|
||||
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift);
|
||||
const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One);
|
||||
const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One);
|
||||
const vec_t* tin0 =
|
||||
reinterpret_cast<const vec_t*>(&(threatAccumulation[perspectives[p]][0]));
|
||||
const vec_t* tin1 = reinterpret_cast<const vec_t*>(
|
||||
&(threatAccumulation[perspectives[p]][HalfDimensions / 2]));
|
||||
for (IndexType j = 0; j < NumOutputChunks; ++j)
|
||||
{
|
||||
const vec_t acc0a = vec_add_16(in0[j * 2 + 0], tin0[j * 2 + 0]);
|
||||
const vec_t acc0b = vec_add_16(in0[j * 2 + 1], tin0[j * 2 + 1]);
|
||||
const vec_t acc1a = vec_add_16(in1[j * 2 + 0], tin1[j * 2 + 0]);
|
||||
const vec_t acc1b = vec_add_16(in1[j * 2 + 1], tin1[j * 2 + 1]);
|
||||
|
||||
const vec_t pa = vec_mulhi_16(sum0a, sum1a);
|
||||
const vec_t pb = vec_mulhi_16(sum0b, sum1b);
|
||||
const vec_t sum0a =
|
||||
vec_slli_16(vec_max_16(vec_min_16(acc0a, One), Zero), shift);
|
||||
const vec_t sum0b =
|
||||
vec_slli_16(vec_max_16(vec_min_16(acc0b, One), Zero), shift);
|
||||
const vec_t sum1a = vec_min_16(acc1a, One);
|
||||
const vec_t sum1b = vec_min_16(acc1b, One);
|
||||
|
||||
out[j] = vec_packus_16(pa, pb);
|
||||
const vec_t pa = vec_mulhi_16(sum0a, sum1a);
|
||||
const vec_t pb = vec_mulhi_16(sum0b, sum1b);
|
||||
|
||||
out[j] = vec_packus_16(pa, pb);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (IndexType j = 0; j < NumOutputChunks; ++j)
|
||||
{
|
||||
const vec_t sum0a =
|
||||
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift);
|
||||
const vec_t sum0b =
|
||||
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift);
|
||||
const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One);
|
||||
const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One);
|
||||
|
||||
const vec_t pa = vec_mulhi_16(sum0a, sum1a);
|
||||
const vec_t pb = vec_mulhi_16(sum0b, sum1b);
|
||||
|
||||
out[j] = vec_packus_16(pa, pb);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
@@ -453,8 +385,21 @@ class FeatureTransformer {
|
||||
BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];
|
||||
BiasType sum1 =
|
||||
accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
|
||||
sum0 = std::clamp<BiasType>(sum0, 0, 127 * 2);
|
||||
sum1 = std::clamp<BiasType>(sum1, 0, 127 * 2);
|
||||
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
BiasType sum0t = threatAccumulation[static_cast<int>(perspectives[p])][j + 0];
|
||||
BiasType sum1t =
|
||||
threatAccumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
|
||||
sum0 = std::clamp<BiasType>(sum0 + sum0t, 0, 255);
|
||||
sum1 = std::clamp<BiasType>(sum1 + sum1t, 0, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
sum0 = std::clamp<BiasType>(sum0, 0, 127 * 2);
|
||||
sum1 = std::clamp<BiasType>(sum1, 0, 127 * 2);
|
||||
}
|
||||
|
||||
output[offset + j] = static_cast<OutputType>(unsigned(sum0 * sum1) / 512);
|
||||
}
|
||||
|
||||
@@ -464,11 +409,27 @@ class FeatureTransformer {
|
||||
return psqt;
|
||||
} // end of function transform()
|
||||
|
||||
alignas(CacheLineSize) BiasType biases[HalfDimensions];
|
||||
alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions];
|
||||
alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets];
|
||||
alignas(CacheLineSize) std::array<BiasType, HalfDimensions> biases;
|
||||
alignas(CacheLineSize) std::array<WeightType, HalfDimensions * InputDimensions> weights;
|
||||
alignas(CacheLineSize)
|
||||
std::array<ThreatWeightType,
|
||||
UseThreats ? HalfDimensions * ThreatInputDimensions : 0> threatWeights;
|
||||
alignas(CacheLineSize) std::array<PSQTWeightType, InputDimensions * PSQTBuckets> psqtWeights;
|
||||
alignas(CacheLineSize)
|
||||
std::array<PSQTWeightType,
|
||||
UseThreats ? ThreatInputDimensions * PSQTBuckets : 0> threatPsqtWeights;
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
|
||||
template<Stockfish::Eval::NNUE::IndexType TransformedFeatureDimensions>
|
||||
struct std::hash<Stockfish::Eval::NNUE::FeatureTransformer<TransformedFeatureDimensions>> {
|
||||
std::size_t
|
||||
operator()(const Stockfish::Eval::NNUE::FeatureTransformer<TransformedFeatureDimensions>& ft)
|
||||
const noexcept {
|
||||
return ft.get_content_hash();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // #ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -120,12 +120,11 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat
|
||||
format_cp_compact(value, &board[y + 2][x + 2], pos);
|
||||
};
|
||||
|
||||
AccumulatorStack accumulators;
|
||||
accumulators.reset(pos, networks, caches);
|
||||
auto accumulators = std::make_unique<AccumulatorStack>();
|
||||
|
||||
// We estimate the value of each piece by doing a differential evaluation from
|
||||
// the current base eval, simulating the removal of the piece from its square.
|
||||
auto [psqt, positional] = networks.big.evaluate(pos, accumulators, &caches.big);
|
||||
auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches.big);
|
||||
Value base = psqt + positional;
|
||||
base = pos.side_to_move() == WHITE ? base : -base;
|
||||
|
||||
@@ -140,8 +139,8 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat
|
||||
{
|
||||
pos.remove_piece(sq);
|
||||
|
||||
accumulators.reset(pos, networks, caches);
|
||||
std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, &caches.big);
|
||||
accumulators->reset();
|
||||
std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, caches.big);
|
||||
Value eval = psqt + positional;
|
||||
eval = pos.side_to_move() == WHITE ? eval : -eval;
|
||||
v = base - eval;
|
||||
@@ -157,8 +156,8 @@ trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::Accumulat
|
||||
ss << board[row] << '\n';
|
||||
ss << '\n';
|
||||
|
||||
accumulators.reset(pos, networks, caches);
|
||||
auto t = networks.big.trace_evaluate(pos, accumulators, &caches.big);
|
||||
accumulators->reset();
|
||||
auto t = networks.big.trace_evaluate(pos, *accumulators, caches.big);
|
||||
|
||||
ss << " NNUE network contributions "
|
||||
<< (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl
|
||||
|
||||
+18
-5
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -20,8 +20,10 @@
|
||||
#define NNUE_MISC_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "../misc.h"
|
||||
#include "../types.h"
|
||||
#include "nnue_architecture.h"
|
||||
|
||||
@@ -31,17 +33,17 @@ class Position;
|
||||
|
||||
namespace Eval::NNUE {
|
||||
|
||||
// EvalFile uses fixed string types because it's part of the network structure which must be trivial.
|
||||
struct EvalFile {
|
||||
// Default net name, will use one of the EvalFileDefaultName* macros defined
|
||||
// in evaluate.h
|
||||
std::string defaultName;
|
||||
FixedString<256> defaultName;
|
||||
// Selected net name, either via uci option or default
|
||||
std::string current;
|
||||
FixedString<256> current;
|
||||
// Net description extracted from the net file
|
||||
std::string netDescription;
|
||||
FixedString<256> netDescription;
|
||||
};
|
||||
|
||||
|
||||
struct NnueEvalTrace {
|
||||
static_assert(LayerStacks == PSQTBuckets);
|
||||
|
||||
@@ -58,4 +60,15 @@ std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& ca
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
} // namespace Stockfish
|
||||
|
||||
template<>
|
||||
struct std::hash<Stockfish::Eval::NNUE::EvalFile> {
|
||||
std::size_t operator()(const Stockfish::Eval::NNUE::EvalFile& evalFile) const noexcept {
|
||||
std::size_t h = 0;
|
||||
Stockfish::hash_combine(h, evalFile.defaultName);
|
||||
Stockfish::hash_combine(h, evalFile.current);
|
||||
Stockfish::hash_combine(h, evalFile.netDescription);
|
||||
return h;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // #ifndef NNUE_MISC_H_INCLUDED
|
||||
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Stockfish is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef NNUE_SIMD_H_INCLUDED
|
||||
#define NNUE_SIMD_H_INCLUDED
|
||||
|
||||
#if defined(USE_AVX2)
|
||||
#include <immintrin.h>
|
||||
|
||||
#elif defined(USE_SSE41)
|
||||
#include <smmintrin.h>
|
||||
|
||||
#elif defined(USE_SSSE3)
|
||||
#include <tmmintrin.h>
|
||||
|
||||
#elif defined(USE_SSE2)
|
||||
#include <emmintrin.h>
|
||||
|
||||
#elif defined(USE_NEON)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#include "../types.h"
|
||||
#include "nnue_common.h"
|
||||
|
||||
namespace Stockfish::Eval::NNUE::SIMD {
|
||||
|
||||
// If vector instructions are enabled, we update and refresh the
|
||||
// accumulator tile by tile such that each tile fits in the CPU's
|
||||
// vector registers.
|
||||
#define VECTOR
|
||||
|
||||
#ifdef USE_AVX512
|
||||
using vec_t = __m512i;
|
||||
using vec_i8_t = __m256i;
|
||||
using vec128_t = __m128i;
|
||||
using psqt_vec_t = __m256i;
|
||||
using vec_uint_t = __m512i;
|
||||
#define vec_load(a) _mm512_load_si512(a)
|
||||
#define vec_store(a, b) _mm512_store_si512(a, b)
|
||||
#define vec_convert_8_16(a) _mm512_cvtepi8_epi16(a)
|
||||
#define vec_add_16(a, b) _mm512_add_epi16(a, b)
|
||||
#define vec_sub_16(a, b) _mm512_sub_epi16(a, b)
|
||||
#define vec_mulhi_16(a, b) _mm512_mulhi_epi16(a, b)
|
||||
#define vec_zero() _mm512_setzero_epi32()
|
||||
#define vec_set_16(a) _mm512_set1_epi16(a)
|
||||
#define vec_max_16(a, b) _mm512_max_epi16(a, b)
|
||||
#define vec_min_16(a, b) _mm512_min_epi16(a, b)
|
||||
#define vec_slli_16(a, b) _mm512_slli_epi16(a, b)
|
||||
// Inverse permuted at load time
|
||||
#define vec_packus_16(a, b) _mm512_packus_epi16(a, b)
|
||||
#define vec_load_psqt(a) _mm256_load_si256(a)
|
||||
#define vec_store_psqt(a, b) _mm256_store_si256(a, b)
|
||||
#define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)
|
||||
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
|
||||
#define vec_zero_psqt() _mm256_setzero_si256()
|
||||
|
||||
#ifdef USE_SSSE3
|
||||
#define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
|
||||
#endif
|
||||
|
||||
#define vec128_zero _mm_setzero_si128()
|
||||
#define vec128_set_16(a) _mm_set1_epi16(a)
|
||||
#define vec128_load(a) _mm_load_si128(a)
|
||||
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
|
||||
#define vec128_add(a, b) _mm_add_epi16(a, b)
|
||||
#define NumRegistersSIMD 16
|
||||
#define MaxChunkSize 64
|
||||
|
||||
#elif USE_AVX2
|
||||
using vec_t = __m256i;
|
||||
using vec_i8_t = __m128i;
|
||||
using vec128_t = __m128i;
|
||||
using psqt_vec_t = __m256i;
|
||||
using vec_uint_t = __m256i;
|
||||
#define vec_load(a) _mm256_load_si256(a)
|
||||
#define vec_store(a, b) _mm256_store_si256(a, b)
|
||||
#define vec_convert_8_16(a) _mm256_cvtepi8_epi16(a)
|
||||
#define vec_add_16(a, b) _mm256_add_epi16(a, b)
|
||||
#define vec_sub_16(a, b) _mm256_sub_epi16(a, b)
|
||||
#define vec_mulhi_16(a, b) _mm256_mulhi_epi16(a, b)
|
||||
#define vec_zero() _mm256_setzero_si256()
|
||||
#define vec_set_16(a) _mm256_set1_epi16(a)
|
||||
#define vec_max_16(a, b) _mm256_max_epi16(a, b)
|
||||
#define vec_min_16(a, b) _mm256_min_epi16(a, b)
|
||||
#define vec_slli_16(a, b) _mm256_slli_epi16(a, b)
|
||||
// Inverse permuted at load time
|
||||
#define vec_packus_16(a, b) _mm256_packus_epi16(a, b)
|
||||
#define vec_load_psqt(a) _mm256_load_si256(a)
|
||||
#define vec_store_psqt(a, b) _mm256_store_si256(a, b)
|
||||
#define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)
|
||||
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
|
||||
#define vec_zero_psqt() _mm256_setzero_si256()
|
||||
|
||||
#ifdef USE_SSSE3
|
||||
#if defined(USE_VNNI) && !defined(USE_AVXVNNI)
|
||||
#define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256())
|
||||
#else
|
||||
#define vec_nnz(a) \
|
||||
_mm256_movemask_ps( \
|
||||
_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define vec128_zero _mm_setzero_si128()
|
||||
#define vec128_set_16(a) _mm_set1_epi16(a)
|
||||
#define vec128_load(a) _mm_load_si128(a)
|
||||
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
|
||||
#define vec128_add(a, b) _mm_add_epi16(a, b)
|
||||
|
||||
#define NumRegistersSIMD 12
|
||||
#define MaxChunkSize 32
|
||||
|
||||
#elif USE_SSE2
|
||||
using vec_t = __m128i;
|
||||
using vec_i8_t = std::uint64_t; // for the correct size -- will be loaded into an xmm reg
|
||||
using vec128_t = __m128i;
|
||||
using psqt_vec_t = __m128i;
|
||||
using vec_uint_t = __m128i;
|
||||
#define vec_load(a) (*(a))
|
||||
#define vec_store(a, b) *(a) = (b)
|
||||
#define vec_add_16(a, b) _mm_add_epi16(a, b)
|
||||
#define vec_sub_16(a, b) _mm_sub_epi16(a, b)
|
||||
#define vec_mulhi_16(a, b) _mm_mulhi_epi16(a, b)
|
||||
#define vec_zero() _mm_setzero_si128()
|
||||
#define vec_set_16(a) _mm_set1_epi16(a)
|
||||
#define vec_max_16(a, b) _mm_max_epi16(a, b)
|
||||
#define vec_min_16(a, b) _mm_min_epi16(a, b)
|
||||
#define vec_slli_16(a, b) _mm_slli_epi16(a, b)
|
||||
#define vec_packus_16(a, b) _mm_packus_epi16(a, b)
|
||||
#define vec_load_psqt(a) (*(a))
|
||||
#define vec_store_psqt(a, b) *(a) = (b)
|
||||
#define vec_add_psqt_32(a, b) _mm_add_epi32(a, b)
|
||||
#define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b)
|
||||
#define vec_zero_psqt() _mm_setzero_si128()
|
||||
|
||||
#ifdef USE_SSSE3
|
||||
#define vec_nnz(a) \
|
||||
_mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128())))
|
||||
#endif
|
||||
|
||||
#ifdef __i386__
|
||||
inline __m128i _mm_cvtsi64_si128(int64_t val) {
|
||||
return _mm_loadl_epi64(reinterpret_cast<const __m128i*>(&val));
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SSE41
|
||||
#define vec_convert_8_16(a) _mm_cvtepi8_epi16(_mm_cvtsi64_si128(static_cast<int64_t>(a)))
|
||||
#else
|
||||
// Credit: Yoshie2000
|
||||
inline __m128i vec_convert_8_16(uint64_t x) {
|
||||
__m128i v8 = _mm_cvtsi64_si128(static_cast<int64_t>(x));
|
||||
__m128i sign = _mm_cmpgt_epi8(_mm_setzero_si128(), v8);
|
||||
return _mm_unpacklo_epi8(v8, sign);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define vec128_zero _mm_setzero_si128()
|
||||
#define vec128_set_16(a) _mm_set1_epi16(a)
|
||||
#define vec128_load(a) _mm_load_si128(a)
|
||||
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
|
||||
#define vec128_add(a, b) _mm_add_epi16(a, b)
|
||||
|
||||
#define NumRegistersSIMD (Is64Bit ? 12 : 6)
|
||||
#define MaxChunkSize 16
|
||||
|
||||
#elif USE_NEON
|
||||
using vec_t = int16x8_t;
|
||||
using vec_i8_t = int8x16_t;
|
||||
using psqt_vec_t = int32x4_t;
|
||||
using vec128_t = uint16x8_t;
|
||||
using vec_uint_t = uint32x4_t;
|
||||
#define vec_load(a) (*(a))
|
||||
#define vec_store(a, b) *(a) = (b)
|
||||
#define vec_add_16(a, b) vaddq_s16(a, b)
|
||||
#define vec_sub_16(a, b) vsubq_s16(a, b)
|
||||
#define vec_mulhi_16(a, b) vqdmulhq_s16(a, b)
|
||||
#define vec_zero() vec_t{0}
|
||||
#define vec_set_16(a) vdupq_n_s16(a)
|
||||
#define vec_max_16(a, b) vmaxq_s16(a, b)
|
||||
#define vec_min_16(a, b) vminq_s16(a, b)
|
||||
#define vec_slli_16(a, b) vshlq_s16(a, vec_set_16(b))
|
||||
#define vec_packus_16(a, b) reinterpret_cast<vec_t>(vcombine_u8(vqmovun_s16(a), vqmovun_s16(b)))
|
||||
#define vec_load_psqt(a) (*(a))
|
||||
#define vec_store_psqt(a, b) *(a) = (b)
|
||||
#define vec_add_psqt_32(a, b) vaddq_s32(a, b)
|
||||
#define vec_sub_psqt_32(a, b) vsubq_s32(a, b)
|
||||
#define vec_zero_psqt() psqt_vec_t{0}
|
||||
|
||||
static constexpr std::uint32_t Mask[4] = {1, 2, 4, 8};
|
||||
#define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask)))
|
||||
#define vec128_zero vdupq_n_u16(0)
|
||||
#define vec128_set_16(a) vdupq_n_u16(a)
|
||||
#define vec128_load(a) vld1q_u16(reinterpret_cast<const std::uint16_t*>(a))
|
||||
#define vec128_storeu(a, b) vst1q_u16(reinterpret_cast<std::uint16_t*>(a), b)
|
||||
#define vec128_add(a, b) vaddq_u16(a, b)
|
||||
|
||||
#define NumRegistersSIMD 16
|
||||
#define MaxChunkSize 16
|
||||
|
||||
#ifndef __aarch64__
|
||||
// Single instruction doesn't exist on 32-bit ARM
|
||||
inline int8x16_t vmovl_high_s8(int8x16_t val) { return vmovl_s8(vget_high_s8(val)); }
|
||||
#endif
|
||||
|
||||
#else
|
||||
#undef VECTOR
|
||||
|
||||
#endif
|
||||
|
||||
struct Vec16Wrapper {
|
||||
#ifdef VECTOR
|
||||
using type = vec_t;
|
||||
static type add(const type& lhs, const type& rhs) { return vec_add_16(lhs, rhs); }
|
||||
static type sub(const type& lhs, const type& rhs) { return vec_sub_16(lhs, rhs); }
|
||||
#else
|
||||
using type = BiasType;
|
||||
static type add(const type& lhs, const type& rhs) { return lhs + rhs; }
|
||||
static type sub(const type& lhs, const type& rhs) { return lhs - rhs; }
|
||||
#endif
|
||||
};
|
||||
|
||||
struct Vec32Wrapper {
|
||||
#ifdef VECTOR
|
||||
using type = psqt_vec_t;
|
||||
static type add(const type& lhs, const type& rhs) { return vec_add_psqt_32(lhs, rhs); }
|
||||
static type sub(const type& lhs, const type& rhs) { return vec_sub_psqt_32(lhs, rhs); }
|
||||
#else
|
||||
using type = PSQTWeightType;
|
||||
static type add(const type& lhs, const type& rhs) { return lhs + rhs; }
|
||||
static type sub(const type& lhs, const type& rhs) { return lhs - rhs; }
|
||||
#endif
|
||||
};
|
||||
|
||||
enum UpdateOperation {
|
||||
Add,
|
||||
Sub
|
||||
};
|
||||
|
||||
template<typename VecWrapper,
|
||||
UpdateOperation... ops,
|
||||
std::enable_if_t<sizeof...(ops) == 0, bool> = true>
|
||||
typename VecWrapper::type fused(const typename VecWrapper::type& in) {
|
||||
return in;
|
||||
}
|
||||
|
||||
template<typename VecWrapper,
|
||||
UpdateOperation update_op,
|
||||
UpdateOperation... ops,
|
||||
typename T,
|
||||
typename... Ts,
|
||||
std::enable_if_t<is_all_same_v<typename VecWrapper::type, T, Ts...>, bool> = true,
|
||||
std::enable_if_t<sizeof...(ops) == sizeof...(Ts), bool> = true>
|
||||
typename VecWrapper::type
|
||||
fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operands) {
|
||||
switch (update_op)
|
||||
{
|
||||
case Add :
|
||||
return fused<VecWrapper, ops...>(VecWrapper::add(in, operand), operands...);
|
||||
case Sub :
|
||||
return fused<VecWrapper, ops...>(VecWrapper::sub(in, operand), operands...);
|
||||
default :
|
||||
static_assert(update_op == Add || update_op == Sub,
|
||||
"Only Add and Sub are currently supported.");
|
||||
return typename VecWrapper::type();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(USE_AVX512)
|
||||
|
||||
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
|
||||
return _mm512_reduce_add_epi32(sum) + bias;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) {
|
||||
|
||||
#if defined(USE_VNNI)
|
||||
acc = _mm512_dpbusd_epi32(acc, a, b);
|
||||
#else
|
||||
__m512i product0 = _mm512_maddubs_epi16(a, b);
|
||||
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
|
||||
acc = _mm512_add_epi32(acc, product0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(USE_AVX2)
|
||||
|
||||
[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
|
||||
__m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
|
||||
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
|
||||
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
|
||||
return _mm_cvtsi128_si32(sum128) + bias;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) {
|
||||
|
||||
#if defined(USE_VNNI)
|
||||
acc = _mm256_dpbusd_epi32(acc, a, b);
|
||||
#else
|
||||
__m256i product0 = _mm256_maddubs_epi16(a, b);
|
||||
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
|
||||
acc = _mm256_add_epi32(acc, product0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(USE_SSSE3)
|
||||
|
||||
[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
|
||||
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
|
||||
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
|
||||
return _mm_cvtsi128_si32(sum) + bias;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
|
||||
|
||||
__m128i product0 = _mm_maddubs_epi16(a, b);
|
||||
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
|
||||
acc = _mm_add_epi32(acc, product0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(USE_NEON_DOTPROD)
|
||||
|
||||
[[maybe_unused]] static void
|
||||
dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
|
||||
|
||||
acc = vdotq_s32(acc, a, b);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_NEON)
|
||||
|
||||
[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
|
||||
#if USE_NEON >= 8
|
||||
return vaddvq_s32(s);
|
||||
#else
|
||||
return s[0] + s[1] + s[2] + s[3];
|
||||
#endif
|
||||
}
|
||||
|
||||
[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {
|
||||
return neon_m128_reduce_add_epi32(sum) + bias;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if USE_NEON >= 8
|
||||
[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
|
||||
|
||||
int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b));
|
||||
int16x8_t product1 = vmull_high_s8(a, b);
|
||||
int16x8_t sum = vpaddq_s16(product0, product1);
|
||||
acc = vpadalq_s16(acc, sum);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Compute optimal SIMD register count for feature transformer accumulation.
|
||||
template<IndexType TransformedFeatureWidth, IndexType HalfDimensions, IndexType PSQTBuckets>
|
||||
class SIMDTiling {
|
||||
#ifdef VECTOR
|
||||
// We use __m* types as template arguments, which causes GCC to emit warnings
|
||||
// about losing some attribute information. This is irrelevant to us as we
|
||||
// only take their size, so the following pragma are harmless.
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
||||
#endif
|
||||
|
||||
template<typename SIMDRegisterType, typename LaneType, int NumLanes, int MaxRegisters>
|
||||
static constexpr int BestRegisterCount() {
|
||||
constexpr std::size_t RegisterSize = sizeof(SIMDRegisterType);
|
||||
constexpr std::size_t LaneSize = sizeof(LaneType);
|
||||
|
||||
static_assert(RegisterSize >= LaneSize);
|
||||
static_assert(MaxRegisters <= NumRegistersSIMD);
|
||||
static_assert(MaxRegisters > 0);
|
||||
static_assert(NumRegistersSIMD > 0);
|
||||
static_assert(RegisterSize % LaneSize == 0);
|
||||
static_assert((NumLanes * LaneSize) % RegisterSize == 0);
|
||||
|
||||
const int ideal = (NumLanes * LaneSize) / RegisterSize;
|
||||
if (ideal <= MaxRegisters)
|
||||
return ideal;
|
||||
|
||||
// Look for the largest divisor of the ideal register count that is smaller than MaxRegisters
|
||||
for (int divisor = MaxRegisters; divisor > 1; --divisor)
|
||||
if (ideal % divisor == 0)
|
||||
return divisor;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
public:
|
||||
static constexpr int NumRegs =
|
||||
BestRegisterCount<vec_t, WeightType, TransformedFeatureWidth, NumRegistersSIMD>();
|
||||
static constexpr int NumPsqtRegs =
|
||||
BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
|
||||
|
||||
static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
|
||||
static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;
|
||||
|
||||
static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions");
|
||||
static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets");
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
+472
-100
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -37,7 +37,7 @@
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#include "memory.h"
|
||||
#include "shm.h"
|
||||
|
||||
// We support linux very well, but we explicitly do NOT support Android,
|
||||
// because there is no affected systems, not worth maintaining.
|
||||
@@ -371,6 +371,50 @@ inline WindowsAffinity get_process_affinity() {
|
||||
return affinity;
|
||||
}
|
||||
|
||||
// Type machinery used to emulate Cache->GroupCount
|
||||
|
||||
template<typename T, typename = void>
|
||||
struct HasGroupCount: std::false_type {};
|
||||
|
||||
template<typename T>
|
||||
struct HasGroupCount<T, std::void_t<decltype(std::declval<T>().Cache.GroupCount)>>: std::true_type {
|
||||
};
|
||||
|
||||
template<typename T, typename Pred, std::enable_if_t<HasGroupCount<T>::value, bool> = true>
|
||||
std::set<CpuIndex> readCacheMembers(const T* info, Pred&& is_cpu_allowed) {
|
||||
std::set<CpuIndex> cpus;
|
||||
// On Windows 10 this will read a 0 because GroupCount doesn't exist
|
||||
int groupCount = std::max(info->Cache.GroupCount, WORD(1));
|
||||
for (WORD procGroup = 0; procGroup < groupCount; ++procGroup)
|
||||
{
|
||||
for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)
|
||||
{
|
||||
WORD groupNumber = info->Cache.GroupMasks[procGroup].Group;
|
||||
const CpuIndex c = static_cast<CpuIndex>(groupNumber) * WIN_PROCESSOR_GROUP_SIZE
|
||||
+ static_cast<CpuIndex>(number);
|
||||
if (!(info->Cache.GroupMasks[procGroup].Mask & (1ULL << number)) || !is_cpu_allowed(c))
|
||||
continue;
|
||||
cpus.insert(c);
|
||||
}
|
||||
}
|
||||
return cpus;
|
||||
}
|
||||
|
||||
template<typename T, typename Pred, std::enable_if_t<!HasGroupCount<T>::value, bool> = true>
|
||||
std::set<CpuIndex> readCacheMembers(const T* info, Pred&& is_cpu_allowed) {
|
||||
std::set<CpuIndex> cpus;
|
||||
for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)
|
||||
{
|
||||
WORD groupNumber = info->Cache.GroupMask.Group;
|
||||
const CpuIndex c = static_cast<CpuIndex>(groupNumber) * WIN_PROCESSOR_GROUP_SIZE
|
||||
+ static_cast<CpuIndex>(number);
|
||||
if (!(info->Cache.GroupMask.Mask & (1ULL << number)) || !is_cpu_allowed(c))
|
||||
continue;
|
||||
cpus.insert(c);
|
||||
}
|
||||
return cpus;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
@@ -447,14 +491,39 @@ class NumaReplicatedAccessToken {
|
||||
NumaIndex n;
|
||||
};
|
||||
|
||||
struct L3Domain {
|
||||
NumaIndex systemNumaIndex{};
|
||||
std::set<CpuIndex> cpus{};
|
||||
};
|
||||
|
||||
// Use system NUMA nodes
|
||||
struct SystemNumaPolicy {};
|
||||
// Use system-reported L3 domains
|
||||
struct L3DomainsPolicy {};
|
||||
// Group system-reported L3 domains until they reach bundleSize
|
||||
struct BundledL3Policy {
|
||||
size_t bundleSize;
|
||||
};
|
||||
|
||||
using NumaAutoPolicy = std::variant<SystemNumaPolicy, L3DomainsPolicy, BundledL3Policy>;
|
||||
|
||||
// Designed as immutable, because there is no good reason to alter an already
|
||||
// existing config in a way that doesn't require recreating it completely, and
|
||||
// it would be complex and expensive to maintain class invariants.
|
||||
// The CPU (processor) numbers always correspond to the actual numbering used
|
||||
// by the system. The NUMA node numbers MAY NOT correspond to the system's
|
||||
// numbering of the NUMA nodes. In particular, empty nodes may be removed, or
|
||||
// the user may create custom nodes. It is guaranteed that NUMA nodes are NOT
|
||||
// empty: every node exposed by NumaConfig has at least one processor assigned.
|
||||
// numbering of the NUMA nodes. In particular, by default, if the processor has
|
||||
// non-uniform cache access within a NUMA node (i.e., a non-unified L3 cache structure),
|
||||
// then L3 domains within a system NUMA node will be used to subdivide it
|
||||
// into multiple logical NUMA nodes in the config. Additionally, empty nodes may
|
||||
// be removed, or the user may create custom nodes.
|
||||
//
|
||||
// As a special case, when performing system-wide replication of read-only data
|
||||
// (i.e., LazyNumaReplicatedSystemWide), the system NUMA node is used, rather than
|
||||
// custom or L3-aware nodes. See that class's get_discriminator() function.
|
||||
//
|
||||
// It is guaranteed that NUMA nodes are NOT empty: every node exposed by NumaConfig
|
||||
// has at least one processor assigned.
|
||||
//
|
||||
// We use startup affinities so as not to modify its own behaviour in time.
|
||||
//
|
||||
@@ -469,78 +538,19 @@ class NumaConfig {
|
||||
add_cpu_range_to_node(NumaIndex{0}, CpuIndex{0}, numCpus - 1);
|
||||
}
|
||||
|
||||
// This function queries the system for the mapping of processors to NUMA nodes.
|
||||
// On Linux we read from standardized kernel sysfs, with a fallback to single NUMA
|
||||
// node. On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see
|
||||
// comment for Windows implementation of get_process_affinity.
|
||||
static NumaConfig from_system([[maybe_unused]] bool respectProcessAffinity = true) {
|
||||
// This function gets a NumaConfig based on the system's provided information.
|
||||
// The available policies are documented above.
|
||||
static NumaConfig from_system([[maybe_unused]] const NumaAutoPolicy& policy,
|
||||
bool respectProcessAffinity = true) {
|
||||
NumaConfig cfg = empty();
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
#if !((defined(__linux__) && !defined(__ANDROID__)) || defined(_WIN64))
|
||||
// Fallback for unsupported systems.
|
||||
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
|
||||
cfg.add_cpu_to_node(NumaIndex{0}, c);
|
||||
#else
|
||||
|
||||
std::set<CpuIndex> allowedCpus;
|
||||
|
||||
if (respectProcessAffinity)
|
||||
allowedCpus = STARTUP_PROCESSOR_AFFINITY;
|
||||
|
||||
auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) {
|
||||
return !respectProcessAffinity || allowedCpus.count(c) == 1;
|
||||
};
|
||||
|
||||
// On Linux things are straightforward, since there's no processor groups and
|
||||
// any thread can be scheduled on all processors.
|
||||
// We try to gather this information from the sysfs first
|
||||
// https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node
|
||||
|
||||
bool useFallback = false;
|
||||
auto fallback = [&]() {
|
||||
useFallback = true;
|
||||
cfg = empty();
|
||||
};
|
||||
|
||||
// /sys/devices/system/node/online contains information about active NUMA nodes
|
||||
auto nodeIdsStr = read_file_to_string("/sys/devices/system/node/online");
|
||||
if (!nodeIdsStr.has_value() || nodeIdsStr->empty())
|
||||
{
|
||||
fallback();
|
||||
}
|
||||
else
|
||||
{
|
||||
remove_whitespace(*nodeIdsStr);
|
||||
for (size_t n : indices_from_shortened_string(*nodeIdsStr))
|
||||
{
|
||||
// /sys/devices/system/node/node.../cpulist
|
||||
std::string path =
|
||||
std::string("/sys/devices/system/node/node") + std::to_string(n) + "/cpulist";
|
||||
auto cpuIdsStr = read_file_to_string(path);
|
||||
// Now, we only bail if the file does not exist. Some nodes may be
|
||||
// empty, that's fine. An empty node still has a file that appears
|
||||
// to have some whitespace, so we need to handle that.
|
||||
if (!cpuIdsStr.has_value())
|
||||
{
|
||||
fallback();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
remove_whitespace(*cpuIdsStr);
|
||||
for (size_t c : indices_from_shortened_string(*cpuIdsStr))
|
||||
{
|
||||
if (is_cpu_allowed(c))
|
||||
cfg.add_cpu_to_node(n, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useFallback)
|
||||
{
|
||||
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
|
||||
if (is_cpu_allowed(c))
|
||||
cfg.add_cpu_to_node(NumaIndex{0}, c);
|
||||
}
|
||||
|
||||
#elif defined(_WIN64)
|
||||
#if defined(_WIN64)
|
||||
|
||||
std::optional<std::set<CpuIndex>> allowedCpus;
|
||||
|
||||
@@ -555,28 +565,38 @@ class NumaConfig {
|
||||
return !allowedCpus.has_value() || allowedCpus->count(c) == 1;
|
||||
};
|
||||
|
||||
WORD numProcGroups = GetActiveProcessorGroupCount();
|
||||
for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup)
|
||||
{
|
||||
for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)
|
||||
{
|
||||
PROCESSOR_NUMBER procnum;
|
||||
procnum.Group = procGroup;
|
||||
procnum.Number = number;
|
||||
procnum.Reserved = 0;
|
||||
USHORT nodeNumber;
|
||||
#elif defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber);
|
||||
const CpuIndex c = static_cast<CpuIndex>(procGroup) * WIN_PROCESSOR_GROUP_SIZE
|
||||
+ static_cast<CpuIndex>(number);
|
||||
if (status != 0 && nodeNumber != std::numeric_limits<USHORT>::max()
|
||||
&& is_cpu_allowed(c))
|
||||
{
|
||||
cfg.add_cpu_to_node(nodeNumber, c);
|
||||
}
|
||||
std::set<CpuIndex> allowedCpus;
|
||||
|
||||
if (respectProcessAffinity)
|
||||
allowedCpus = STARTUP_PROCESSOR_AFFINITY;
|
||||
|
||||
auto is_cpu_allowed = [respectProcessAffinity, &allowedCpus](CpuIndex c) {
|
||||
return !respectProcessAffinity || allowedCpus.count(c) == 1;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
bool l3Success = false;
|
||||
if (!std::holds_alternative<SystemNumaPolicy>(policy))
|
||||
{
|
||||
size_t l3BundleSize = 0;
|
||||
if (const auto* v = std::get_if<BundledL3Policy>(&policy))
|
||||
{
|
||||
l3BundleSize = v->bundleSize;
|
||||
}
|
||||
if (auto l3Cfg =
|
||||
try_get_l3_aware_config(respectProcessAffinity, l3BundleSize, is_cpu_allowed))
|
||||
{
|
||||
cfg = std::move(*l3Cfg);
|
||||
l3Success = true;
|
||||
}
|
||||
}
|
||||
if (!l3Success)
|
||||
cfg = from_system_numa(respectProcessAffinity, is_cpu_allowed);
|
||||
|
||||
#if defined(_WIN64)
|
||||
// Split the NUMA nodes to be contained within a group if necessary.
|
||||
// This is needed between Windows 10 Build 20348 and Windows 11, because
|
||||
// the new NUMA allocation behaviour was introduced while there was
|
||||
@@ -623,12 +643,7 @@ class NumaConfig {
|
||||
|
||||
cfg = std::move(splitCfg);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
// Fallback for unsupported systems.
|
||||
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
|
||||
cfg.add_cpu_to_node(NumaIndex{0}, c);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -945,10 +960,11 @@ class NumaConfig {
|
||||
th.join();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::set<CpuIndex>> nodes;
|
||||
std::map<CpuIndex, NumaIndex> nodeByCpu;
|
||||
CpuIndex highestCpuIndex;
|
||||
|
||||
private:
|
||||
CpuIndex highestCpuIndex;
|
||||
|
||||
bool customAffinity;
|
||||
|
||||
@@ -1040,6 +1056,227 @@ class NumaConfig {
|
||||
|
||||
return indices;
|
||||
}
|
||||
|
||||
// This function queries the system for the mapping of processors to NUMA nodes.
|
||||
// On Linux we read from standardized kernel sysfs, with a fallback to single NUMA
|
||||
// node. On Windows we utilize GetNumaProcessorNodeEx, which has its quirks, see
|
||||
// comment for Windows implementation of get_process_affinity.
|
||||
template<typename Pred>
|
||||
static NumaConfig from_system_numa([[maybe_unused]] bool respectProcessAffinity,
|
||||
[[maybe_unused]] Pred&& is_cpu_allowed) {
|
||||
NumaConfig cfg = empty();
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
// On Linux things are straightforward, since there's no processor groups and
|
||||
// any thread can be scheduled on all processors.
|
||||
// We try to gather this information from the sysfs first
|
||||
// https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-devices-node
|
||||
|
||||
bool useFallback = false;
|
||||
auto fallback = [&]() {
|
||||
useFallback = true;
|
||||
cfg = empty();
|
||||
};
|
||||
|
||||
// /sys/devices/system/node/online contains information about active NUMA nodes
|
||||
auto nodeIdsStr = read_file_to_string("/sys/devices/system/node/online");
|
||||
if (!nodeIdsStr.has_value() || nodeIdsStr->empty())
|
||||
{
|
||||
fallback();
|
||||
}
|
||||
else
|
||||
{
|
||||
remove_whitespace(*nodeIdsStr);
|
||||
for (size_t n : indices_from_shortened_string(*nodeIdsStr))
|
||||
{
|
||||
// /sys/devices/system/node/node.../cpulist
|
||||
std::string path =
|
||||
std::string("/sys/devices/system/node/node") + std::to_string(n) + "/cpulist";
|
||||
auto cpuIdsStr = read_file_to_string(path);
|
||||
// Now, we only bail if the file does not exist. Some nodes may be
|
||||
// empty, that's fine. An empty node still has a file that appears
|
||||
// to have some whitespace, so we need to handle that.
|
||||
if (!cpuIdsStr.has_value())
|
||||
{
|
||||
fallback();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
remove_whitespace(*cpuIdsStr);
|
||||
for (size_t c : indices_from_shortened_string(*cpuIdsStr))
|
||||
{
|
||||
if (is_cpu_allowed(c))
|
||||
cfg.add_cpu_to_node(n, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useFallback)
|
||||
{
|
||||
for (CpuIndex c = 0; c < SYSTEM_THREADS_NB; ++c)
|
||||
if (is_cpu_allowed(c))
|
||||
cfg.add_cpu_to_node(NumaIndex{0}, c);
|
||||
}
|
||||
|
||||
#elif defined(_WIN64)
|
||||
|
||||
WORD numProcGroups = GetActiveProcessorGroupCount();
|
||||
for (WORD procGroup = 0; procGroup < numProcGroups; ++procGroup)
|
||||
{
|
||||
for (BYTE number = 0; number < WIN_PROCESSOR_GROUP_SIZE; ++number)
|
||||
{
|
||||
PROCESSOR_NUMBER procnum;
|
||||
procnum.Group = procGroup;
|
||||
procnum.Number = number;
|
||||
procnum.Reserved = 0;
|
||||
USHORT nodeNumber;
|
||||
|
||||
const BOOL status = GetNumaProcessorNodeEx(&procnum, &nodeNumber);
|
||||
const CpuIndex c = static_cast<CpuIndex>(procGroup) * WIN_PROCESSOR_GROUP_SIZE
|
||||
+ static_cast<CpuIndex>(number);
|
||||
if (status != 0 && nodeNumber != std::numeric_limits<USHORT>::max()
|
||||
&& is_cpu_allowed(c))
|
||||
{
|
||||
cfg.add_cpu_to_node(nodeNumber, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
abort(); // should not reach here
|
||||
|
||||
#endif
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
template<typename Pred>
|
||||
static std::optional<NumaConfig> try_get_l3_aware_config(
|
||||
bool respectProcessAffinity, size_t bundleSize, [[maybe_unused]] Pred&& is_cpu_allowed) {
|
||||
// Get the normal system configuration so we know to which NUMA node
|
||||
// each L3 domain belongs.
|
||||
NumaConfig systemConfig =
|
||||
NumaConfig::from_system(SystemNumaPolicy{}, respectProcessAffinity);
|
||||
std::vector<L3Domain> l3Domains;
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
std::set<CpuIndex> seenCpus;
|
||||
auto nextUnseenCpu = [&seenCpus]() {
|
||||
for (CpuIndex i = 0;; ++i)
|
||||
if (!seenCpus.count(i))
|
||||
return i;
|
||||
};
|
||||
|
||||
while (true)
|
||||
{
|
||||
CpuIndex next = nextUnseenCpu();
|
||||
auto siblingsStr =
|
||||
read_file_to_string("/sys/devices/system/cpu/cpu" + std::to_string(next)
|
||||
+ "/cache/index3/shared_cpu_list");
|
||||
|
||||
if (!siblingsStr.has_value() || siblingsStr->empty())
|
||||
{
|
||||
break; // we have read all available CPUs
|
||||
}
|
||||
|
||||
L3Domain domain;
|
||||
for (size_t c : indices_from_shortened_string(*siblingsStr))
|
||||
{
|
||||
if (is_cpu_allowed(c))
|
||||
{
|
||||
domain.systemNumaIndex = systemConfig.nodeByCpu.at(c);
|
||||
domain.cpus.insert(c);
|
||||
}
|
||||
seenCpus.insert(c);
|
||||
}
|
||||
if (!domain.cpus.empty())
|
||||
{
|
||||
l3Domains.emplace_back(std::move(domain));
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(_WIN64)
|
||||
|
||||
DWORD bufSize = 0;
|
||||
GetLogicalProcessorInformationEx(RelationCache, nullptr, &bufSize);
|
||||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
|
||||
return std::nullopt;
|
||||
|
||||
std::vector<char> buffer(bufSize);
|
||||
auto info = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(buffer.data());
|
||||
if (!GetLogicalProcessorInformationEx(RelationCache, info, &bufSize))
|
||||
return std::nullopt;
|
||||
|
||||
while (reinterpret_cast<char*>(info) < buffer.data() + bufSize)
|
||||
{
|
||||
info = std::launder(info);
|
||||
if (info->Relationship == RelationCache && info->Cache.Level == 3)
|
||||
{
|
||||
L3Domain domain{};
|
||||
domain.cpus = readCacheMembers(info, is_cpu_allowed);
|
||||
if (!domain.cpus.empty())
|
||||
{
|
||||
domain.systemNumaIndex = systemConfig.nodeByCpu.at(*domain.cpus.begin());
|
||||
l3Domains.push_back(std::move(domain));
|
||||
}
|
||||
}
|
||||
// Variable length data structure, advance to next
|
||||
info = reinterpret_cast<PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(
|
||||
reinterpret_cast<char*>(info) + info->Size);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!l3Domains.empty())
|
||||
return {NumaConfig::from_l3_info(std::move(l3Domains), bundleSize)};
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
static NumaConfig from_l3_info(std::vector<L3Domain>&& domains, size_t bundleSize) {
|
||||
assert(!domains.empty());
|
||||
|
||||
std::map<NumaIndex, std::vector<L3Domain>> list;
|
||||
for (auto& d : domains)
|
||||
list[d.systemNumaIndex].emplace_back(std::move(d));
|
||||
|
||||
NumaConfig cfg = empty();
|
||||
NumaIndex n = 0;
|
||||
for (auto& [_, ds] : list)
|
||||
{
|
||||
bool changed;
|
||||
// Scan through pairs and merge them. With roughly equal L3 sizes, should give
|
||||
// a decent distribution.
|
||||
do
|
||||
{
|
||||
changed = false;
|
||||
for (size_t j = 0; j + 1 < ds.size(); ++j)
|
||||
{
|
||||
if (ds[j].cpus.size() + ds[j + 1].cpus.size() <= bundleSize)
|
||||
{
|
||||
changed = true;
|
||||
ds[j].cpus.merge(ds[j + 1].cpus);
|
||||
ds.erase(ds.begin() + j + 1);
|
||||
}
|
||||
}
|
||||
// ds.size() has decreased if changed is true, so this loop will terminate
|
||||
} while (changed);
|
||||
for (const L3Domain& d : ds)
|
||||
{
|
||||
const NumaIndex dn = n++;
|
||||
for (CpuIndex cpu : d.cpus)
|
||||
{
|
||||
cfg.add_cpu_to_node(dn, cpu);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
};
|
||||
|
||||
class NumaReplicationContext;
|
||||
@@ -1263,6 +1500,141 @@ class LazyNumaReplicated: public NumaReplicatedBase {
|
||||
}
|
||||
};
|
||||
|
||||
// Utilizes shared memory.
|
||||
template<typename T>
|
||||
class LazyNumaReplicatedSystemWide: public NumaReplicatedBase {
|
||||
public:
|
||||
using ReplicatorFuncType = std::function<T(const T&)>;
|
||||
|
||||
LazyNumaReplicatedSystemWide(NumaReplicationContext& ctx) :
|
||||
NumaReplicatedBase(ctx) {
|
||||
prepare_replicate_from(std::make_unique<T>());
|
||||
}
|
||||
|
||||
LazyNumaReplicatedSystemWide(NumaReplicationContext& ctx, std::unique_ptr<T>&& source) :
|
||||
NumaReplicatedBase(ctx) {
|
||||
prepare_replicate_from(std::move(source));
|
||||
}
|
||||
|
||||
LazyNumaReplicatedSystemWide(const LazyNumaReplicatedSystemWide&) = delete;
|
||||
LazyNumaReplicatedSystemWide(LazyNumaReplicatedSystemWide&& other) noexcept :
|
||||
NumaReplicatedBase(std::move(other)),
|
||||
instances(std::exchange(other.instances, {})) {}
|
||||
|
||||
LazyNumaReplicatedSystemWide& operator=(const LazyNumaReplicatedSystemWide&) = delete;
|
||||
LazyNumaReplicatedSystemWide& operator=(LazyNumaReplicatedSystemWide&& other) noexcept {
|
||||
NumaReplicatedBase::operator=(*this, std::move(other));
|
||||
instances = std::exchange(other.instances, {});
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
LazyNumaReplicatedSystemWide& operator=(std::unique_ptr<T>&& source) {
|
||||
prepare_replicate_from(std::move(source));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~LazyNumaReplicatedSystemWide() override = default;
|
||||
|
||||
const T& operator[](NumaReplicatedAccessToken token) const {
|
||||
assert(token.get_numa_index() < instances.size());
|
||||
ensure_present(token.get_numa_index());
|
||||
return *(instances[token.get_numa_index()]);
|
||||
}
|
||||
|
||||
const T& operator*() const { return *(instances[0]); }
|
||||
|
||||
const T* operator->() const { return &*instances[0]; }
|
||||
|
||||
std::vector<std::pair<SystemWideSharedConstantAllocationStatus, std::optional<std::string>>>
|
||||
get_status_and_errors() const {
|
||||
std::vector<std::pair<SystemWideSharedConstantAllocationStatus, std::optional<std::string>>>
|
||||
status;
|
||||
status.reserve(instances.size());
|
||||
|
||||
for (const auto& instance : instances)
|
||||
{
|
||||
status.emplace_back(instance.get_status(), instance.get_error_message());
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
template<typename FuncT>
|
||||
void modify_and_replicate(FuncT&& f) {
|
||||
auto source = std::make_unique<T>(*instances[0]);
|
||||
std::forward<FuncT>(f)(*source);
|
||||
prepare_replicate_from(std::move(source));
|
||||
}
|
||||
|
||||
void on_numa_config_changed() override {
|
||||
// Use the first one as the source. It doesn't matter which one we use,
|
||||
// because they all must be identical, but the first one is guaranteed to exist.
|
||||
auto source = std::make_unique<T>(*instances[0]);
|
||||
prepare_replicate_from(std::move(source));
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::vector<SystemWideSharedConstant<T>> instances;
|
||||
mutable std::mutex mutex;
|
||||
|
||||
std::size_t get_discriminator(NumaIndex idx) const {
|
||||
const NumaConfig& cfg = get_numa_config();
|
||||
const NumaConfig& cfg_sys = NumaConfig::from_system(SystemNumaPolicy{}, false);
|
||||
// as a discriminator, locate the hardware/system numadomain this cpuindex belongs to
|
||||
CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex
|
||||
NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0;
|
||||
std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx);
|
||||
return static_cast<std::size_t>(hash_string(s));
|
||||
}
|
||||
|
||||
void ensure_present(NumaIndex idx) const {
|
||||
assert(idx < instances.size());
|
||||
|
||||
if (instances[idx] != nullptr)
|
||||
return;
|
||||
|
||||
assert(idx != 0);
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
// Check again for races.
|
||||
if (instances[idx] != nullptr)
|
||||
return;
|
||||
|
||||
const NumaConfig& cfg = get_numa_config();
|
||||
cfg.execute_on_numa_node(idx, [this, idx]() {
|
||||
instances[idx] = SystemWideSharedConstant<T>(*instances[0], get_discriminator(idx));
|
||||
});
|
||||
}
|
||||
|
||||
void prepare_replicate_from(std::unique_ptr<T>&& source) {
|
||||
instances.clear();
|
||||
|
||||
const NumaConfig& cfg = get_numa_config();
|
||||
// We just need to make sure the first instance is there.
|
||||
// Note that we cannot move here as we need to reallocate the data
|
||||
// on the correct NUMA node.
|
||||
// Even in the case of a single NUMA node we have to copy since it's shared memory.
|
||||
if (cfg.requires_memory_replication())
|
||||
{
|
||||
assert(cfg.num_numa_nodes() > 0);
|
||||
|
||||
cfg.execute_on_numa_node(0, [this, &source]() {
|
||||
instances.emplace_back(SystemWideSharedConstant<T>(*source, get_discriminator(0)));
|
||||
});
|
||||
|
||||
// Prepare others for lazy init.
|
||||
instances.resize(cfg.num_numa_nodes());
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(cfg.num_numa_nodes() == 1);
|
||||
instances.emplace_back(SystemWideSharedConstant<T>(*source, get_discriminator(0)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class NumaReplicationContext {
|
||||
public:
|
||||
NumaReplicationContext(NumaConfig&& cfg) :
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -56,9 +56,9 @@ uint64_t perft(Position& pos, Depth depth) {
|
||||
}
|
||||
|
||||
inline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) {
|
||||
StateListPtr states(new std::deque<StateInfo>(1));
|
||||
Position p;
|
||||
p.set(fen, isChess960, &states->back());
|
||||
StateInfo st;
|
||||
Position p;
|
||||
p.set(fen, isChess960, &st);
|
||||
|
||||
return perft<true>(p, depth);
|
||||
}
|
||||
|
||||
+337
-84
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "bitboard.h"
|
||||
#include "history.h"
|
||||
#include "misc.h"
|
||||
#include "movegen.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
@@ -48,14 +49,15 @@ Key psq[PIECE_NB][SQUARE_NB];
|
||||
Key enpassant[FILE_NB];
|
||||
Key castling[CASTLING_RIGHT_NB];
|
||||
Key side, noPawns;
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
|
||||
|
||||
constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
|
||||
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING};
|
||||
static constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
|
||||
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING};
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -64,12 +66,15 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
||||
|
||||
os << "\n +---+---+---+---+---+---+---+---+\n";
|
||||
|
||||
for (Rank r = RANK_8; r >= RANK_1; --r)
|
||||
for (Rank r = RANK_8;; --r)
|
||||
{
|
||||
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||
os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
|
||||
|
||||
os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
|
||||
|
||||
if (r == RANK_1)
|
||||
break;
|
||||
}
|
||||
|
||||
os << " a b c d e f g h\n"
|
||||
@@ -79,7 +84,7 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
|
||||
for (Bitboard b = pos.checkers(); b;)
|
||||
os << UCIEngine::square(pop_lsb(b)) << " ";
|
||||
|
||||
if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
|
||||
if (Tablebases::MaxCardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
|
||||
{
|
||||
StateInfo st;
|
||||
|
||||
@@ -200,7 +205,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) {
|
||||
Square sq = SQ_A8;
|
||||
std::istringstream ss(fenStr);
|
||||
|
||||
std::memset(this, 0, sizeof(Position));
|
||||
std::memset(reinterpret_cast<char*>(this), 0, sizeof(Position));
|
||||
std::memset(si, 0, sizeof(StateInfo));
|
||||
st = si;
|
||||
|
||||
@@ -270,7 +275,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si) {
|
||||
// a) side to move have a pawn threatening epSquare
|
||||
// b) there is an enemy pawn in front of epSquare
|
||||
// c) there is no piece on epSquare or behind epSquare
|
||||
enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN)
|
||||
enpassant = attacks_bb<PAWN>(st->epSquare, ~sideToMove) & pieces(sideToMove, PAWN)
|
||||
&& (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
|
||||
&& !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));
|
||||
}
|
||||
@@ -321,7 +326,7 @@ void Position::set_check_info() const {
|
||||
|
||||
Square ksq = square<KING>(~sideToMove);
|
||||
|
||||
st->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq);
|
||||
st->checkSquares[PAWN] = attacks_bb<PAWN>(ksq, ~sideToMove);
|
||||
st->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);
|
||||
st->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());
|
||||
st->checkSquares[ROOK] = attacks_bb<ROOK>(ksq, pieces());
|
||||
@@ -335,8 +340,8 @@ void Position::set_check_info() const {
|
||||
// The function is only used when a new position is set up
|
||||
void Position::set_state() const {
|
||||
|
||||
st->key = st->materialKey = 0;
|
||||
st->minorPieceKey = 0;
|
||||
st->key = 0;
|
||||
st->minorPieceKey = 0;
|
||||
st->nonPawnKey[WHITE] = st->nonPawnKey[BLACK] = 0;
|
||||
st->pawnKey = Zobrist::noPawns;
|
||||
st->nonPawnMaterial[WHITE] = st->nonPawnMaterial[BLACK] = VALUE_ZERO;
|
||||
@@ -374,10 +379,15 @@ void Position::set_state() const {
|
||||
st->key ^= Zobrist::side;
|
||||
|
||||
st->key ^= Zobrist::castling[st->castlingRights];
|
||||
st->materialKey = compute_material_key();
|
||||
}
|
||||
|
||||
Key Position::compute_material_key() const {
|
||||
Key k = 0;
|
||||
for (Piece pc : Pieces)
|
||||
for (int cnt = 0; cnt < pieceCount[pc]; ++cnt)
|
||||
st->materialKey ^= Zobrist::psq[pc][8 + cnt];
|
||||
k ^= Zobrist::psq[pc][8 + cnt];
|
||||
return k;
|
||||
}
|
||||
|
||||
|
||||
@@ -409,7 +419,7 @@ string Position::fen() const {
|
||||
int emptyCnt;
|
||||
std::ostringstream ss;
|
||||
|
||||
for (Rank r = RANK_8; r >= RANK_1; --r)
|
||||
for (Rank r = RANK_8;; --r)
|
||||
{
|
||||
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||
{
|
||||
@@ -423,8 +433,9 @@ string Position::fen() const {
|
||||
ss << PieceToChar[piece_on(make_square(f, r))];
|
||||
}
|
||||
|
||||
if (r > RANK_1)
|
||||
ss << '/';
|
||||
if (r == RANK_1)
|
||||
break;
|
||||
ss << '/';
|
||||
}
|
||||
|
||||
ss << (sideToMove == WHITE ? " w " : " b ");
|
||||
@@ -487,8 +498,8 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
|
||||
|
||||
return (attacks_bb<ROOK>(s, occupied) & pieces(ROOK, QUEEN))
|
||||
| (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
|
||||
| (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN))
|
||||
| (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN))
|
||||
| (attacks_bb<PAWN>(s, BLACK) & pieces(WHITE, PAWN))
|
||||
| (attacks_bb<PAWN>(s, WHITE) & pieces(BLACK, PAWN))
|
||||
| (attacks_bb<KNIGHT>(s) & pieces(KNIGHT)) | (attacks_bb<KING>(s) & pieces(KING));
|
||||
}
|
||||
|
||||
@@ -498,7 +509,7 @@ bool Position::attackers_to_exist(Square s, Bitboard occupied, Color c) const {
|
||||
&& (attacks_bb<ROOK>(s, occupied) & pieces(c, ROOK, QUEEN)))
|
||||
|| ((attacks_bb<BISHOP>(s) & pieces(c, BISHOP, QUEEN))
|
||||
&& (attacks_bb<BISHOP>(s, occupied) & pieces(c, BISHOP, QUEEN)))
|
||||
|| (((pawn_attacks_bb(~c, s) & pieces(PAWN)) | (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
|
||||
|| (((attacks_bb<PAWN>(s, ~c) & pieces(PAWN)) | (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
|
||||
| (attacks_bb<KING>(s) & pieces(KING)))
|
||||
& pieces(c));
|
||||
}
|
||||
@@ -597,10 +608,14 @@ bool Position::pseudo_legal(const Move m) const {
|
||||
if ((Rank8BB | Rank1BB) & to)
|
||||
return false;
|
||||
|
||||
if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
|
||||
&& !((from + pawn_push(us) == to) && empty(to)) // Not a single push
|
||||
&& !((from + 2 * pawn_push(us) == to) // Not a double push
|
||||
&& (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us))))
|
||||
// Check if it's a valid capture, single push, or double push
|
||||
const bool isCapture = bool(attacks_bb<PAWN>(from, us) & pieces(~us) & to);
|
||||
const bool isSinglePush = (from + pawn_push(us) == to) && empty(to);
|
||||
const bool isDoublePush = (from + 2 * pawn_push(us) == to)
|
||||
&& (relative_rank(us, from) == RANK_2) && empty(to)
|
||||
&& empty(to - pawn_push(us));
|
||||
|
||||
if (!(isCapture || isSinglePush || isDoublePush))
|
||||
return false;
|
||||
}
|
||||
else if (!(attacks_bb(type_of(pc), from, pieces()) & to))
|
||||
@@ -682,11 +697,14 @@ bool Position::gives_check(Move m) const {
|
||||
// to a StateInfo object. The move is assumed to be legal. Pseudo-legal
|
||||
// moves should be filtered out before this function is called.
|
||||
// If a pointer to the TT table is passed, the entry for the new position
|
||||
// will be prefetched
|
||||
DirtyPiece Position::do_move(Move m,
|
||||
StateInfo& newSt,
|
||||
bool givesCheck,
|
||||
const TranspositionTable* tt = nullptr) {
|
||||
// will be prefetched, and likewise for shared history.
|
||||
void Position::do_move(Move m,
|
||||
StateInfo& newSt,
|
||||
bool givesCheck,
|
||||
DirtyPiece& dp,
|
||||
DirtyThreats& dts,
|
||||
const TranspositionTable* tt = nullptr,
|
||||
const SharedHistories* history = nullptr) {
|
||||
|
||||
assert(m.is_ok());
|
||||
assert(&newSt != st);
|
||||
@@ -698,7 +716,6 @@ DirtyPiece Position::do_move(Move m,
|
||||
// our state pointer to point to the new (ready to be updated) state.
|
||||
std::memcpy(&newSt, st, offsetof(StateInfo, key));
|
||||
newSt.previous = st;
|
||||
st->next = &newSt;
|
||||
st = &newSt;
|
||||
|
||||
// Increment ply counters. In particular, rule50 will be reset to zero later on
|
||||
@@ -707,9 +724,6 @@ DirtyPiece Position::do_move(Move m,
|
||||
++st->rule50;
|
||||
++st->pliesFromNull;
|
||||
|
||||
DirtyPiece dp;
|
||||
dp.dirty_num = 1;
|
||||
|
||||
Color us = sideToMove;
|
||||
Color them = ~us;
|
||||
Square from = m.from_sq();
|
||||
@@ -717,6 +731,16 @@ DirtyPiece Position::do_move(Move m,
|
||||
Piece pc = piece_on(from);
|
||||
Piece captured = m.type_of() == EN_PASSANT ? make_piece(them, PAWN) : piece_on(to);
|
||||
|
||||
bool checkEP = false;
|
||||
|
||||
dp.pc = pc;
|
||||
dp.from = from;
|
||||
dp.to = to;
|
||||
dp.add_sq = SQ_NONE;
|
||||
dts.us = us;
|
||||
dts.prevKsq = square<KING>(us);
|
||||
dts.threatenedSqs = dts.threateningSqs = 0;
|
||||
|
||||
assert(color_of(pc) == us);
|
||||
assert(captured == NO_PIECE || color_of(captured) == (m.type_of() != CASTLING ? them : us));
|
||||
assert(type_of(captured) != KING);
|
||||
@@ -727,14 +751,13 @@ DirtyPiece Position::do_move(Move m,
|
||||
assert(captured == make_piece(us, ROOK));
|
||||
|
||||
Square rfrom, rto;
|
||||
do_castling<true>(us, from, to, rfrom, rto, &dp);
|
||||
do_castling<true>(us, from, to, rfrom, rto, &dts, &dp);
|
||||
|
||||
k ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
|
||||
st->nonPawnKey[us] ^= Zobrist::psq[captured][rfrom] ^ Zobrist::psq[captured][rto];
|
||||
captured = NO_PIECE;
|
||||
}
|
||||
|
||||
if (captured)
|
||||
else if (captured)
|
||||
{
|
||||
Square capsq = to;
|
||||
|
||||
@@ -751,6 +774,9 @@ DirtyPiece Position::do_move(Move m,
|
||||
assert(relative_rank(us, to) == RANK_6);
|
||||
assert(piece_on(to) == NO_PIECE);
|
||||
assert(piece_on(capsq) == make_piece(them, PAWN));
|
||||
|
||||
// Update board and piece lists in ep case, normal captures are updated later
|
||||
remove_piece(capsq, &dts);
|
||||
}
|
||||
|
||||
st->pawnKey ^= Zobrist::psq[captured][capsq];
|
||||
@@ -764,20 +790,18 @@ DirtyPiece Position::do_move(Move m,
|
||||
st->minorPieceKey ^= Zobrist::psq[captured][capsq];
|
||||
}
|
||||
|
||||
dp.dirty_num = 2; // 1 piece moved, 1 piece captured
|
||||
dp.piece[1] = captured;
|
||||
dp.from[1] = capsq;
|
||||
dp.to[1] = SQ_NONE;
|
||||
|
||||
// Update board and piece lists
|
||||
remove_piece(capsq);
|
||||
dp.remove_pc = captured;
|
||||
dp.remove_sq = capsq;
|
||||
|
||||
k ^= Zobrist::psq[captured][capsq];
|
||||
st->materialKey ^= Zobrist::psq[captured][8 + pieceCount[captured]];
|
||||
st->materialKey ^=
|
||||
Zobrist::psq[captured][8 + pieceCount[captured] - (m.type_of() != EN_PASSANT)];
|
||||
|
||||
// Reset rule 50 counter
|
||||
st->rule50 = 0;
|
||||
}
|
||||
else
|
||||
dp.remove_sq = SQ_NONE;
|
||||
|
||||
// Update hash key
|
||||
k ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
||||
@@ -800,23 +824,21 @@ DirtyPiece Position::do_move(Move m,
|
||||
// Move the piece. The tricky Chess960 castling is handled earlier
|
||||
if (m.type_of() != CASTLING)
|
||||
{
|
||||
dp.piece[0] = pc;
|
||||
dp.from[0] = from;
|
||||
dp.to[0] = to;
|
||||
|
||||
move_piece(from, to);
|
||||
if (captured && m.type_of() != EN_PASSANT)
|
||||
{
|
||||
remove_piece(from, &dts);
|
||||
swap_piece(to, pc, &dts);
|
||||
}
|
||||
else
|
||||
move_piece(from, to, &dts);
|
||||
}
|
||||
|
||||
// If the moving piece is a pawn do some special extra work
|
||||
if (type_of(pc) == PAWN)
|
||||
{
|
||||
// Set en passant square if the moved pawn can be captured
|
||||
if ((int(to) ^ int(from)) == 16
|
||||
&& (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
|
||||
{
|
||||
st->epSquare = to - pawn_push(us);
|
||||
k ^= Zobrist::enpassant[file_of(st->epSquare)];
|
||||
}
|
||||
// Check later if the en passant square needs to be set
|
||||
if ((int(to) ^ int(from)) == 16)
|
||||
checkEP = true;
|
||||
|
||||
else if (m.type_of() == PROMOTION)
|
||||
{
|
||||
@@ -826,21 +848,18 @@ DirtyPiece Position::do_move(Move m,
|
||||
assert(relative_rank(us, to) == RANK_8);
|
||||
assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
|
||||
|
||||
remove_piece(to);
|
||||
put_piece(promotion, to);
|
||||
swap_piece(to, promotion, &dts);
|
||||
|
||||
// Promoting pawn to SQ_NONE, promoted piece from SQ_NONE
|
||||
dp.to[0] = SQ_NONE;
|
||||
dp.piece[dp.dirty_num] = promotion;
|
||||
dp.from[dp.dirty_num] = SQ_NONE;
|
||||
dp.to[dp.dirty_num] = to;
|
||||
dp.dirty_num++;
|
||||
dp.add_pc = promotion;
|
||||
dp.add_sq = to;
|
||||
dp.to = SQ_NONE;
|
||||
|
||||
// Update hash keys
|
||||
// Zobrist::psq[pc][to] is zero, so we don't need to clear it
|
||||
k ^= Zobrist::psq[promotion][to];
|
||||
st->materialKey ^= Zobrist::psq[promotion][8 + pieceCount[promotion] - 1]
|
||||
^ Zobrist::psq[pc][8 + pieceCount[pc]];
|
||||
st->nonPawnKey[us] ^= Zobrist::psq[promotion][to];
|
||||
|
||||
if (promotionType <= BISHOP)
|
||||
st->minorPieceKey ^= Zobrist::psq[promotion][to];
|
||||
@@ -864,10 +883,18 @@ DirtyPiece Position::do_move(Move m,
|
||||
st->minorPieceKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to];
|
||||
}
|
||||
|
||||
// Update the key with the final value
|
||||
st->key = k;
|
||||
if (tt)
|
||||
prefetch(tt->first_entry(key()));
|
||||
// If en passant is impossible, then k will not change and we can prefetch earlier
|
||||
if (tt && !checkEP)
|
||||
prefetch(tt->first_entry(adjust_key50(k)));
|
||||
|
||||
if (history)
|
||||
{
|
||||
prefetch(&history->pawn_entry(*this)[pc][to]);
|
||||
prefetch(&history->pawn_correction_entry(*this));
|
||||
prefetch(&history->minor_piece_correction_entry(*this));
|
||||
prefetch(&history->nonpawn_correction_entry<WHITE>(*this));
|
||||
prefetch(&history->nonpawn_correction_entry<BLACK>(*this));
|
||||
}
|
||||
|
||||
// Set capture piece
|
||||
st->capturedPiece = captured;
|
||||
@@ -880,6 +907,63 @@ DirtyPiece Position::do_move(Move m,
|
||||
// Update king attacks used for fast check detection
|
||||
set_check_info();
|
||||
|
||||
// Accurate e.p. info is needed for correct zobrist key generation and 3-fold checking
|
||||
while (checkEP)
|
||||
{
|
||||
auto updateEpSquare = [&] {
|
||||
st->epSquare = to - pawn_push(us);
|
||||
k ^= Zobrist::enpassant[file_of(st->epSquare)];
|
||||
};
|
||||
|
||||
Bitboard pawns = attacks_bb<PAWN>(to - pawn_push(us), us) & pieces(them, PAWN);
|
||||
|
||||
// If there are no pawns attacking the ep square, ep is not possible
|
||||
if (!pawns)
|
||||
break;
|
||||
|
||||
// If there are checkers other than the to be captured pawn, ep is never legal
|
||||
if (checkers() & ~square_bb(to))
|
||||
break;
|
||||
|
||||
if (more_than_one(pawns))
|
||||
{
|
||||
// If there are two pawns potentially being able to capture and at least one
|
||||
// is not pinned, ep is legal as there are no horizontal exposed checks
|
||||
if (!more_than_one(blockers_for_king(them) & pawns))
|
||||
{
|
||||
updateEpSquare();
|
||||
break;
|
||||
}
|
||||
|
||||
// If there is no pawn on our king's file, and thus both pawns are pinned
|
||||
// by bishops, ep is not legal as the king square must be in front of the to square.
|
||||
// And because the ep square and the king are not on a common diagonal, either ep capture
|
||||
// would expose the king to a check from one of the bishops
|
||||
if (!(file_bb(square<KING>(them)) & pawns))
|
||||
break;
|
||||
|
||||
// Otherwise remove the pawn on the king file, as an ep capture by it can never be legal and the
|
||||
// check below relies on there only being one pawn
|
||||
pawns &= ~file_bb(square<KING>(them));
|
||||
}
|
||||
|
||||
Square ksq = square<KING>(them);
|
||||
Square capsq = to;
|
||||
Bitboard occupied = (pieces() ^ lsb(pawns) ^ capsq) | (to - pawn_push(us));
|
||||
|
||||
// If our king is not attacked after making the move, ep is legal.
|
||||
if (!(attacks_bb<ROOK>(ksq, occupied) & pieces(us, QUEEN, ROOK))
|
||||
&& !(attacks_bb<BISHOP>(ksq, occupied) & pieces(us, QUEEN, BISHOP)))
|
||||
updateEpSquare();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the key with the final value
|
||||
st->key = k;
|
||||
if (tt)
|
||||
prefetch(tt->first_entry(key()));
|
||||
|
||||
// Calculate the repetition info. It is the ply distance from the previous
|
||||
// occurrence of the same position, negative in the 3-fold case, or zero
|
||||
// if the position was not repeated.
|
||||
@@ -899,9 +983,14 @@ DirtyPiece Position::do_move(Move m,
|
||||
}
|
||||
}
|
||||
|
||||
dts.ksq = square<KING>(us);
|
||||
|
||||
assert(pos_is_ok());
|
||||
|
||||
return dp;
|
||||
assert(dp.pc != NO_PIECE);
|
||||
assert(!(bool(captured) || m.type_of() == CASTLING) ^ (dp.remove_sq != SQ_NONE));
|
||||
assert(dp.from != SQ_NONE);
|
||||
assert(!(dp.add_sq != SQ_NONE) ^ (m.type_of() == PROMOTION || m.type_of() == CASTLING));
|
||||
}
|
||||
|
||||
|
||||
@@ -967,12 +1056,172 @@ void Position::undo_move(Move m) {
|
||||
assert(pos_is_ok());
|
||||
}
|
||||
|
||||
template<bool PutPiece>
|
||||
inline void add_dirty_threat(
|
||||
DirtyThreats* const dts, Piece pc, Piece threatened, Square s, Square threatenedSq) {
|
||||
if (PutPiece)
|
||||
{
|
||||
dts->threatenedSqs |= square_bb(threatenedSq);
|
||||
dts->threateningSqs |= square_bb(s);
|
||||
}
|
||||
|
||||
dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece});
|
||||
}
|
||||
|
||||
#ifdef USE_AVX512ICL
|
||||
// Given a DirtyThreat template and bit offsets to insert the piece type and square, write the threats
|
||||
// present at the given bitboard.
|
||||
template<int SqShift, int PcShift>
|
||||
void write_multiple_dirties(const Position& p,
|
||||
Bitboard mask,
|
||||
DirtyThreat dt_template,
|
||||
DirtyThreats* dts) {
|
||||
static_assert(sizeof(DirtyThreat) == 4);
|
||||
|
||||
const __m512i board = _mm512_loadu_si512(p.piece_array().data());
|
||||
const __m512i AllSquares = _mm512_set_epi8(
|
||||
63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41,
|
||||
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18,
|
||||
17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
|
||||
|
||||
const int dt_count = popcount(mask);
|
||||
assert(dt_count <= 16);
|
||||
|
||||
const __m512i template_v = _mm512_set1_epi32(dt_template.raw());
|
||||
auto* write = dts->list.make_space(dt_count);
|
||||
|
||||
// Extract the list of squares and upconvert to 32 bits. There are never more than 16
|
||||
// incoming threats so this is sufficient.
|
||||
__m512i threat_squares = _mm512_maskz_compress_epi8(mask, AllSquares);
|
||||
threat_squares = _mm512_cvtepi8_epi32(_mm512_castsi512_si128(threat_squares));
|
||||
|
||||
__m512i threat_pieces =
|
||||
_mm512_maskz_permutexvar_epi8(0x1111111111111111ULL, threat_squares, board);
|
||||
|
||||
// Shift the piece and square into place
|
||||
threat_squares = _mm512_slli_epi32(threat_squares, SqShift);
|
||||
threat_pieces = _mm512_slli_epi32(threat_pieces, PcShift);
|
||||
|
||||
const __m512i dirties =
|
||||
_mm512_ternarylogic_epi32(template_v, threat_squares, threat_pieces, 254 /* A | B | C */);
|
||||
_mm512_storeu_si512(reinterpret_cast<__m512i*>(write), dirties);
|
||||
}
|
||||
#endif
|
||||
|
||||
template<bool PutPiece, bool ComputeRay>
|
||||
void Position::update_piece_threats(Piece pc,
|
||||
Square s,
|
||||
DirtyThreats* const dts,
|
||||
[[maybe_unused]] Bitboard noRaysContaining) const {
|
||||
const Bitboard occupied = pieces();
|
||||
const Bitboard rookQueens = pieces(ROOK, QUEEN);
|
||||
const Bitboard bishopQueens = pieces(BISHOP, QUEEN);
|
||||
const Bitboard knights = pieces(KNIGHT);
|
||||
const Bitboard kings = pieces(KING);
|
||||
const Bitboard whitePawns = pieces(WHITE, PAWN);
|
||||
const Bitboard blackPawns = pieces(BLACK, PAWN);
|
||||
|
||||
const Bitboard rAttacks = attacks_bb<ROOK>(s, occupied);
|
||||
const Bitboard bAttacks = attacks_bb<BISHOP>(s, occupied);
|
||||
|
||||
Bitboard threatened = attacks_bb(pc, s, occupied) & occupied;
|
||||
Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks);
|
||||
Bitboard incoming_threats =
|
||||
(PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb<PAWN>(s, WHITE) & blackPawns)
|
||||
| (attacks_bb<PAWN>(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings);
|
||||
|
||||
#ifdef USE_AVX512ICL
|
||||
if (threatened)
|
||||
{
|
||||
if constexpr (PutPiece)
|
||||
{
|
||||
dts->threatenedSqs |= threatened;
|
||||
dts->threateningSqs |= square_bb(s);
|
||||
}
|
||||
|
||||
DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece};
|
||||
write_multiple_dirties<DirtyThreat::ThreatenedSqOffset, DirtyThreat::ThreatenedPcOffset>(
|
||||
*this, threatened, dt_template, dts);
|
||||
}
|
||||
|
||||
Bitboard all_attackers = sliders | incoming_threats;
|
||||
if (!all_attackers)
|
||||
return; // Square s is threatened iff there's at least one attacker
|
||||
|
||||
if constexpr (PutPiece)
|
||||
{
|
||||
dts->threatenedSqs |= square_bb(s);
|
||||
dts->threateningSqs |= all_attackers;
|
||||
}
|
||||
|
||||
DirtyThreat dt_template{NO_PIECE, pc, Square(0), s, PutPiece};
|
||||
write_multiple_dirties<DirtyThreat::PcSqOffset, DirtyThreat::PcOffset>(*this, all_attackers,
|
||||
dt_template, dts);
|
||||
#else
|
||||
while (threatened)
|
||||
{
|
||||
Square threatenedSq = pop_lsb(threatened);
|
||||
Piece threatenedPc = piece_on(threatenedSq);
|
||||
|
||||
assert(threatenedSq != s);
|
||||
assert(threatenedPc);
|
||||
|
||||
add_dirty_threat<PutPiece>(dts, pc, threatenedPc, s, threatenedSq);
|
||||
}
|
||||
#endif
|
||||
|
||||
if constexpr (ComputeRay)
|
||||
{
|
||||
while (sliders)
|
||||
{
|
||||
Square sliderSq = pop_lsb(sliders);
|
||||
Piece slider = piece_on(sliderSq);
|
||||
|
||||
const Bitboard ray = RayPassBB[sliderSq][s] & ~BetweenBB[sliderSq][s];
|
||||
const Bitboard discovered = ray & (rAttacks | bAttacks) & occupied;
|
||||
|
||||
assert(!more_than_one(discovered));
|
||||
if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining)
|
||||
{
|
||||
const Square threatenedSq = lsb(discovered);
|
||||
const Piece threatenedPc = piece_on(threatenedSq);
|
||||
add_dirty_threat<!PutPiece>(dts, slider, threatenedPc, sliderSq, threatenedSq);
|
||||
}
|
||||
|
||||
#ifndef USE_AVX512ICL // for ICL, direct threats were processed earlier (all_attackers)
|
||||
add_dirty_threat<PutPiece>(dts, slider, pc, sliderSq, s);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
incoming_threats |= sliders;
|
||||
}
|
||||
|
||||
#ifndef USE_AVX512ICL
|
||||
while (incoming_threats)
|
||||
{
|
||||
Square srcSq = pop_lsb(incoming_threats);
|
||||
Piece srcPc = piece_on(srcSq);
|
||||
|
||||
assert(srcSq != s);
|
||||
assert(srcPc != NO_PIECE);
|
||||
|
||||
add_dirty_threat<PutPiece>(dts, srcPc, pc, srcSq, s);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Helper used to do/undo a castling move. This is a bit
|
||||
// tricky in Chess960 where from/to squares can overlap.
|
||||
template<bool Do>
|
||||
void Position::do_castling(
|
||||
Color us, Square from, Square& to, Square& rfrom, Square& rto, DirtyPiece* const dp) {
|
||||
void Position::do_castling(Color us,
|
||||
Square from,
|
||||
Square& to,
|
||||
Square& rfrom,
|
||||
Square& rto,
|
||||
DirtyThreats* const dts,
|
||||
DirtyPiece* const dp) {
|
||||
|
||||
bool kingSide = to > from;
|
||||
rfrom = to; // Castling is encoded as "king captures friendly rook"
|
||||
@@ -983,22 +1232,17 @@ void Position::do_castling(
|
||||
|
||||
if (Do)
|
||||
{
|
||||
dp->piece[0] = make_piece(us, KING);
|
||||
dp->from[0] = from;
|
||||
dp->to[0] = to;
|
||||
dp->piece[1] = make_piece(us, ROOK);
|
||||
dp->from[1] = rfrom;
|
||||
dp->to[1] = rto;
|
||||
dp->dirty_num = 2;
|
||||
dp->to = to;
|
||||
dp->remove_pc = dp->add_pc = make_piece(us, ROOK);
|
||||
dp->remove_sq = rfrom;
|
||||
dp->add_sq = rto;
|
||||
}
|
||||
|
||||
// Remove both pieces first since squares could overlap in Chess960
|
||||
remove_piece(Do ? from : to);
|
||||
remove_piece(Do ? rfrom : rto);
|
||||
board[Do ? from : to] = board[Do ? rfrom : rto] =
|
||||
NO_PIECE; // remove_piece does not do this for us
|
||||
put_piece(make_piece(us, KING), Do ? to : from);
|
||||
put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
|
||||
remove_piece(Do ? from : to, dts);
|
||||
remove_piece(Do ? rfrom : rto, dts);
|
||||
put_piece(make_piece(us, KING), Do ? to : from, dts);
|
||||
put_piece(make_piece(us, ROOK), Do ? rto : rfrom, dts);
|
||||
}
|
||||
|
||||
|
||||
@@ -1012,7 +1256,6 @@ void Position::do_null_move(StateInfo& newSt, const TranspositionTable& tt) {
|
||||
std::memcpy(&newSt, st, sizeof(StateInfo));
|
||||
|
||||
newSt.previous = st;
|
||||
st->next = &newSt;
|
||||
st = &newSt;
|
||||
|
||||
if (st->epSquare != SQ_NONE)
|
||||
@@ -1059,6 +1302,8 @@ bool Position::see_ge(Move m, int threshold) const {
|
||||
|
||||
Square from = m.from_sq(), to = m.to_sq();
|
||||
|
||||
assert(piece_on(from) != NO_PIECE);
|
||||
|
||||
int swap = PieceValue[piece_on(to)] - threshold;
|
||||
if (swap < 0)
|
||||
return false;
|
||||
@@ -1236,10 +1481,13 @@ void Position::flip() {
|
||||
string f, token;
|
||||
std::stringstream ss(fen());
|
||||
|
||||
for (Rank r = RANK_8; r >= RANK_1; --r) // Piece placement
|
||||
for (Rank r = RANK_8;; --r) // Piece placement
|
||||
{
|
||||
std::getline(ss, token, r > RANK_1 ? '/' : ' ');
|
||||
f.insert(0, token + (f.empty() ? " " : "/"));
|
||||
|
||||
if (r == RANK_1)
|
||||
break;
|
||||
}
|
||||
|
||||
ss >> token; // Active color
|
||||
@@ -1263,6 +1511,9 @@ void Position::flip() {
|
||||
}
|
||||
|
||||
|
||||
bool Position::material_key_is_ok() const { return compute_material_key() == st->materialKey; }
|
||||
|
||||
|
||||
// Performs some consistency checks for the position object
|
||||
// and raise an assert if something wrong is detected.
|
||||
// This is meant to be helpful when debugging.
|
||||
@@ -1297,7 +1548,7 @@ bool Position::pos_is_ok() const {
|
||||
|
||||
for (Piece pc : Pieces)
|
||||
if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
|
||||
|| pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
|
||||
|| pieceCount[pc] != std::count(board.begin(), board.end(), pc))
|
||||
assert(0 && "pos_is_ok: Pieces");
|
||||
|
||||
for (Color c : {WHITE, BLACK})
|
||||
@@ -1312,6 +1563,8 @@ bool Position::pos_is_ok() const {
|
||||
assert(0 && "pos_is_ok: Castling");
|
||||
}
|
||||
|
||||
assert(material_key_is_ok() && "pos_is_ok: materialKey");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+98
-58
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -19,10 +19,12 @@
|
||||
#ifndef POSITION_H_INCLUDED
|
||||
#define POSITION_H_INCLUDED
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <deque>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
#include "bitboard.h"
|
||||
@@ -31,6 +33,7 @@
|
||||
namespace Stockfish {
|
||||
|
||||
class TranspositionTable;
|
||||
struct SharedHistories;
|
||||
|
||||
// StateInfo struct stores information needed to restore a Position object to
|
||||
// its previous state when we retract a move. Whenever a move is made on the
|
||||
@@ -53,7 +56,6 @@ struct StateInfo {
|
||||
Key key;
|
||||
Bitboard checkersBB;
|
||||
StateInfo* previous;
|
||||
StateInfo* next;
|
||||
Bitboard blockersForKing[COLOR_NB];
|
||||
Bitboard pinners[COLOR_NB];
|
||||
Bitboard checkSquares[PIECE_TYPE_NB];
|
||||
@@ -68,7 +70,6 @@ struct StateInfo {
|
||||
// elements are not invalidated upon list resizing.
|
||||
using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
|
||||
|
||||
|
||||
// Position class stores information regarding the board representation as
|
||||
// pieces, side to move, hash keys, castling info, etc. Important methods are
|
||||
// do_move() and undo_move(), used by the search to update node info when
|
||||
@@ -87,15 +88,16 @@ class Position {
|
||||
std::string fen() const;
|
||||
|
||||
// Position representation
|
||||
Bitboard pieces(PieceType pt = ALL_PIECES) const;
|
||||
Bitboard pieces() const; // All pieces
|
||||
template<typename... PieceTypes>
|
||||
Bitboard pieces(PieceType pt, PieceTypes... pts) const;
|
||||
Bitboard pieces(PieceTypes... pts) const;
|
||||
Bitboard pieces(Color c) const;
|
||||
template<typename... PieceTypes>
|
||||
Bitboard pieces(Color c, PieceTypes... pts) const;
|
||||
Piece piece_on(Square s) const;
|
||||
Square ep_square() const;
|
||||
bool empty(Square s) const;
|
||||
Bitboard pieces(Color c, PieceTypes... pts) const;
|
||||
Piece piece_on(Square s) const;
|
||||
const std::array<Piece, SQUARE_NB>& piece_array() const;
|
||||
Square ep_square() const;
|
||||
bool empty(Square s) const;
|
||||
template<PieceType Pt>
|
||||
int count(Color c) const;
|
||||
template<PieceType Pt>
|
||||
@@ -104,10 +106,9 @@ class Position {
|
||||
Square square(Color c) const;
|
||||
|
||||
// Castling
|
||||
CastlingRights castling_rights(Color c) const;
|
||||
bool can_castle(CastlingRights cr) const;
|
||||
bool castling_impeded(CastlingRights cr) const;
|
||||
Square castling_rook_square(CastlingRights cr) const;
|
||||
bool can_castle(CastlingRights cr) const;
|
||||
bool castling_impeded(CastlingRights cr) const;
|
||||
Square castling_rook_square(CastlingRights cr) const;
|
||||
|
||||
// Checking
|
||||
Bitboard checkers() const;
|
||||
@@ -133,11 +134,17 @@ class Position {
|
||||
Piece captured_piece() const;
|
||||
|
||||
// Doing and undoing moves
|
||||
void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt);
|
||||
DirtyPiece do_move(Move m, StateInfo& newSt, bool givesCheck, const TranspositionTable* tt);
|
||||
void undo_move(Move m);
|
||||
void do_null_move(StateInfo& newSt, const TranspositionTable& tt);
|
||||
void undo_null_move();
|
||||
void do_move(Move m, StateInfo& newSt, const TranspositionTable* tt);
|
||||
void do_move(Move m,
|
||||
StateInfo& newSt,
|
||||
bool givesCheck,
|
||||
DirtyPiece& dp,
|
||||
DirtyThreats& dts,
|
||||
const TranspositionTable* tt,
|
||||
const SharedHistories* worker);
|
||||
void undo_move(Move m);
|
||||
void do_null_move(StateInfo& newSt, const TranspositionTable& tt);
|
||||
void undo_null_move();
|
||||
|
||||
// Static Exchange Evaluation
|
||||
bool see_ge(Move m, int threshold = 0) const;
|
||||
@@ -163,44 +170,54 @@ class Position {
|
||||
|
||||
// Position consistency check, for debugging
|
||||
bool pos_is_ok() const;
|
||||
bool material_key_is_ok() const;
|
||||
void flip();
|
||||
|
||||
// Used by NNUE
|
||||
StateInfo* state() const;
|
||||
|
||||
void put_piece(Piece pc, Square s);
|
||||
void remove_piece(Square s);
|
||||
void put_piece(Piece pc, Square s, DirtyThreats* const dts = nullptr);
|
||||
void remove_piece(Square s, DirtyThreats* const dts = nullptr);
|
||||
void swap_piece(Square s, Piece pc, DirtyThreats* const dts = nullptr);
|
||||
|
||||
private:
|
||||
// Initialization helpers (used while setting up a position)
|
||||
void set_castling_right(Color c, Square rfrom);
|
||||
Key compute_material_key() const;
|
||||
void set_state() const;
|
||||
void set_check_info() const;
|
||||
|
||||
// Other helpers
|
||||
void move_piece(Square from, Square to);
|
||||
template<bool PutPiece, bool ComputeRay = true>
|
||||
void update_piece_threats(Piece pc,
|
||||
Square s,
|
||||
DirtyThreats* const dts,
|
||||
Bitboard noRaysContaining = -1ULL) const;
|
||||
void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr);
|
||||
template<bool Do>
|
||||
void do_castling(Color us,
|
||||
Square from,
|
||||
Square& to,
|
||||
Square& rfrom,
|
||||
Square& rto,
|
||||
DirtyPiece* const dp = nullptr);
|
||||
template<bool AfterMove>
|
||||
Key adjust_key50(Key k) const;
|
||||
void do_castling(Color us,
|
||||
Square from,
|
||||
Square& to,
|
||||
Square& rfrom,
|
||||
Square& rto,
|
||||
DirtyThreats* const dts = nullptr,
|
||||
DirtyPiece* const dp = nullptr);
|
||||
Key adjust_key50(Key k) const;
|
||||
|
||||
// Data members
|
||||
Piece board[SQUARE_NB];
|
||||
Bitboard byTypeBB[PIECE_TYPE_NB];
|
||||
Bitboard byColorBB[COLOR_NB];
|
||||
int pieceCount[PIECE_NB];
|
||||
int castlingRightsMask[SQUARE_NB];
|
||||
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
||||
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
||||
StateInfo* st;
|
||||
int gamePly;
|
||||
Color sideToMove;
|
||||
bool chess960;
|
||||
std::array<Piece, SQUARE_NB> board;
|
||||
std::array<Bitboard, PIECE_TYPE_NB> byTypeBB;
|
||||
std::array<Bitboard, COLOR_NB> byColorBB;
|
||||
|
||||
int pieceCount[PIECE_NB];
|
||||
int castlingRightsMask[SQUARE_NB];
|
||||
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
||||
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
||||
StateInfo* st;
|
||||
int gamePly;
|
||||
Color sideToMove;
|
||||
bool chess960;
|
||||
DirtyPiece scratch_dp;
|
||||
DirtyThreats scratch_dts;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Position& pos);
|
||||
@@ -212,15 +229,17 @@ inline Piece Position::piece_on(Square s) const {
|
||||
return board[s];
|
||||
}
|
||||
|
||||
inline const std::array<Piece, SQUARE_NB>& Position::piece_array() const { return board; }
|
||||
|
||||
inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; }
|
||||
|
||||
inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); }
|
||||
|
||||
inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; }
|
||||
inline Bitboard Position::pieces() const { return byTypeBB[ALL_PIECES]; }
|
||||
|
||||
template<typename... PieceTypes>
|
||||
inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const {
|
||||
return pieces(pt) | pieces(pts...);
|
||||
inline Bitboard Position::pieces(PieceTypes... pts) const {
|
||||
return (byTypeBB[pts] | ...);
|
||||
}
|
||||
|
||||
inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; }
|
||||
@@ -250,10 +269,6 @@ inline Square Position::ep_square() const { return st->epSquare; }
|
||||
|
||||
inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; }
|
||||
|
||||
inline CastlingRights Position::castling_rights(Color c) const {
|
||||
return c & CastlingRights(st->castlingRights);
|
||||
}
|
||||
|
||||
inline bool Position::castling_impeded(CastlingRights cr) const {
|
||||
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
|
||||
return pieces() & castlingPath[cr];
|
||||
@@ -290,11 +305,10 @@ inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; }
|
||||
|
||||
inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; }
|
||||
|
||||
inline Key Position::key() const { return adjust_key50<false>(st->key); }
|
||||
inline Key Position::key() const { return adjust_key50(st->key); }
|
||||
|
||||
template<bool AfterMove>
|
||||
inline Key Position::adjust_key50(Key k) const {
|
||||
return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
|
||||
return st->rule50 < 14 ? k : k ^ make_key((st->rule50 - 14) / 8);
|
||||
}
|
||||
|
||||
inline Key Position::pawn_key() const { return st->pawnKey; }
|
||||
@@ -332,18 +346,23 @@ inline bool Position::capture_stage(Move m) const {
|
||||
|
||||
inline Piece Position::captured_piece() const { return st->capturedPiece; }
|
||||
|
||||
inline void Position::put_piece(Piece pc, Square s) {
|
||||
|
||||
inline void Position::put_piece(Piece pc, Square s, DirtyThreats* const dts) {
|
||||
board[s] = pc;
|
||||
byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;
|
||||
byColorBB[color_of(pc)] |= s;
|
||||
pieceCount[pc]++;
|
||||
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<true>(pc, s, dts);
|
||||
}
|
||||
|
||||
inline void Position::remove_piece(Square s) {
|
||||
|
||||
inline void Position::remove_piece(Square s, DirtyThreats* const dts) {
|
||||
Piece pc = board[s];
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<false>(pc, s, dts);
|
||||
|
||||
byTypeBB[ALL_PIECES] ^= s;
|
||||
byTypeBB[type_of(pc)] ^= s;
|
||||
byColorBB[color_of(pc)] ^= s;
|
||||
@@ -352,19 +371,40 @@ inline void Position::remove_piece(Square s) {
|
||||
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
|
||||
}
|
||||
|
||||
inline void Position::move_piece(Square from, Square to) {
|
||||
|
||||
inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts) {
|
||||
Piece pc = board[from];
|
||||
Bitboard fromTo = from | to;
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<false>(pc, from, dts, fromTo);
|
||||
|
||||
byTypeBB[ALL_PIECES] ^= fromTo;
|
||||
byTypeBB[type_of(pc)] ^= fromTo;
|
||||
byColorBB[color_of(pc)] ^= fromTo;
|
||||
board[from] = NO_PIECE;
|
||||
board[to] = pc;
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<true>(pc, to, dts, fromTo);
|
||||
}
|
||||
|
||||
inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) {
|
||||
Piece old = board[s];
|
||||
|
||||
remove_piece(s);
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<false, false>(old, s, dts);
|
||||
|
||||
put_piece(pc, s);
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<true, false>(pc, s, dts);
|
||||
}
|
||||
|
||||
inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) {
|
||||
do_move(m, newSt, gives_check(m), tt);
|
||||
new (&scratch_dts) DirtyThreats;
|
||||
do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt, nullptr);
|
||||
}
|
||||
|
||||
inline StateInfo* Position::state() const { return st; }
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
+501
-511
File diff suppressed because it is too large
Load Diff
+33
-27
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -75,7 +76,6 @@ struct Stack {
|
||||
bool ttHit;
|
||||
int cutoffCnt;
|
||||
int reduction;
|
||||
bool isTTMove;
|
||||
};
|
||||
|
||||
|
||||
@@ -134,19 +134,22 @@ struct LimitsType {
|
||||
// The UCI stores the uci options, thread pool, and transposition table.
|
||||
// This struct is used to easily forward data to the Search::Worker class.
|
||||
struct SharedState {
|
||||
SharedState(const OptionsMap& optionsMap,
|
||||
ThreadPool& threadPool,
|
||||
TranspositionTable& transpositionTable,
|
||||
const LazyNumaReplicated<Eval::NNUE::Networks>& nets) :
|
||||
SharedState(const OptionsMap& optionsMap,
|
||||
ThreadPool& threadPool,
|
||||
TranspositionTable& transpositionTable,
|
||||
std::map<NumaIndex, SharedHistories>& sharedHists,
|
||||
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& nets) :
|
||||
options(optionsMap),
|
||||
threads(threadPool),
|
||||
tt(transpositionTable),
|
||||
sharedHistories(sharedHists),
|
||||
networks(nets) {}
|
||||
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
const LazyNumaReplicated<Eval::NNUE::Networks>& networks;
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
std::map<NumaIndex, SharedHistories>& sharedHistories;
|
||||
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks;
|
||||
};
|
||||
|
||||
class Worker;
|
||||
@@ -259,13 +262,17 @@ class NullSearchManager: public ISearchManager {
|
||||
void check_time(Search::Worker&) override {}
|
||||
};
|
||||
|
||||
|
||||
// Search::Worker is the class that does the actual search.
|
||||
// It is instantiated once per thread, and it is responsible for keeping track
|
||||
// of the search history, and storing data required for the search.
|
||||
class Worker {
|
||||
public:
|
||||
Worker(SharedState&, std::unique_ptr<ISearchManager>, size_t, NumaReplicatedAccessToken);
|
||||
Worker(SharedState&,
|
||||
std::unique_ptr<ISearchManager>,
|
||||
size_t,
|
||||
size_t,
|
||||
size_t,
|
||||
NumaReplicatedAccessToken);
|
||||
|
||||
// Called at instantiation to initialize reductions tables.
|
||||
// Reset histories, usually before a new game.
|
||||
@@ -283,21 +290,20 @@ class Worker {
|
||||
ButterflyHistory mainHistory;
|
||||
LowPlyHistory lowPlyHistory;
|
||||
|
||||
CapturePieceToHistory captureHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
PawnHistory pawnHistory;
|
||||
|
||||
CorrectionHistory<Pawn> pawnCorrectionHistory;
|
||||
CorrectionHistory<Minor> minorPieceCorrectionHistory;
|
||||
CorrectionHistory<NonPawn> nonPawnCorrectionHistory;
|
||||
CapturePieceToHistory captureHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
CorrectionHistory<Continuation> continuationCorrectionHistory;
|
||||
|
||||
TTMoveHistory ttMoveHistory;
|
||||
SharedHistories& sharedHistory;
|
||||
|
||||
private:
|
||||
void iterative_deepening();
|
||||
|
||||
void do_move(Position& pos, const Move move, StateInfo& st);
|
||||
void do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck);
|
||||
void do_null_move(Position& pos, StateInfo& st);
|
||||
void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss);
|
||||
void
|
||||
do_move(Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss);
|
||||
void do_null_move(Position& pos, StateInfo& st, Stack* const ss);
|
||||
void undo_move(Position& pos, const Move move);
|
||||
void undo_null_move(Position& pos);
|
||||
|
||||
@@ -336,7 +342,7 @@ class Worker {
|
||||
Depth rootDepth, completedDepth;
|
||||
Value rootDelta;
|
||||
|
||||
size_t threadIdx;
|
||||
size_t threadIdx, numaThreadIdx, numaTotal;
|
||||
NumaReplicatedAccessToken numaAccessToken;
|
||||
|
||||
// Reductions lookup table initialized at startup
|
||||
@@ -347,10 +353,10 @@ class Worker {
|
||||
|
||||
Tablebases::Config tbConfig;
|
||||
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
const LazyNumaReplicated<Eval::NNUE::Networks>& networks;
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks;
|
||||
|
||||
// Used by NNUE
|
||||
Eval::NNUE::AccumulatorStack accumulatorStack;
|
||||
|
||||
@@ -0,0 +1,633 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Stockfish is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SHM_H_INCLUDED
|
||||
#define SHM_H_INCLUDED
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
#include "shm_linux.h"
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__)
|
||||
#include <limits.h>
|
||||
#define SF_MAX_SEM_NAME_LEN NAME_MAX
|
||||
#endif
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#if _WIN32_WINNT < 0x0601
|
||||
#undef _WIN32_WINNT
|
||||
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
|
||||
#endif
|
||||
|
||||
#if !defined(NOMINMAX)
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#elif defined(__linux__)
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <mach-o/dyld.h>
|
||||
#include <sys/syslimits.h>
|
||||
|
||||
#elif defined(__sun)
|
||||
#include <stdlib.h>
|
||||
|
||||
#elif defined(__FreeBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#elif defined(__NetBSD__) || defined(__DragonFly__) || defined(__linux__)
|
||||
#include <limits.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
// argv[0] CANNOT be used because we need to identify the executable.
|
||||
// argv[0] contains the command used to invoke it, which does not involve the full path.
|
||||
// Just using a path is not fully resilient either, as the executable could
|
||||
// have changed if it wasn't locked by the OS. Ideally we would hash the executable
|
||||
// but it's not really that important at this point.
|
||||
// If the path is longer than 4095 bytes the hash will be computed from an unspecified
|
||||
// amount of bytes of the path; in particular it can a hash of an empty string.
|
||||
|
||||
inline std::string getExecutablePathHash() {
|
||||
char executable_path[4096] = {0};
|
||||
std::size_t path_length = 0;
|
||||
|
||||
#if defined(_WIN32)
|
||||
path_length = GetModuleFileNameA(NULL, executable_path, sizeof(executable_path));
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
uint32_t size = sizeof(executable_path);
|
||||
if (_NSGetExecutablePath(executable_path, &size) == 0)
|
||||
{
|
||||
path_length = std::strlen(executable_path);
|
||||
}
|
||||
|
||||
#elif defined(__sun) // Solaris
|
||||
const char* path = getexecname();
|
||||
if (path)
|
||||
{
|
||||
std::strncpy(executable_path, path, sizeof(executable_path) - 1);
|
||||
path_length = std::strlen(executable_path);
|
||||
}
|
||||
|
||||
#elif defined(__FreeBSD__)
|
||||
size_t size = sizeof(executable_path);
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
|
||||
if (sysctl(mib, 4, executable_path, &size, NULL, 0) == 0)
|
||||
{
|
||||
path_length = std::strlen(executable_path);
|
||||
}
|
||||
|
||||
#elif defined(__NetBSD__) || defined(__DragonFly__)
|
||||
ssize_t len = readlink("/proc/curproc/exe", executable_path, sizeof(executable_path) - 1);
|
||||
if (len >= 0)
|
||||
{
|
||||
executable_path[len] = '\0';
|
||||
path_length = len;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
ssize_t len = readlink("/proc/self/exe", executable_path, sizeof(executable_path) - 1);
|
||||
if (len >= 0)
|
||||
{
|
||||
executable_path[len] = '\0';
|
||||
path_length = len;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// In case of any error the path will be empty.
|
||||
return std::string(executable_path, path_length);
|
||||
}
|
||||
|
||||
enum class SystemWideSharedConstantAllocationStatus {
|
||||
NoAllocation,
|
||||
LocalMemory,
|
||||
SharedMemory
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
inline std::string GetLastErrorAsString(DWORD error) {
|
||||
//Get the error message ID, if any.
|
||||
DWORD errorMessageID = error;
|
||||
if (errorMessageID == 0)
|
||||
{
|
||||
return std::string(); //No error message has been recorded
|
||||
}
|
||||
|
||||
LPSTR messageBuffer = nullptr;
|
||||
|
||||
//Ask Win32 to give us the string version of that message ID.
|
||||
//The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be).
|
||||
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
|
||||
| FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPSTR) &messageBuffer, 0, NULL);
|
||||
|
||||
//Copy the error message into a std::string.
|
||||
std::string message(messageBuffer, size);
|
||||
|
||||
//Free the Win32's string's buffer.
|
||||
LocalFree(messageBuffer);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
// Utilizes shared memory to store the value. It is deduplicated system-wide (for the single user).
|
||||
template<typename T>
|
||||
class SharedMemoryBackend {
|
||||
public:
|
||||
enum class Status {
|
||||
Success,
|
||||
LargePageAllocationError,
|
||||
FileMappingError,
|
||||
MapViewError,
|
||||
MutexCreateError,
|
||||
MutexWaitError,
|
||||
MutexReleaseError,
|
||||
NotInitialized
|
||||
};
|
||||
|
||||
static constexpr DWORD IS_INITIALIZED_VALUE = 1;
|
||||
|
||||
SharedMemoryBackend() :
|
||||
status(Status::NotInitialized) {};
|
||||
|
||||
SharedMemoryBackend(const std::string& shm_name, const T& value) :
|
||||
status(Status::NotInitialized) {
|
||||
|
||||
initialize(shm_name, value);
|
||||
}
|
||||
|
||||
bool is_valid() const { return status == Status::Success; }
|
||||
|
||||
std::optional<std::string> get_error_message() const {
|
||||
switch (status)
|
||||
{
|
||||
case Status::Success :
|
||||
return std::nullopt;
|
||||
case Status::LargePageAllocationError :
|
||||
return "Failed to allocate large page memory";
|
||||
case Status::FileMappingError :
|
||||
return "Failed to create file mapping: " + last_error_message;
|
||||
case Status::MapViewError :
|
||||
return "Failed to map view: " + last_error_message;
|
||||
case Status::MutexCreateError :
|
||||
return "Failed to create mutex: " + last_error_message;
|
||||
case Status::MutexWaitError :
|
||||
return "Failed to wait on mutex: " + last_error_message;
|
||||
case Status::MutexReleaseError :
|
||||
return "Failed to release mutex: " + last_error_message;
|
||||
case Status::NotInitialized :
|
||||
return "Not initialized";
|
||||
default :
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
void* get() const { return is_valid() ? pMap : nullptr; }
|
||||
|
||||
~SharedMemoryBackend() { cleanup(); }
|
||||
|
||||
SharedMemoryBackend(const SharedMemoryBackend&) = delete;
|
||||
SharedMemoryBackend& operator=(const SharedMemoryBackend&) = delete;
|
||||
|
||||
SharedMemoryBackend(SharedMemoryBackend&& other) noexcept :
|
||||
pMap(other.pMap),
|
||||
hMapFile(other.hMapFile),
|
||||
status(other.status),
|
||||
last_error_message(std::move(other.last_error_message)) {
|
||||
|
||||
other.pMap = nullptr;
|
||||
other.hMapFile = 0;
|
||||
other.status = Status::NotInitialized;
|
||||
}
|
||||
|
||||
SharedMemoryBackend& operator=(SharedMemoryBackend&& other) noexcept {
|
||||
if (this != &other)
|
||||
{
|
||||
cleanup();
|
||||
pMap = other.pMap;
|
||||
hMapFile = other.hMapFile;
|
||||
status = other.status;
|
||||
last_error_message = std::move(other.last_error_message);
|
||||
|
||||
other.pMap = nullptr;
|
||||
other.hMapFile = 0;
|
||||
other.status = Status::NotInitialized;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SystemWideSharedConstantAllocationStatus get_status() const {
|
||||
return status == Status::Success ? SystemWideSharedConstantAllocationStatus::SharedMemory
|
||||
: SystemWideSharedConstantAllocationStatus::NoAllocation;
|
||||
}
|
||||
|
||||
private:
|
||||
void initialize(const std::string& shm_name, const T& value) {
|
||||
const size_t total_size = sizeof(T) + sizeof(IS_INITIALIZED_VALUE);
|
||||
|
||||
// Try allocating with large pages first.
|
||||
hMapFile = windows_try_with_large_page_priviliges(
|
||||
[&](size_t largePageSize) {
|
||||
const size_t total_size_aligned =
|
||||
(total_size + largePageSize - 1) / largePageSize * largePageSize;
|
||||
|
||||
#if defined(_WIN64)
|
||||
DWORD total_size_low = total_size_aligned & 0xFFFFFFFFu;
|
||||
DWORD total_size_high = total_size_aligned >> 32u;
|
||||
#else
|
||||
DWORD total_size_low = total_size_aligned;
|
||||
DWORD total_size_high = 0;
|
||||
#endif
|
||||
|
||||
return CreateFileMappingA(INVALID_HANDLE_VALUE, NULL,
|
||||
PAGE_READWRITE | SEC_COMMIT | SEC_LARGE_PAGES,
|
||||
total_size_high, total_size_low, shm_name.c_str());
|
||||
},
|
||||
[]() { return (void*) nullptr; });
|
||||
|
||||
// Fallback to normal allocation if no large pages available.
|
||||
if (!hMapFile)
|
||||
{
|
||||
hMapFile = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0,
|
||||
static_cast<DWORD>(total_size), shm_name.c_str());
|
||||
}
|
||||
|
||||
if (!hMapFile)
|
||||
{
|
||||
const DWORD err = GetLastError();
|
||||
last_error_message = GetLastErrorAsString(err);
|
||||
status = Status::FileMappingError;
|
||||
return;
|
||||
}
|
||||
|
||||
pMap = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, total_size);
|
||||
if (!pMap)
|
||||
{
|
||||
const DWORD err = GetLastError();
|
||||
last_error_message = GetLastErrorAsString(err);
|
||||
status = Status::MapViewError;
|
||||
cleanup_partial();
|
||||
return;
|
||||
}
|
||||
|
||||
// Use named mutex to ensure only one initializer
|
||||
std::string mutex_name = shm_name + "$mutex";
|
||||
HANDLE hMutex = CreateMutexA(NULL, FALSE, mutex_name.c_str());
|
||||
if (!hMutex)
|
||||
{
|
||||
const DWORD err = GetLastError();
|
||||
last_error_message = GetLastErrorAsString(err);
|
||||
status = Status::MutexCreateError;
|
||||
cleanup_partial();
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD wait_result = WaitForSingleObject(hMutex, INFINITE);
|
||||
if (wait_result != WAIT_OBJECT_0)
|
||||
{
|
||||
const DWORD err = GetLastError();
|
||||
last_error_message = GetLastErrorAsString(err);
|
||||
status = Status::MutexWaitError;
|
||||
CloseHandle(hMutex);
|
||||
cleanup_partial();
|
||||
return;
|
||||
}
|
||||
|
||||
// Crucially, we place the object first to ensure alignment.
|
||||
volatile DWORD* is_initialized =
|
||||
std::launder(reinterpret_cast<DWORD*>(reinterpret_cast<char*>(pMap) + sizeof(T)));
|
||||
T* object = std::launder(reinterpret_cast<T*>(pMap));
|
||||
|
||||
if (*is_initialized != IS_INITIALIZED_VALUE)
|
||||
{
|
||||
// First time initialization, message for debug purposes
|
||||
new (object) T{value};
|
||||
*is_initialized = IS_INITIALIZED_VALUE;
|
||||
}
|
||||
|
||||
BOOL release_result = ReleaseMutex(hMutex);
|
||||
CloseHandle(hMutex);
|
||||
|
||||
if (!release_result)
|
||||
{
|
||||
const DWORD err = GetLastError();
|
||||
last_error_message = GetLastErrorAsString(err);
|
||||
status = Status::MutexReleaseError;
|
||||
cleanup_partial();
|
||||
return;
|
||||
}
|
||||
|
||||
status = Status::Success;
|
||||
}
|
||||
|
||||
void cleanup_partial() {
|
||||
if (pMap != nullptr)
|
||||
{
|
||||
UnmapViewOfFile(pMap);
|
||||
pMap = nullptr;
|
||||
}
|
||||
if (hMapFile)
|
||||
{
|
||||
CloseHandle(hMapFile);
|
||||
hMapFile = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
if (pMap != nullptr)
|
||||
{
|
||||
UnmapViewOfFile(pMap);
|
||||
pMap = nullptr;
|
||||
}
|
||||
if (hMapFile)
|
||||
{
|
||||
CloseHandle(hMapFile);
|
||||
hMapFile = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void* pMap = nullptr;
|
||||
HANDLE hMapFile = 0;
|
||||
Status status = Status::NotInitialized;
|
||||
std::string last_error_message;
|
||||
};
|
||||
|
||||
#elif defined(__linux__) && !defined(__ANDROID__)
|
||||
|
||||
template<typename T>
|
||||
class SharedMemoryBackend {
|
||||
public:
|
||||
SharedMemoryBackend() = default;
|
||||
|
||||
SharedMemoryBackend(const std::string& shm_name, const T& value) :
|
||||
shm1(shm::create_shared<T>(shm_name, value)) {}
|
||||
|
||||
void* get() const {
|
||||
const T* ptr = &shm1->get();
|
||||
return reinterpret_cast<void*>(const_cast<T*>(ptr));
|
||||
}
|
||||
|
||||
bool is_valid() const { return shm1 && shm1->is_open() && shm1->is_initialized(); }
|
||||
|
||||
SystemWideSharedConstantAllocationStatus get_status() const {
|
||||
return is_valid() ? SystemWideSharedConstantAllocationStatus::SharedMemory
|
||||
: SystemWideSharedConstantAllocationStatus::NoAllocation;
|
||||
}
|
||||
|
||||
std::optional<std::string> get_error_message() const {
|
||||
if (!shm1)
|
||||
return "Shared memory not initialized";
|
||||
|
||||
if (!shm1->is_open())
|
||||
return "Shared memory is not open";
|
||||
|
||||
if (!shm1->is_initialized())
|
||||
return "Not initialized";
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<shm::SharedMemory<T>> shm1;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
// For systems that don't have shared memory, or support is troublesome.
|
||||
// The way fallback is done is that we need a dummy backend.
|
||||
|
||||
template<typename T>
|
||||
class SharedMemoryBackend {
|
||||
public:
|
||||
SharedMemoryBackend() = default;
|
||||
|
||||
SharedMemoryBackend([[maybe_unused]] const std::string& shm_name,
|
||||
[[maybe_unused]] const T& value) {}
|
||||
|
||||
void* get() const { return nullptr; }
|
||||
|
||||
bool is_valid() const { return false; }
|
||||
|
||||
SystemWideSharedConstantAllocationStatus get_status() const {
|
||||
return SystemWideSharedConstantAllocationStatus::NoAllocation;
|
||||
}
|
||||
|
||||
std::optional<std::string> get_error_message() const { return "Dummy SharedMemoryBackend"; }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
template<typename T>
|
||||
struct SharedMemoryBackendFallback {
|
||||
SharedMemoryBackendFallback() = default;
|
||||
|
||||
SharedMemoryBackendFallback(const std::string&, const T& value) :
|
||||
fallback_object(make_unique_large_page<T>(value)) {}
|
||||
|
||||
void* get() const { return fallback_object.get(); }
|
||||
|
||||
SharedMemoryBackendFallback(const SharedMemoryBackendFallback&) = delete;
|
||||
SharedMemoryBackendFallback& operator=(const SharedMemoryBackendFallback&) = delete;
|
||||
|
||||
SharedMemoryBackendFallback(SharedMemoryBackendFallback&& other) noexcept :
|
||||
fallback_object(std::move(other.fallback_object)) {}
|
||||
|
||||
SharedMemoryBackendFallback& operator=(SharedMemoryBackendFallback&& other) noexcept {
|
||||
fallback_object = std::move(other.fallback_object);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SystemWideSharedConstantAllocationStatus get_status() const {
|
||||
return fallback_object == nullptr ? SystemWideSharedConstantAllocationStatus::NoAllocation
|
||||
: SystemWideSharedConstantAllocationStatus::LocalMemory;
|
||||
}
|
||||
|
||||
std::optional<std::string> get_error_message() const {
|
||||
if (fallback_object == nullptr)
|
||||
return "Not initialized";
|
||||
|
||||
return "Shared memory not supported by the OS. Local allocation fallback.";
|
||||
}
|
||||
|
||||
private:
|
||||
LargePagePtr<T> fallback_object;
|
||||
};
|
||||
|
||||
// Platform-independent wrapper
|
||||
template<typename T>
|
||||
struct SystemWideSharedConstant {
|
||||
private:
|
||||
static std::string createHashString(const std::string& input) {
|
||||
char buf[1024];
|
||||
std::snprintf(buf, sizeof(buf), "%016" PRIx64, hash_string(input));
|
||||
return buf;
|
||||
}
|
||||
|
||||
public:
|
||||
// We can't run the destructor because it may be in a completely different process.
|
||||
// The object stored must also be obviously in-line but we can't check for that, other than some basic checks that cover most cases.
|
||||
static_assert(std::is_trivially_destructible_v<T>);
|
||||
static_assert(std::is_trivially_move_constructible_v<T>);
|
||||
static_assert(std::is_trivially_copy_constructible_v<T>);
|
||||
|
||||
SystemWideSharedConstant() = default;
|
||||
|
||||
|
||||
// Content is addressed by its hash. An additional discriminator can be added to account for differences
|
||||
// that are not present in the content, for example NUMA node allocation.
|
||||
SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) {
|
||||
std::size_t content_hash = std::hash<T>{}(value);
|
||||
std::size_t executable_hash = hash_string(getExecutablePathHash());
|
||||
|
||||
char buf[1024];
|
||||
std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash, discriminator);
|
||||
std::string shm_name = buf;
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
// POSIX shared memory names must start with a slash
|
||||
shm_name = "/sf_" + createHashString(shm_name);
|
||||
|
||||
// hash name and make sure it is not longer than SF_MAX_SEM_NAME_LEN
|
||||
if (shm_name.size() > SF_MAX_SEM_NAME_LEN)
|
||||
{
|
||||
shm_name = shm_name.substr(0, SF_MAX_SEM_NAME_LEN - 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
SharedMemoryBackend<T> shm_backend(shm_name, value);
|
||||
|
||||
if (shm_backend.is_valid())
|
||||
{
|
||||
backend = std::move(shm_backend);
|
||||
}
|
||||
else
|
||||
{
|
||||
backend = SharedMemoryBackendFallback<T>(shm_name, value);
|
||||
}
|
||||
}
|
||||
|
||||
SystemWideSharedConstant(const SystemWideSharedConstant&) = delete;
|
||||
SystemWideSharedConstant& operator=(const SystemWideSharedConstant&) = delete;
|
||||
|
||||
SystemWideSharedConstant(SystemWideSharedConstant&& other) noexcept :
|
||||
backend(std::move(other.backend)) {}
|
||||
|
||||
SystemWideSharedConstant& operator=(SystemWideSharedConstant&& other) noexcept {
|
||||
backend = std::move(other.backend);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const T& operator*() const { return *std::launder(reinterpret_cast<const T*>(get_ptr())); }
|
||||
|
||||
bool operator==(std::nullptr_t) const noexcept { return get_ptr() == nullptr; }
|
||||
|
||||
bool operator!=(std::nullptr_t) const noexcept { return get_ptr() != nullptr; }
|
||||
|
||||
SystemWideSharedConstantAllocationStatus get_status() const {
|
||||
return std::visit(
|
||||
[](const auto& end) -> SystemWideSharedConstantAllocationStatus {
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)
|
||||
{
|
||||
return SystemWideSharedConstantAllocationStatus::NoAllocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
return end.get_status();
|
||||
}
|
||||
},
|
||||
backend);
|
||||
}
|
||||
|
||||
std::optional<std::string> get_error_message() const {
|
||||
return std::visit(
|
||||
[](const auto& end) -> std::optional<std::string> {
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
return end.get_error_message();
|
||||
}
|
||||
},
|
||||
backend);
|
||||
}
|
||||
|
||||
private:
|
||||
auto get_ptr() const {
|
||||
return std::visit(
|
||||
[](const auto& end) -> void* {
|
||||
if constexpr (std::is_same_v<std::decay_t<decltype(end)>, std::monostate>)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return end.get();
|
||||
}
|
||||
},
|
||||
backend);
|
||||
}
|
||||
|
||||
std::variant<std::monostate, SharedMemoryBackend<T>, SharedMemoryBackendFallback<T>> backend;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif // #ifndef SHM_H_INCLUDED
|
||||
+671
@@ -0,0 +1,671 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Stockfish is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef SHM_LINUX_H_INCLUDED
|
||||
#define SHM_LINUX_H_INCLUDED
|
||||
|
||||
#if !defined(__linux__) || defined(__ANDROID__)
|
||||
#error shm_linux.h should not be included on this platform.
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <dirent.h>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <optional>
|
||||
#include <pthread.h>
|
||||
#include <string>
|
||||
#include <inttypes.h>
|
||||
#include <type_traits>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#define SF_MAX_SEM_NAME_LEN NAME_MAX
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
namespace Stockfish::shm {
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct ShmHeader {
|
||||
static constexpr uint32_t SHM_MAGIC = 0xAD5F1A12;
|
||||
pthread_mutex_t mutex;
|
||||
std::atomic<uint32_t> ref_count{0};
|
||||
std::atomic<bool> initialized{false};
|
||||
uint32_t magic = SHM_MAGIC;
|
||||
};
|
||||
|
||||
class SharedMemoryBase {
|
||||
public:
|
||||
virtual ~SharedMemoryBase() = default;
|
||||
virtual void close(bool skip_unmap = false) noexcept = 0;
|
||||
virtual const std::string& name() const noexcept = 0;
|
||||
};
|
||||
|
||||
class SharedMemoryRegistry {
|
||||
private:
|
||||
static std::mutex registry_mutex_;
|
||||
static std::vector<SharedMemoryBase*> active_instances_;
|
||||
|
||||
public:
|
||||
static void register_instance(SharedMemoryBase* instance) {
|
||||
std::scoped_lock lock(registry_mutex_);
|
||||
active_instances_.push_back(instance);
|
||||
}
|
||||
|
||||
static void unregister_instance(SharedMemoryBase* instance) {
|
||||
std::scoped_lock lock(registry_mutex_);
|
||||
active_instances_.erase(
|
||||
std::remove(active_instances_.begin(), active_instances_.end(), instance), active_instances_.end());
|
||||
}
|
||||
|
||||
static void cleanup_all(bool skip_unmap = false) noexcept {
|
||||
std::scoped_lock lock(registry_mutex_);
|
||||
for (auto* instance : active_instances_)
|
||||
instance->close(skip_unmap);
|
||||
active_instances_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
inline std::mutex SharedMemoryRegistry::registry_mutex_;
|
||||
inline std::vector<SharedMemoryBase*> SharedMemoryRegistry::active_instances_;
|
||||
|
||||
class CleanupHooks {
|
||||
private:
|
||||
static std::once_flag register_once_;
|
||||
|
||||
static void handle_signal(int sig) noexcept {
|
||||
// Search threads may still be running, so skip munmap (but still perform
|
||||
// other cleanup actions). The memory mappings will be released on exit.
|
||||
SharedMemoryRegistry::cleanup_all(true);
|
||||
|
||||
// Invoke the default handler, which will exit
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = SIG_DFL;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
if (sigaction(sig, &sa, nullptr) == -1)
|
||||
_Exit(128 + sig);
|
||||
|
||||
raise(sig);
|
||||
}
|
||||
|
||||
static void register_signal_handlers() noexcept {
|
||||
std::atexit([]() { SharedMemoryRegistry::cleanup_all(true); });
|
||||
|
||||
constexpr int signals[] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE,
|
||||
SIGSEGV, SIGTERM, SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ};
|
||||
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = handle_signal;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
|
||||
for (int sig : signals)
|
||||
sigaction(sig, &sa, nullptr);
|
||||
}
|
||||
|
||||
public:
|
||||
static void ensure_registered() noexcept {
|
||||
std::call_once(register_once_, register_signal_handlers);
|
||||
}
|
||||
};
|
||||
|
||||
inline std::once_flag CleanupHooks::register_once_;
|
||||
|
||||
|
||||
inline int portable_fallocate(int fd, off_t offset, off_t length) {
|
||||
#ifdef __APPLE__
|
||||
fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, offset, length, 0};
|
||||
int ret = fcntl(fd, F_PREALLOCATE, &store);
|
||||
if (ret == -1)
|
||||
{
|
||||
store.fst_flags = F_ALLOCATEALL;
|
||||
ret = fcntl(fd, F_PREALLOCATE, &store);
|
||||
}
|
||||
if (ret != -1)
|
||||
ret = ftruncate(fd, offset + length);
|
||||
return ret;
|
||||
#else
|
||||
return posix_fallocate(fd, offset, length);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template<typename T>
|
||||
class SharedMemory: public detail::SharedMemoryBase {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||
static_assert(!std::is_pointer_v<T>, "T cannot be a pointer type");
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
int fd_ = -1;
|
||||
void* mapped_ptr_ = nullptr;
|
||||
T* data_ptr_ = nullptr;
|
||||
detail::ShmHeader* header_ptr_ = nullptr;
|
||||
size_t total_size_ = 0;
|
||||
std::string sentinel_base_;
|
||||
std::string sentinel_path_;
|
||||
|
||||
static constexpr size_t calculate_total_size() noexcept {
|
||||
return sizeof(T) + sizeof(detail::ShmHeader);
|
||||
}
|
||||
|
||||
static std::string make_sentinel_base(const std::string& name) {
|
||||
char buf[32];
|
||||
// Using std::to_string here causes non-deterministic PGO builds.
|
||||
// snprintf, being part of libc, is insensitive to the formatted values.
|
||||
std::snprintf(buf, sizeof(buf), "sfshm_%016" PRIu64, hash_string(name));
|
||||
return buf;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit SharedMemory(const std::string& name) noexcept :
|
||||
name_(name),
|
||||
total_size_(calculate_total_size()),
|
||||
sentinel_base_(make_sentinel_base(name)) {}
|
||||
|
||||
~SharedMemory() noexcept override {
|
||||
detail::SharedMemoryRegistry::unregister_instance(this);
|
||||
close();
|
||||
}
|
||||
|
||||
SharedMemory(const SharedMemory&) = delete;
|
||||
SharedMemory& operator=(const SharedMemory&) = delete;
|
||||
|
||||
SharedMemory(SharedMemory&& other) noexcept :
|
||||
name_(std::move(other.name_)),
|
||||
fd_(other.fd_),
|
||||
mapped_ptr_(other.mapped_ptr_),
|
||||
data_ptr_(other.data_ptr_),
|
||||
header_ptr_(other.header_ptr_),
|
||||
total_size_(other.total_size_),
|
||||
sentinel_base_(std::move(other.sentinel_base_)),
|
||||
sentinel_path_(std::move(other.sentinel_path_)) {
|
||||
|
||||
detail::SharedMemoryRegistry::unregister_instance(&other);
|
||||
detail::SharedMemoryRegistry::register_instance(this);
|
||||
other.reset();
|
||||
}
|
||||
|
||||
SharedMemory& operator=(SharedMemory&& other) noexcept {
|
||||
if (this != &other)
|
||||
{
|
||||
detail::SharedMemoryRegistry::unregister_instance(this);
|
||||
close();
|
||||
|
||||
name_ = std::move(other.name_);
|
||||
fd_ = other.fd_;
|
||||
mapped_ptr_ = other.mapped_ptr_;
|
||||
data_ptr_ = other.data_ptr_;
|
||||
header_ptr_ = other.header_ptr_;
|
||||
total_size_ = other.total_size_;
|
||||
sentinel_base_ = std::move(other.sentinel_base_);
|
||||
sentinel_path_ = std::move(other.sentinel_path_);
|
||||
|
||||
detail::SharedMemoryRegistry::unregister_instance(&other);
|
||||
detail::SharedMemoryRegistry::register_instance(this);
|
||||
|
||||
other.reset();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool open(const T& initial_value) noexcept {
|
||||
detail::CleanupHooks::ensure_registered();
|
||||
|
||||
bool retried_stale = false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (is_open())
|
||||
return false;
|
||||
|
||||
bool created_new = false;
|
||||
fd_ = shm_open(name_.c_str(), O_CREAT | O_EXCL | O_RDWR, 0666);
|
||||
|
||||
if (fd_ == -1)
|
||||
{
|
||||
fd_ = shm_open(name_.c_str(), O_RDWR, 0666);
|
||||
if (fd_ == -1)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
created_new = true;
|
||||
|
||||
if (!lock_file(LOCK_EX))
|
||||
{
|
||||
::close(fd_);
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool invalid_header = false;
|
||||
bool success =
|
||||
created_new ? setup_new_region(initial_value) : setup_existing_region(invalid_header);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
if (created_new || invalid_header)
|
||||
shm_unlink(name_.c_str());
|
||||
if (mapped_ptr_)
|
||||
unmap_region();
|
||||
unlock_file();
|
||||
::close(fd_);
|
||||
reset();
|
||||
|
||||
if (!created_new && invalid_header && !retried_stale)
|
||||
{
|
||||
retried_stale = true;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!lock_shared_mutex())
|
||||
{
|
||||
if (created_new)
|
||||
shm_unlink(name_.c_str());
|
||||
if (mapped_ptr_)
|
||||
unmap_region();
|
||||
unlock_file();
|
||||
::close(fd_);
|
||||
reset();
|
||||
|
||||
if (!created_new && !retried_stale)
|
||||
{
|
||||
retried_stale = true;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!create_sentinel_file_locked())
|
||||
{
|
||||
unlock_shared_mutex();
|
||||
unmap_region();
|
||||
if (created_new)
|
||||
shm_unlink(name_.c_str());
|
||||
unlock_file();
|
||||
::close(fd_);
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
header_ptr_->ref_count.fetch_add(1, std::memory_order_acq_rel);
|
||||
|
||||
unlock_shared_mutex();
|
||||
unlock_file();
|
||||
detail::SharedMemoryRegistry::register_instance(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void close(bool skip_unmap = false) noexcept override {
|
||||
if (fd_ == -1 && mapped_ptr_ == nullptr)
|
||||
return;
|
||||
|
||||
bool remove_region = false;
|
||||
bool file_locked = lock_file(LOCK_EX);
|
||||
bool mutex_locked = false;
|
||||
|
||||
if (file_locked && header_ptr_ != nullptr)
|
||||
mutex_locked = lock_shared_mutex();
|
||||
|
||||
if (mutex_locked)
|
||||
{
|
||||
if (header_ptr_)
|
||||
{
|
||||
header_ptr_->ref_count.fetch_sub(1, std::memory_order_acq_rel);
|
||||
}
|
||||
remove_sentinel_file();
|
||||
remove_region = !has_other_live_sentinels_locked();
|
||||
unlock_shared_mutex();
|
||||
}
|
||||
else
|
||||
{
|
||||
remove_sentinel_file();
|
||||
decrement_refcount_relaxed();
|
||||
}
|
||||
|
||||
if (skip_unmap)
|
||||
mapped_ptr_ = nullptr;
|
||||
else
|
||||
unmap_region();
|
||||
|
||||
if (remove_region)
|
||||
shm_unlink(name_.c_str());
|
||||
|
||||
if (file_locked)
|
||||
unlock_file();
|
||||
|
||||
if (fd_ != -1)
|
||||
{
|
||||
::close(fd_);
|
||||
fd_ = -1;
|
||||
}
|
||||
|
||||
if (!skip_unmap)
|
||||
reset();
|
||||
}
|
||||
|
||||
const std::string& name() const noexcept override { return name_; }
|
||||
|
||||
[[nodiscard]] bool is_open() const noexcept { return fd_ != -1 && mapped_ptr_ && data_ptr_; }
|
||||
|
||||
[[nodiscard]] const T& get() const noexcept { return *data_ptr_; }
|
||||
|
||||
[[nodiscard]] const T* operator->() const noexcept { return data_ptr_; }
|
||||
|
||||
[[nodiscard]] const T& operator*() const noexcept { return *data_ptr_; }
|
||||
|
||||
[[nodiscard]] uint32_t ref_count() const noexcept {
|
||||
return header_ptr_ ? header_ptr_->ref_count.load(std::memory_order_acquire) : 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool is_initialized() const noexcept {
|
||||
return header_ptr_ ? header_ptr_->initialized.load(std::memory_order_acquire) : false;
|
||||
}
|
||||
|
||||
static void cleanup_all_instances() noexcept { detail::SharedMemoryRegistry::cleanup_all(); }
|
||||
|
||||
private:
|
||||
void reset() noexcept {
|
||||
fd_ = -1;
|
||||
mapped_ptr_ = nullptr;
|
||||
data_ptr_ = nullptr;
|
||||
header_ptr_ = nullptr;
|
||||
sentinel_path_.clear();
|
||||
}
|
||||
|
||||
void unmap_region() noexcept {
|
||||
if (mapped_ptr_)
|
||||
{
|
||||
munmap(mapped_ptr_, total_size_);
|
||||
mapped_ptr_ = nullptr;
|
||||
data_ptr_ = nullptr;
|
||||
header_ptr_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool lock_file(int operation) noexcept {
|
||||
if (fd_ == -1)
|
||||
return false;
|
||||
|
||||
while (flock(fd_, operation) == -1)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void unlock_file() noexcept {
|
||||
if (fd_ == -1)
|
||||
return;
|
||||
|
||||
while (flock(fd_, LOCK_UN) == -1)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string sentinel_full_path(pid_t pid) const {
|
||||
char buf[1024];
|
||||
// See above snprintf comment
|
||||
std::snprintf(buf, sizeof(buf), "/dev/shm/%s.%ld", sentinel_base_.c_str(), long(pid));
|
||||
return buf;
|
||||
}
|
||||
|
||||
void decrement_refcount_relaxed() noexcept {
|
||||
if (!header_ptr_)
|
||||
return;
|
||||
|
||||
uint32_t expected = header_ptr_->ref_count.load(std::memory_order_relaxed);
|
||||
while (expected != 0
|
||||
&& !header_ptr_->ref_count.compare_exchange_weak(
|
||||
expected, expected - 1, std::memory_order_acq_rel, std::memory_order_relaxed))
|
||||
{}
|
||||
}
|
||||
|
||||
bool create_sentinel_file_locked() noexcept {
|
||||
if (!header_ptr_)
|
||||
return false;
|
||||
|
||||
const pid_t self_pid = getpid();
|
||||
sentinel_path_ = sentinel_full_path(self_pid);
|
||||
|
||||
for (int attempt = 0; attempt < 2; ++attempt)
|
||||
{
|
||||
int fd = ::open(sentinel_path_.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0600);
|
||||
if (fd != -1)
|
||||
{
|
||||
::close(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (errno == EEXIST)
|
||||
{
|
||||
::unlink(sentinel_path_.c_str());
|
||||
decrement_refcount_relaxed();
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
sentinel_path_.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
void remove_sentinel_file() noexcept {
|
||||
if (!sentinel_path_.empty())
|
||||
{
|
||||
::unlink(sentinel_path_.c_str());
|
||||
sentinel_path_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
static bool pid_is_alive(pid_t pid) noexcept {
|
||||
if (pid <= 0)
|
||||
return false;
|
||||
|
||||
if (kill(pid, 0) == 0)
|
||||
return true;
|
||||
|
||||
return errno == EPERM;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool initialize_shared_mutex() noexcept {
|
||||
if (!header_ptr_)
|
||||
return false;
|
||||
|
||||
pthread_mutexattr_t attr;
|
||||
if (pthread_mutexattr_init(&attr) != 0)
|
||||
return false;
|
||||
|
||||
bool success = pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) == 0;
|
||||
#if _POSIX_C_SOURCE >= 200809L
|
||||
if (success)
|
||||
success = pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST) == 0;
|
||||
#endif
|
||||
|
||||
if (success)
|
||||
success = pthread_mutex_init(&header_ptr_->mutex, &attr) == 0;
|
||||
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
return success;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool lock_shared_mutex() noexcept {
|
||||
if (!header_ptr_)
|
||||
return false;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int rc = pthread_mutex_lock(&header_ptr_->mutex);
|
||||
if (rc == 0)
|
||||
return true;
|
||||
|
||||
#if _POSIX_C_SOURCE >= 200809L
|
||||
if (rc == EOWNERDEAD)
|
||||
{
|
||||
if (pthread_mutex_consistent(&header_ptr_->mutex) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rc == EINTR)
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void unlock_shared_mutex() noexcept {
|
||||
if (header_ptr_)
|
||||
pthread_mutex_unlock(&header_ptr_->mutex);
|
||||
}
|
||||
|
||||
bool has_other_live_sentinels_locked() const noexcept {
|
||||
DIR* dir = opendir("/dev/shm");
|
||||
if (!dir)
|
||||
return false;
|
||||
|
||||
std::string prefix = sentinel_base_ + ".";
|
||||
bool found = false;
|
||||
|
||||
while (dirent* entry = readdir(dir))
|
||||
{
|
||||
std::string name = entry->d_name;
|
||||
if (name.rfind(prefix, 0) != 0)
|
||||
continue;
|
||||
|
||||
auto pid_str = name.substr(prefix.size());
|
||||
char* end = nullptr;
|
||||
long value = std::strtol(pid_str.c_str(), &end, 10);
|
||||
if (!end || *end != '\0')
|
||||
continue;
|
||||
|
||||
pid_t pid = static_cast<pid_t>(value);
|
||||
if (pid_is_alive(pid))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
std::string stale_path = std::string("/dev/shm/") + name;
|
||||
::unlink(stale_path.c_str());
|
||||
const_cast<SharedMemory*>(this)->decrement_refcount_relaxed();
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return found;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool setup_new_region(const T& initial_value) noexcept {
|
||||
if (ftruncate(fd_, static_cast<off_t>(total_size_)) == -1)
|
||||
return false;
|
||||
|
||||
if (detail::portable_fallocate(fd_, 0, static_cast<off_t>(total_size_)) != 0)
|
||||
return false;
|
||||
|
||||
mapped_ptr_ = mmap(nullptr, total_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
|
||||
if (mapped_ptr_ == MAP_FAILED)
|
||||
{
|
||||
mapped_ptr_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
data_ptr_ = static_cast<T*>(mapped_ptr_);
|
||||
header_ptr_ =
|
||||
reinterpret_cast<detail::ShmHeader*>(static_cast<char*>(mapped_ptr_) + sizeof(T));
|
||||
|
||||
new (header_ptr_) detail::ShmHeader{};
|
||||
new (data_ptr_) T{initial_value};
|
||||
|
||||
if (!initialize_shared_mutex())
|
||||
return false;
|
||||
|
||||
header_ptr_->ref_count.store(0, std::memory_order_release);
|
||||
header_ptr_->initialized.store(true, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool setup_existing_region(bool& invalid_header) noexcept {
|
||||
invalid_header = false;
|
||||
|
||||
struct stat st;
|
||||
fstat(fd_, &st);
|
||||
if (static_cast<size_t>(st.st_size) < total_size_)
|
||||
{
|
||||
invalid_header = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
mapped_ptr_ = mmap(nullptr, total_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
|
||||
if (mapped_ptr_ == MAP_FAILED)
|
||||
{
|
||||
mapped_ptr_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
data_ptr_ = static_cast<T*>(mapped_ptr_);
|
||||
header_ptr_ = std::launder(
|
||||
reinterpret_cast<detail::ShmHeader*>(static_cast<char*>(mapped_ptr_) + sizeof(T)));
|
||||
|
||||
if (!header_ptr_->initialized.load(std::memory_order_acquire)
|
||||
|| header_ptr_->magic != detail::ShmHeader::SHM_MAGIC)
|
||||
{
|
||||
invalid_header = true;
|
||||
unmap_region();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::optional<SharedMemory<T>> create_shared(const std::string& name,
|
||||
const T& initial_value) noexcept {
|
||||
SharedMemory<T> shm(name);
|
||||
if (shm.open(initial_value))
|
||||
return shm;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Stockfish::shm
|
||||
|
||||
#endif // #ifndef SHM_LINUX_H_INCLUDED
|
||||
+37
-26
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "../bitboard.h"
|
||||
#include "../misc.h"
|
||||
@@ -584,7 +585,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
||||
// idx = k * d->span + idx % d->span (2)
|
||||
//
|
||||
// So from (1) and (2) we can compute idx - I(K):
|
||||
int diff = idx % d->span - d->span / 2;
|
||||
int diff = int(idx % d->span - d->span / 2);
|
||||
|
||||
// Sum the above to offset to find the offset corresponding to our idx
|
||||
offset += diff;
|
||||
@@ -708,15 +709,11 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
|
||||
return value + 1;
|
||||
}
|
||||
|
||||
// A temporary fix for the compiler bug with AVX-512. (#4450)
|
||||
#ifdef USE_AVX512
|
||||
#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15
|
||||
#define CLANG_AVX512_BUG_FIX __attribute__((optnone))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CLANG_AVX512_BUG_FIX
|
||||
#define CLANG_AVX512_BUG_FIX
|
||||
// A temporary fix for the compiler bug with vectorization. (#4450)
|
||||
#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15
|
||||
#define DISABLE_CLANG_LOOP_VEC _Pragma("clang loop vectorize(disable)")
|
||||
#else
|
||||
#define DISABLE_CLANG_LOOP_VEC
|
||||
#endif
|
||||
|
||||
// Compute a unique index out of a position and use it to probe the TB file. To
|
||||
@@ -726,8 +723,7 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
|
||||
// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk]
|
||||
//
|
||||
template<typename T, typename Ret = typename T::Ret>
|
||||
CLANG_AVX512_BUG_FIX Ret
|
||||
do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {
|
||||
Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {
|
||||
|
||||
Square squares[TBPIECES];
|
||||
Piece pieces[TBPIECES];
|
||||
@@ -811,8 +807,11 @@ do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result)
|
||||
// Now we map again the squares so that the square of the lead piece is in
|
||||
// the triangle A1-D1-D4.
|
||||
if (file_of(squares[0]) > FILE_D)
|
||||
{
|
||||
DISABLE_CLANG_LOOP_VEC
|
||||
for (int i = 0; i < size; ++i)
|
||||
squares[i] = flip_file(squares[i]);
|
||||
}
|
||||
|
||||
// Encode leading pawns starting with the one with minimum MapPawns[] and
|
||||
// proceeding in ascending order.
|
||||
@@ -831,19 +830,26 @@ do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result)
|
||||
// In positions without pawns, we further flip the squares to ensure leading
|
||||
// piece is below RANK_5.
|
||||
if (rank_of(squares[0]) > RANK_4)
|
||||
{
|
||||
DISABLE_CLANG_LOOP_VEC
|
||||
for (int i = 0; i < size; ++i)
|
||||
squares[i] = flip_rank(squares[i]);
|
||||
}
|
||||
|
||||
// Look for the first piece of the leading group not on the A1-D4 diagonal
|
||||
// and ensure it is mapped below the diagonal.
|
||||
DISABLE_CLANG_LOOP_VEC
|
||||
for (int i = 0; i < d->groupLen[0]; ++i)
|
||||
{
|
||||
if (!off_A1H8(squares[i]))
|
||||
continue;
|
||||
|
||||
if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1
|
||||
{
|
||||
DISABLE_CLANG_LOOP_VEC
|
||||
for (int j = i; j < size; ++j)
|
||||
squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1092,7 +1098,7 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
|
||||
// See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf
|
||||
std::vector<bool> visited(d->symlen.size());
|
||||
|
||||
for (std::size_t sym = 0; sym < d->symlen.size(); ++sym)
|
||||
for (Sym sym = 0; sym < d->symlen.size(); ++sym)
|
||||
if (!visited[sym])
|
||||
d->symlen[sym] = set_symlen(d, sym, visited);
|
||||
|
||||
@@ -1214,6 +1220,8 @@ template<TBType Type>
|
||||
void* mapped(TBTable<Type>& e, const Position& pos) {
|
||||
|
||||
static std::mutex mutex;
|
||||
// Because TB is the only usage of materialKey, check it here in debug mode
|
||||
assert(pos.material_key_is_ok());
|
||||
|
||||
// Use 'acquire' to avoid a thread reading 'ready' == true while
|
||||
// another is still working. (compiler reordering may cause this).
|
||||
@@ -1592,10 +1600,11 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
|
||||
// Use the DTZ tables to rank root moves.
|
||||
//
|
||||
// A return value false indicates that not all probes were successful.
|
||||
bool Tablebases::root_probe(Position& pos,
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rule50,
|
||||
bool rankDTZ) {
|
||||
bool Tablebases::root_probe(Position& pos,
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rule50,
|
||||
bool rankDTZ,
|
||||
const std::function<bool()>& time_abort) {
|
||||
|
||||
ProbeState result = OK;
|
||||
StateInfo st;
|
||||
@@ -1640,7 +1649,7 @@ bool Tablebases::root_probe(Position& pos,
|
||||
|
||||
pos.undo_move(m.pv[0]);
|
||||
|
||||
if (result == FAIL)
|
||||
if (time_abort() || result == FAIL)
|
||||
return false;
|
||||
|
||||
// Better moves are ranked higher. Certain wins are ranked equally.
|
||||
@@ -1705,10 +1714,11 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, boo
|
||||
return true;
|
||||
}
|
||||
|
||||
Config Tablebases::rank_root_moves(const OptionsMap& options,
|
||||
Position& pos,
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rankDTZ) {
|
||||
Config Tablebases::rank_root_moves(const OptionsMap& options,
|
||||
Position& pos,
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rankDTZ,
|
||||
const std::function<bool()>& time_abort) {
|
||||
Config config;
|
||||
|
||||
if (rootMoves.empty())
|
||||
@@ -1731,10 +1741,11 @@ Config Tablebases::rank_root_moves(const OptionsMap& options,
|
||||
|
||||
if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
|
||||
{
|
||||
// Rank moves using DTZ tables
|
||||
config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ);
|
||||
// Rank moves using DTZ tables, bail out if time_abort flags zeitnot
|
||||
config.rootInTB =
|
||||
root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ, time_abort);
|
||||
|
||||
if (!config.rootInTB)
|
||||
if (!config.rootInTB && !time_abort())
|
||||
{
|
||||
// DTZ tables are missing; try to rank moves using WDL tables
|
||||
dtz_available = false;
|
||||
|
||||
+13
-6
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -19,6 +19,7 @@
|
||||
#ifndef TBPROBE_H
|
||||
#define TBPROBE_H
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -66,12 +67,18 @@ extern int MaxCardinality;
|
||||
void init(const std::string& paths);
|
||||
WDLScore probe_wdl(Position& pos, ProbeState* result);
|
||||
int probe_dtz(Position& pos, ProbeState* result);
|
||||
bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50, bool rankDTZ);
|
||||
bool root_probe(Position& pos,
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rule50,
|
||||
bool rankDTZ,
|
||||
const std::function<bool()>& time_abort);
|
||||
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50);
|
||||
Config rank_root_moves(const OptionsMap& options,
|
||||
Position& pos,
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rankDTZ = false);
|
||||
Config rank_root_moves(
|
||||
const OptionsMap& options,
|
||||
Position& pos,
|
||||
Search::RootMoves& rootMoves,
|
||||
bool rankDTZ = false,
|
||||
const std::function<bool()>& time_abort = []() { return false; });
|
||||
|
||||
} // namespace Stockfish::Tablebases
|
||||
|
||||
|
||||
+64
-18
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -21,11 +21,15 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "bitboard.h"
|
||||
#include "history.h"
|
||||
#include "memory.h"
|
||||
#include "movegen.h"
|
||||
#include "search.h"
|
||||
#include "syzygy/tbprobe.h"
|
||||
@@ -41,8 +45,12 @@ namespace Stockfish {
|
||||
Thread::Thread(Search::SharedState& sharedState,
|
||||
std::unique_ptr<Search::ISearchManager> sm,
|
||||
size_t n,
|
||||
size_t numaN,
|
||||
size_t totalNumaCount,
|
||||
OptionalThreadToNumaNodeBinder binder) :
|
||||
idx(n),
|
||||
idxInNuma(numaN),
|
||||
totalNuma(totalNumaCount),
|
||||
nthreads(sharedState.options["Threads"]),
|
||||
stdThread(&Thread::idle_loop, this) {
|
||||
|
||||
@@ -53,8 +61,8 @@ Thread::Thread(Search::SharedState& sharedState,
|
||||
// the Worker allocation. Ideally we would also allocate the SearchManager
|
||||
// here, but that's minor.
|
||||
this->numaAccessToken = binder();
|
||||
this->worker =
|
||||
std::make_unique<Search::Worker>(sharedState, std::move(sm), n, this->numaAccessToken);
|
||||
this->worker = make_unique_large_page<Search::Worker>(
|
||||
sharedState, std::move(sm), n, idxInNuma, totalNuma, this->numaAccessToken);
|
||||
});
|
||||
|
||||
wait_for_search_finished();
|
||||
@@ -133,6 +141,8 @@ Search::SearchManager* ThreadPool::main_manager() { return main_thread()->worker
|
||||
uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); }
|
||||
uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); }
|
||||
|
||||
static size_t next_power_of_two(uint64_t count) { return count > 1 ? (2ULL << msb(count - 1)) : 1; }
|
||||
|
||||
// Creates/destroys threads to match the requested number.
|
||||
// Created and launched threads will immediately go to sleep in idle_loop.
|
||||
// Upon resizing, threads are recreated to allow for binding if necessary.
|
||||
@@ -171,27 +181,63 @@ void ThreadPool::set(const NumaConfig& numaConfig,
|
||||
return true;
|
||||
}();
|
||||
|
||||
std::map<NumaIndex, size_t> counts;
|
||||
boundThreadToNumaNode = doBindThreads
|
||||
? numaConfig.distribute_threads_among_numa_nodes(requested)
|
||||
: std::vector<NumaIndex>{};
|
||||
|
||||
if (boundThreadToNumaNode.empty())
|
||||
counts[0] = requested; // Pretend all threads are part of numa node 0
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < boundThreadToNumaNode.size(); ++i)
|
||||
counts[boundThreadToNumaNode[i]]++;
|
||||
}
|
||||
|
||||
sharedState.sharedHistories.clear();
|
||||
for (auto pair : counts)
|
||||
{
|
||||
NumaIndex numaIndex = pair.first;
|
||||
uint64_t count = pair.second;
|
||||
auto f = [&]() {
|
||||
sharedState.sharedHistories.try_emplace(numaIndex, next_power_of_two(count));
|
||||
};
|
||||
if (doBindThreads)
|
||||
numaConfig.execute_on_numa_node(numaIndex, f);
|
||||
else
|
||||
f();
|
||||
}
|
||||
|
||||
auto threadsPerNode = counts;
|
||||
counts.clear();
|
||||
|
||||
while (threads.size() < requested)
|
||||
{
|
||||
const size_t threadId = threads.size();
|
||||
const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0;
|
||||
auto manager = threadId == 0 ? std::unique_ptr<Search::ISearchManager>(
|
||||
std::make_unique<Search::SearchManager>(updateContext))
|
||||
: std::make_unique<Search::NullSearchManager>();
|
||||
const size_t threadId = threads.size();
|
||||
const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0;
|
||||
auto create_thread = [&]() {
|
||||
auto manager = threadId == 0
|
||||
? std::unique_ptr<Search::ISearchManager>(
|
||||
std::make_unique<Search::SearchManager>(updateContext))
|
||||
: std::make_unique<Search::NullSearchManager>();
|
||||
|
||||
// When not binding threads we want to force all access to happen
|
||||
// from the same NUMA node, because in case of NUMA replicated memory
|
||||
// accesses we don't want to trash cache in case the threads get scheduled
|
||||
// on the same NUMA node.
|
||||
auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId)
|
||||
: OptionalThreadToNumaNodeBinder(numaId);
|
||||
// When not binding threads we want to force all access to happen
|
||||
// from the same NUMA node, because in case of NUMA replicated memory
|
||||
// accesses we don't want to trash cache in case the threads get scheduled
|
||||
// on the same NUMA node.
|
||||
auto binder = doBindThreads ? OptionalThreadToNumaNodeBinder(numaConfig, numaId)
|
||||
: OptionalThreadToNumaNodeBinder(numaId);
|
||||
|
||||
threads.emplace_back(
|
||||
std::make_unique<Thread>(sharedState, std::move(manager), threadId, binder));
|
||||
threads.emplace_back(std::make_unique<Thread>(sharedState, std::move(manager),
|
||||
threadId, counts[numaId]++,
|
||||
threadsPerNode[numaId], binder));
|
||||
};
|
||||
|
||||
// Ensure the worker thread inherits the intended NUMA affinity at creation.
|
||||
if (doBindThreads)
|
||||
numaConfig.execute_on_numa_node(numaId, create_thread);
|
||||
else
|
||||
create_thread();
|
||||
}
|
||||
|
||||
clear();
|
||||
@@ -282,8 +328,8 @@ void ThreadPool::start_thinking(const OptionsMap& options,
|
||||
{
|
||||
th->run_custom_job([&]() {
|
||||
th->worker->limits = limits;
|
||||
th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly =
|
||||
th->worker->bestMoveChanges = 0;
|
||||
th->worker->nodes = th->worker->tbHits = th->worker->bestMoveChanges = 0;
|
||||
th->worker->nmpMinPly = 0;
|
||||
th->worker->rootDepth = th->worker->completedDepth = 0;
|
||||
th->worker->rootMoves = rootMoves;
|
||||
th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState);
|
||||
|
||||
+8
-5
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "memory.h"
|
||||
#include "numa.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
@@ -75,6 +76,8 @@ class Thread {
|
||||
Thread(Search::SharedState&,
|
||||
std::unique_ptr<Search::ISearchManager>,
|
||||
size_t,
|
||||
size_t,
|
||||
size_t,
|
||||
OptionalThreadToNumaNodeBinder);
|
||||
virtual ~Thread();
|
||||
|
||||
@@ -93,13 +96,13 @@ class Thread {
|
||||
void wait_for_search_finished();
|
||||
size_t id() const { return idx; }
|
||||
|
||||
std::unique_ptr<Search::Worker> worker;
|
||||
std::function<void()> jobFunc;
|
||||
LargePagePtr<Search::Worker> worker;
|
||||
std::function<void()> jobFunc;
|
||||
|
||||
private:
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
size_t idx, nthreads;
|
||||
size_t idx, idxInNuma, totalNuma, nthreads;
|
||||
bool exit = false, searching = true; // Set before starting std::thread
|
||||
NativeThread stdThread;
|
||||
NumaReplicatedAccessToken numaAccessToken;
|
||||
@@ -164,7 +167,7 @@ class ThreadPool {
|
||||
std::vector<std::unique_ptr<Thread>> threads;
|
||||
std::vector<NumaIndex> boundThreadToNumaNode;
|
||||
|
||||
uint64_t accumulate(std::atomic<uint64_t> Search::Worker::*member) const {
|
||||
uint64_t accumulate(std::atomic<uint64_t> Search::Worker::* member) const {
|
||||
|
||||
uint64_t sum = 0;
|
||||
for (auto&& th : threads)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
+3
-6
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -85,16 +85,13 @@ void TimeManagement::init(Search::LimitsType& limits,
|
||||
// with constants are involved.
|
||||
const int64_t scaleFactor = useNodesTime ? npmsec : 1;
|
||||
const TimePoint scaledTime = limits.time[us] / scaleFactor;
|
||||
const TimePoint scaledInc = limits.inc[us] / scaleFactor;
|
||||
|
||||
// Maximum move horizon
|
||||
int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051;
|
||||
|
||||
// If less than one second, gradually reduce mtg
|
||||
if (scaledTime < 1000 && double(centiMTG) / scaledInc > 5.051)
|
||||
{
|
||||
centiMTG = scaledTime * 5.051;
|
||||
}
|
||||
if (scaledTime < 1000)
|
||||
centiMTG = int(scaledTime * 5.051);
|
||||
|
||||
// Make sure timeLeft is > 0 since we may use it as a divisor
|
||||
TimePoint timeLeft =
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -22,11 +22,11 @@
|
||||
#include <cstdint>
|
||||
|
||||
#include "misc.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
class OptionsMap;
|
||||
enum Color : uint8_t;
|
||||
|
||||
namespace Search {
|
||||
struct LimitsType;
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -234,8 +234,8 @@ std::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) cons
|
||||
// Find an entry to be replaced according to the replacement strategy
|
||||
TTEntry* replace = tte;
|
||||
for (int i = 1; i < ClusterSize; ++i)
|
||||
if (replace->depth8 - replace->relative_age(generation8) * 2
|
||||
> tte[i].depth8 - tte[i].relative_age(generation8) * 2)
|
||||
if (replace->depth8 - replace->relative_age(generation8)
|
||||
> tte[i].depth8 - tte[i].relative_age(generation8))
|
||||
replace = &tte[i];
|
||||
|
||||
return {false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
||||
+11
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -170,11 +170,20 @@ class Tune {
|
||||
static OptionsMap* options;
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
constexpr void tune_check_args(Args&&...) {
|
||||
static_assert((!std::is_fundamental_v<Args> && ...), "TUNE macro arguments wrong");
|
||||
}
|
||||
|
||||
// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add()
|
||||
#define STRINGIFY(x) #x
|
||||
#define UNIQUE2(x, y) x##y
|
||||
#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__
|
||||
#define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__)
|
||||
#define TUNE(...) \
|
||||
int UNIQUE(p, __LINE__) = []() -> int { \
|
||||
tune_check_args(__VA_ARGS__); \
|
||||
return Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__); \
|
||||
}();
|
||||
|
||||
#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true
|
||||
|
||||
|
||||
+101
-49
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -37,7 +37,10 @@
|
||||
// | only in 64-bit mode and requires hardware with pext support.
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include "misc.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
// Disable some silly and noisy warnings from MSVC compiler
|
||||
@@ -55,9 +58,15 @@
|
||||
// _WIN32 Building on Windows (any)
|
||||
// _WIN64 Building on Windows 64 bit
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) \
|
||||
&& defined(_WIN32) && !defined(__clang__)
|
||||
#define ALIGNAS_ON_STACK_VARIABLES_BROKEN
|
||||
// Enforce minimum GCC version
|
||||
#if defined(__GNUC__) && !defined(__clang__) \
|
||||
&& (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ < 3))
|
||||
#error "Stockfish requires GCC 9.3 or later for correct compilation"
|
||||
#endif
|
||||
|
||||
// Enforce minimum Clang version
|
||||
#if defined(__clang__) && (__clang_major__ < 10)
|
||||
#error "Stockfish requires Clang 10.0 or later for correct compilation"
|
||||
#endif
|
||||
|
||||
#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
|
||||
@@ -108,13 +117,13 @@ using Bitboard = uint64_t;
|
||||
constexpr int MAX_MOVES = 256;
|
||||
constexpr int MAX_PLY = 246;
|
||||
|
||||
enum Color {
|
||||
enum Color : uint8_t {
|
||||
WHITE,
|
||||
BLACK,
|
||||
COLOR_NB = 2
|
||||
};
|
||||
|
||||
enum CastlingRights {
|
||||
enum CastlingRights : uint8_t {
|
||||
NO_CASTLING,
|
||||
WHITE_OO,
|
||||
WHITE_OOO = WHITE_OO << 1,
|
||||
@@ -130,7 +139,7 @@ enum CastlingRights {
|
||||
CASTLING_RIGHT_NB = 16
|
||||
};
|
||||
|
||||
enum Bound {
|
||||
enum Bound : uint8_t {
|
||||
BOUND_NONE,
|
||||
BOUND_UPPER,
|
||||
BOUND_LOWER,
|
||||
@@ -181,13 +190,13 @@ constexpr Value QueenValue = 2538;
|
||||
|
||||
|
||||
// clang-format off
|
||||
enum PieceType {
|
||||
enum PieceType : std::uint8_t {
|
||||
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
|
||||
ALL_PIECES = 0,
|
||||
PIECE_TYPE_NB = 8
|
||||
};
|
||||
|
||||
enum Piece {
|
||||
enum Piece : std::uint8_t {
|
||||
NO_PIECE,
|
||||
W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
|
||||
B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
|
||||
@@ -201,26 +210,24 @@ constexpr Value PieceValue[PIECE_NB] = {
|
||||
|
||||
using Depth = int;
|
||||
|
||||
enum : int {
|
||||
// The following DEPTH_ constants are used for transposition table entries
|
||||
// and quiescence search move generation stages. In regular search, the
|
||||
// depth stored in the transposition table is literal: the search depth
|
||||
// (effort) used to make the corresponding transposition table value. In
|
||||
// quiescence search, however, the transposition table entries only store
|
||||
// the current quiescence move generation stage (which should thus compare
|
||||
// lower than any regular search depth).
|
||||
DEPTH_QS = 0,
|
||||
// For transposition table entries where no searching at all was done
|
||||
// (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus
|
||||
// compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET
|
||||
// is used only for the transposition table entry occupancy check (see tt.cpp),
|
||||
// and should thus be lower than DEPTH_UNSEARCHED.
|
||||
DEPTH_UNSEARCHED = -2,
|
||||
DEPTH_ENTRY_OFFSET = -3
|
||||
};
|
||||
// The following DEPTH_ constants are used for transposition table entries
|
||||
// and quiescence search move generation stages. In regular search, the
|
||||
// depth stored in the transposition table is literal: the search depth
|
||||
// (effort) used to make the corresponding transposition table value. In
|
||||
// quiescence search, however, the transposition table entries only store
|
||||
// the current quiescence move generation stage (which should thus compare
|
||||
// lower than any regular search depth).
|
||||
constexpr Depth DEPTH_QS = 0;
|
||||
// For transposition table entries where no searching at all was done
|
||||
// (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus
|
||||
// compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET
|
||||
// is used only for the transposition table entry occupancy check (see tt.cpp),
|
||||
// and should thus be lower than DEPTH_UNSEARCHED.
|
||||
constexpr Depth DEPTH_UNSEARCHED = -2;
|
||||
constexpr Depth DEPTH_ENTRY_OFFSET = -3;
|
||||
|
||||
// clang-format off
|
||||
enum Square : int {
|
||||
enum Square : uint8_t {
|
||||
SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1,
|
||||
SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2,
|
||||
SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3,
|
||||
@@ -236,7 +243,7 @@ enum Square : int {
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
enum Direction : int {
|
||||
enum Direction : int8_t {
|
||||
NORTH = 8,
|
||||
EAST = 1,
|
||||
SOUTH = -NORTH,
|
||||
@@ -248,7 +255,7 @@ enum Direction : int {
|
||||
NORTH_WEST = NORTH + WEST
|
||||
};
|
||||
|
||||
enum File : int {
|
||||
enum File : uint8_t {
|
||||
FILE_A,
|
||||
FILE_B,
|
||||
FILE_C,
|
||||
@@ -260,7 +267,7 @@ enum File : int {
|
||||
FILE_NB
|
||||
};
|
||||
|
||||
enum Rank : int {
|
||||
enum Rank : uint8_t {
|
||||
RANK_1,
|
||||
RANK_2,
|
||||
RANK_3,
|
||||
@@ -274,23 +281,62 @@ enum Rank : int {
|
||||
|
||||
// Keep track of what a move changes on the board (used by NNUE)
|
||||
struct DirtyPiece {
|
||||
Piece pc; // this is never allowed to be NO_PIECE
|
||||
Square from, to; // to should be SQ_NONE for promotions
|
||||
|
||||
// Number of changed pieces
|
||||
int dirty_num;
|
||||
// if {add,remove}_sq is SQ_NONE, {add,remove}_pc is allowed to be
|
||||
// uninitialized
|
||||
// castling uses add_sq and remove_sq to remove and add the rook
|
||||
Square remove_sq, add_sq;
|
||||
Piece remove_pc, add_pc;
|
||||
};
|
||||
|
||||
// Max 3 pieces can change in one move. A promotion with capture moves
|
||||
// both the pawn and the captured piece to SQ_NONE and the piece promoted
|
||||
// to from SQ_NONE to the capture square.
|
||||
Piece piece[3];
|
||||
// Keep track of what threats change on the board (used by NNUE)
|
||||
struct DirtyThreat {
|
||||
static constexpr int PcSqOffset = 0;
|
||||
static constexpr int ThreatenedSqOffset = 8;
|
||||
static constexpr int ThreatenedPcOffset = 16;
|
||||
static constexpr int PcOffset = 20;
|
||||
|
||||
// From and to squares, which may be SQ_NONE
|
||||
Square from[3];
|
||||
Square to[3];
|
||||
DirtyThreat() { /* don't initialize data */ }
|
||||
DirtyThreat(uint32_t raw) :
|
||||
data(raw) {}
|
||||
DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) {
|
||||
data = (uint32_t(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset)
|
||||
| (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset);
|
||||
}
|
||||
|
||||
Piece pc() const { return static_cast<Piece>(data >> PcOffset & 0xf); }
|
||||
Piece threatened_pc() const { return static_cast<Piece>(data >> ThreatenedPcOffset & 0xf); }
|
||||
Square threatened_sq() const { return static_cast<Square>(data >> ThreatenedSqOffset & 0xff); }
|
||||
Square pc_sq() const { return static_cast<Square>(data >> PcSqOffset & 0xff); }
|
||||
bool add() const { return data >> 31; }
|
||||
uint32_t raw() const { return data; }
|
||||
|
||||
private:
|
||||
uint32_t data;
|
||||
};
|
||||
|
||||
// A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks.
|
||||
// Moving a piece also can reveal at most 8 discovered attacks.
|
||||
// This implies that a non-castling move can change at most (8 + 16) * 3 + 8 = 80 features.
|
||||
// By similar logic, a castling move can change at most (5 + 1 + 3 + 9) * 2 = 36 features.
|
||||
// Thus, 80 should work as an upper bound. Finally, 16 entries are added to accommodate
|
||||
// unmasked vector stores near the end of the list.
|
||||
|
||||
using DirtyThreatList = ValueList<DirtyThreat, 96>;
|
||||
|
||||
struct DirtyThreats {
|
||||
DirtyThreatList list;
|
||||
Color us;
|
||||
Square prevKsq, ksq;
|
||||
|
||||
Bitboard threatenedSqs, threateningSqs;
|
||||
};
|
||||
|
||||
#define ENABLE_INCR_OPERATORS_ON(T) \
|
||||
inline T& operator++(T& d) { return d = T(int(d) + 1); } \
|
||||
inline T& operator--(T& d) { return d = T(int(d) - 1); }
|
||||
constexpr T& operator++(T& d) { return d = T(int(d) + 1); } \
|
||||
constexpr T& operator--(T& d) { return d = T(int(d) - 1); }
|
||||
|
||||
ENABLE_INCR_OPERATORS_ON(PieceType)
|
||||
ENABLE_INCR_OPERATORS_ON(Square)
|
||||
@@ -303,10 +349,10 @@ constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int
|
||||
constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); }
|
||||
|
||||
// Additional operators to add a Direction to a Square
|
||||
constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
|
||||
constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); }
|
||||
inline Square& operator+=(Square& s, Direction d) { return s = s + d; }
|
||||
inline Square& operator-=(Square& s, Direction d) { return s = s - d; }
|
||||
constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
|
||||
constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); }
|
||||
constexpr Square& operator+=(Square& s, Direction d) { return s = s + d; }
|
||||
constexpr Square& operator-=(Square& s, Direction d) { return s = s - d; }
|
||||
|
||||
// Toggle color
|
||||
constexpr Color operator~(Color c) { return Color(c ^ BLACK); }
|
||||
@@ -334,7 +380,7 @@ constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt);
|
||||
|
||||
constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); }
|
||||
|
||||
inline Color color_of(Piece pc) {
|
||||
constexpr Color color_of(Piece pc) {
|
||||
assert(pc != NO_PIECE);
|
||||
return Color(pc >> 3);
|
||||
}
|
||||
@@ -360,7 +406,7 @@ constexpr Key make_key(uint64_t seed) {
|
||||
}
|
||||
|
||||
|
||||
enum MoveType {
|
||||
enum MoveType : uint16_t {
|
||||
NORMAL,
|
||||
PROMOTION = 1 << 14,
|
||||
EN_PASSANT = 2 << 14,
|
||||
@@ -403,8 +449,6 @@ class Move {
|
||||
return Square(data & 0x3F);
|
||||
}
|
||||
|
||||
constexpr int from_to() const { return data & 0xFFF; }
|
||||
|
||||
constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); }
|
||||
|
||||
constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); }
|
||||
@@ -429,6 +473,14 @@ class Move {
|
||||
std::uint16_t data;
|
||||
};
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
struct is_all_same {
|
||||
static constexpr bool value = (std::is_same_v<T, Ts> && ...);
|
||||
};
|
||||
|
||||
template<typename... Ts>
|
||||
constexpr auto is_all_same_v = is_all_same<Ts...>::value;
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif // #ifndef TYPES_H_INCLUDED
|
||||
|
||||
+14
-20
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -313,8 +313,8 @@ void UCIEngine::benchmark(std::istream& args) {
|
||||
|
||||
Benchmark::BenchmarkSetup setup = Benchmark::setup_benchmark(args);
|
||||
|
||||
const int numGoCommands = count_if(setup.commands.begin(), setup.commands.end(),
|
||||
[](const std::string& s) { return s.find("go ") == 0; });
|
||||
const auto numGoCommands = count_if(setup.commands.begin(), setup.commands.end(),
|
||||
[](const std::string& s) { return s.find("go ") == 0; });
|
||||
|
||||
TimePoint totalTime = 0;
|
||||
|
||||
@@ -339,16 +339,9 @@ void UCIEngine::benchmark(std::istream& args) {
|
||||
|
||||
Search::LimitsType limits = parse_limits(is);
|
||||
|
||||
TimePoint elapsed = now();
|
||||
|
||||
// Run with silenced network verification
|
||||
engine.go(limits);
|
||||
engine.wait_for_search_finished();
|
||||
|
||||
totalTime += now() - elapsed;
|
||||
|
||||
nodes += nodesSearched;
|
||||
nodesSearched = 0;
|
||||
}
|
||||
else if (token == "position")
|
||||
position(is);
|
||||
@@ -368,13 +361,14 @@ void UCIEngine::benchmark(std::istream& args) {
|
||||
|
||||
int numHashfullReadings = 0;
|
||||
constexpr int hashfullAges[] = {0, 999}; // Only normal hashfull and touched hash.
|
||||
int totalHashfull[std::size(hashfullAges)] = {0};
|
||||
int maxHashfull[std::size(hashfullAges)] = {0};
|
||||
constexpr int hashfullAgeCount = std::size(hashfullAges);
|
||||
int totalHashfull[hashfullAgeCount] = {0};
|
||||
int maxHashfull[hashfullAgeCount] = {0};
|
||||
|
||||
auto updateHashfullReadings = [&]() {
|
||||
numHashfullReadings += 1;
|
||||
|
||||
for (int i = 0; i < static_cast<int>(std::size(hashfullAges)); ++i)
|
||||
for (int i = 0; i < hashfullAgeCount; ++i)
|
||||
{
|
||||
const int hashfull = engine.get_hashfull(hashfullAges[i]);
|
||||
maxHashfull[i] = std::max(maxHashfull[i], hashfull);
|
||||
@@ -396,6 +390,7 @@ void UCIEngine::benchmark(std::istream& args) {
|
||||
|
||||
Search::LimitsType limits = parse_limits(is);
|
||||
|
||||
nodesSearched = 0;
|
||||
TimePoint elapsed = now();
|
||||
|
||||
// Run with silenced network verification
|
||||
@@ -407,7 +402,6 @@ void UCIEngine::benchmark(std::istream& args) {
|
||||
updateHashfullReadings();
|
||||
|
||||
nodes += nodesSearched;
|
||||
nodesSearched = 0;
|
||||
}
|
||||
else if (token == "position")
|
||||
position(is);
|
||||
@@ -513,8 +507,8 @@ WinRateParams win_rate_params(const Position& pos) {
|
||||
double m = std::clamp(material, 17, 78) / 58.0;
|
||||
|
||||
// Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model
|
||||
constexpr double as[] = {-13.50030198, 40.92780883, -36.82753545, 386.83004070};
|
||||
constexpr double bs[] = {96.53354896, -165.79058388, 90.89679019, 49.29561889};
|
||||
constexpr double as[] = {-72.32565836, 185.93832038, -144.58862193, 416.44950446};
|
||||
constexpr double bs[] = {83.86794042, -136.06112997, 69.98820887, 47.62901433};
|
||||
|
||||
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
|
||||
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
|
||||
@@ -561,7 +555,7 @@ int UCIEngine::to_cp(Value v, const Position& pos) {
|
||||
|
||||
auto [a, b] = win_rate_params(pos);
|
||||
|
||||
return std::round(100 * int(v) / a);
|
||||
return int(std::round(100 * int(v) / a));
|
||||
}
|
||||
|
||||
std::string UCIEngine::wdl(Value v, const Position& pos) {
|
||||
@@ -630,12 +624,12 @@ void UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) {
|
||||
<< " multipv " << info.multiPV //
|
||||
<< " score " << format_score(info.score); //
|
||||
|
||||
if (showWDL)
|
||||
ss << " wdl " << info.wdl;
|
||||
|
||||
if (!info.bound.empty())
|
||||
ss << " " << info.bound;
|
||||
|
||||
if (showWDL)
|
||||
ss << " wdl " << info.wdl;
|
||||
|
||||
ss << " nodes " << info.nodes //
|
||||
<< " nps " << info.nps //
|
||||
<< " hashfull " << info.hashfull //
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -33,7 +33,7 @@ namespace Stockfish {
|
||||
class Position;
|
||||
class Move;
|
||||
class Score;
|
||||
enum Square : int;
|
||||
enum Square : uint8_t;
|
||||
using Value = int;
|
||||
|
||||
class UCIEngine {
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -110,7 +110,7 @@ Option::Option(OnChange f) :
|
||||
max(0),
|
||||
on_change(std::move(f)) {}
|
||||
|
||||
Option::Option(double v, int minv, int maxv, OnChange f) :
|
||||
Option::Option(int v, int minv, int maxv, OnChange f) :
|
||||
type("spin"),
|
||||
min(minv),
|
||||
max(maxv),
|
||||
@@ -154,7 +154,7 @@ Option& Option::operator=(const std::string& v) {
|
||||
|
||||
if ((type != "button" && type != "string" && v.empty())
|
||||
|| (type == "check" && v != "true" && v != "false")
|
||||
|| (type == "spin" && (std::stof(v) < min || std::stof(v) > max)))
|
||||
|| (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max)))
|
||||
return *this;
|
||||
|
||||
if (type == "combo")
|
||||
@@ -202,7 +202,7 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
|
||||
}
|
||||
|
||||
else if (o.type == "spin")
|
||||
os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max "
|
||||
os << " default " << stoi(o.defaultValue) << " min " << o.min << " max "
|
||||
<< o.max;
|
||||
|
||||
break;
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||
Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)
|
||||
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
|
||||
|
||||
Stockfish is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -43,7 +43,7 @@ class Option {
|
||||
Option(OnChange = nullptr);
|
||||
Option(bool v, OnChange = nullptr);
|
||||
Option(const char* v, OnChange = nullptr);
|
||||
Option(double v, int minv, int maxv, OnChange = nullptr);
|
||||
Option(int v, int minv, int maxv, OnChange = nullptr);
|
||||
Option(const char* v, const char* cur, OnChange = nullptr);
|
||||
|
||||
Option& operator=(const std::string&);
|
||||
|
||||
@@ -67,7 +67,6 @@ def Stockfish(*args, **kwargs):
|
||||
|
||||
|
||||
class TestCLI(metaclass=OrderedClassMembers):
|
||||
|
||||
def beforeAll(self):
|
||||
pass
|
||||
|
||||
@@ -141,7 +140,7 @@ class TestCLI(metaclass=OrderedClassMembers):
|
||||
|
||||
def test_bench_128_threads_3_bench_tmp_epd_depth(self):
|
||||
self.stockfish = Stockfish(
|
||||
f"bench 128 {get_threads()} 3 {os.path.join(PATH,'bench_tmp.epd')} depth".split(
|
||||
f"bench 128 {get_threads()} 3 {os.path.join(PATH, 'bench_tmp.epd')} depth".split(
|
||||
" "
|
||||
),
|
||||
True,
|
||||
@@ -167,7 +166,7 @@ class TestCLI(metaclass=OrderedClassMembers):
|
||||
def test_export_net_verify_nnue(self):
|
||||
current_path = os.path.abspath(os.getcwd())
|
||||
self.stockfish = Stockfish(
|
||||
f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True
|
||||
f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True
|
||||
)
|
||||
assert self.stockfish.process.returncode == 0
|
||||
|
||||
@@ -254,7 +253,7 @@ class TestInteractive(metaclass=OrderedClassMembers):
|
||||
self.stockfish.send_command("go depth 5")
|
||||
|
||||
def callback(output):
|
||||
regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv"
|
||||
regex = r"info depth \d+ seldepth \d+ multipv \d+ score cp -?\d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv"
|
||||
if output.startswith("info depth") and not re.match(regex, output):
|
||||
assert False
|
||||
if output.startswith("bestmove"):
|
||||
@@ -274,7 +273,7 @@ class TestInteractive(metaclass=OrderedClassMembers):
|
||||
def callback(output):
|
||||
nonlocal depth
|
||||
|
||||
regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv"
|
||||
regex = rf"info depth {depth} seldepth \d+ multipv \d+ score cp -?\d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv"
|
||||
|
||||
if output.startswith("info depth"):
|
||||
if not re.match(regex, output):
|
||||
@@ -390,7 +389,7 @@ class TestInteractive(metaclass=OrderedClassMembers):
|
||||
def test_verify_nnue_network(self):
|
||||
current_path = os.path.abspath(os.getcwd())
|
||||
Stockfish(
|
||||
f"export_net {os.path.join(current_path , 'verify.nnue')}".split(" "), True
|
||||
f"export_net {os.path.join(current_path, 'verify.nnue')}".split(" "), True
|
||||
)
|
||||
|
||||
self.stockfish.send_command("setoption name EvalFile value verify.nnue")
|
||||
@@ -469,7 +468,7 @@ class TestSyzygy(metaclass=OrderedClassMembers):
|
||||
self.stockfish.send_command("go depth 5")
|
||||
|
||||
def check_output(output):
|
||||
if "score cp -20000" in output or "score mate" in output:
|
||||
if "score cp -20000" in output or "score mate -" in output:
|
||||
return True
|
||||
|
||||
self.stockfish.check_output(check_output)
|
||||
@@ -508,7 +507,7 @@ if __name__ == "__main__":
|
||||
|
||||
framework = MiniTestFramework()
|
||||
|
||||
# Each test suite will be ran inside a temporary directory
|
||||
# Each test suite will be run inside a temporary directory
|
||||
framework.run([TestCLI, TestInteractive, TestSyzygy])
|
||||
|
||||
EPD.delete_bench_epd()
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ EXPECT_SCRIPT=$(mktemp)
|
||||
|
||||
cat << 'EOF' > $EXPECT_SCRIPT
|
||||
#!/usr/bin/expect -f
|
||||
set timeout 30
|
||||
set timeout 120
|
||||
lassign [lrange $argv 0 4] pos depth result chess960 logfile
|
||||
log_file -noappend $logfile
|
||||
spawn ./stockfish
|
||||
|
||||
+12
-2
@@ -2,16 +2,26 @@
|
||||
# obtain and optionally verify Bench / signature
|
||||
# if no reference is given, the output is deliberately limited to just the signature
|
||||
|
||||
STDOUT_FILE=$(mktemp)
|
||||
STDERR_FILE=$(mktemp)
|
||||
|
||||
error()
|
||||
{
|
||||
echo "running bench for signature failed on line $1"
|
||||
echo "===== STDOUT ====="
|
||||
cat "$STDOUT_FILE"
|
||||
echo "===== STDERR ====="
|
||||
cat "$STDERR_FILE"
|
||||
rm -f "$STDOUT_FILE" "$STDERR_FILE"
|
||||
exit 1
|
||||
}
|
||||
trap 'error ${LINENO}' ERR
|
||||
|
||||
# obtain
|
||||
eval "$RUN_PREFIX ./stockfish bench" > "$STDOUT_FILE" 2> "$STDERR_FILE" || error ${LINENO}
|
||||
signature=$(grep "Nodes searched : " "$STDERR_FILE" | awk '{print $4}')
|
||||
|
||||
signature=`eval "$WINE_PATH ./stockfish bench 2>&1" | grep "Nodes searched : " | awk '{print $4}'`
|
||||
rm -f "$STDOUT_FILE" "$STDERR_FILE"
|
||||
|
||||
if [ $# -gt 0 ]; then
|
||||
# compare to given reference
|
||||
@@ -28,4 +38,4 @@ if [ $# -gt 0 ]; then
|
||||
else
|
||||
# just report signature
|
||||
echo $signature
|
||||
fi
|
||||
fi
|
||||
Reference in New Issue
Block a user