mirror of
https://github.com/official-stockfish/Stockfish.git
synced 2026-07-22 12:47:08 +00:00
Compare commits
@@ -4,4 +4,8 @@
|
||||
|
||||
# Post commit formatting fixes
|
||||
0fca5605fa2e5e7240fde5e1aae50952b2612231
|
||||
08ed4c90db31959521b7ef3186c026edd1e90307
|
||||
08ed4c90db31959521b7ef3186c026edd1e90307
|
||||
|
||||
# Ignore commits related to integer type renamings
|
||||
dd3e1c4a50fd358c8bf8051c9ef73219669ff6c0
|
||||
6e4e03fd282d5e1cba514197bf1ef326c990f0d2
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
name: Report issue
|
||||
description: Create a report to help us fix issues with the engine
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> [!IMPORTANT]
|
||||
> Please do not open bug reports regarding the engine's choice of moves, such as the engine failing to find a particular "best" move. If you have any questions or wish to discuss engine analysis, please use our community channels, such as Discord.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the issue
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"emu": "qemu-aarch64",
|
||||
"comp": "ndk",
|
||||
"shell": "bash",
|
||||
"archive_ext": "tar"
|
||||
"archive_ext": "tar.gz"
|
||||
},
|
||||
{
|
||||
"name": "Android NDK arm",
|
||||
@@ -18,29 +18,20 @@
|
||||
"emu": "qemu-arm",
|
||||
"comp": "ndk",
|
||||
"shell": "bash",
|
||||
"archive_ext": "tar"
|
||||
"archive_ext": "tar.gz"
|
||||
}
|
||||
],
|
||||
"binaries": ["armv8-dotprod", "armv8", "armv7", "armv7-neon"],
|
||||
"binaries": [
|
||||
"arm64-universal",
|
||||
"armv7-neon"
|
||||
],
|
||||
"exclude": [
|
||||
{
|
||||
"binaries": "armv8-dotprod",
|
||||
"binaries": "arm64-universal",
|
||||
"config": {
|
||||
"compiler": "armv7a-linux-androideabi29-clang++"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv8",
|
||||
"config": {
|
||||
"compiler": "armv7a-linux-androideabi29-clang++"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv7",
|
||||
"config": {
|
||||
"compiler": "aarch64-linux-android29-clang++"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "armv7-neon",
|
||||
"config": {
|
||||
|
||||
@@ -1,282 +0,0 @@
|
||||
{
|
||||
"config": [
|
||||
{
|
||||
"name": "Ubuntu 22.04 GCC",
|
||||
"os": "ubuntu-22.04",
|
||||
"simple_name": "ubuntu",
|
||||
"compiler": "g++",
|
||||
"comp": "gcc",
|
||||
"shell": "bash",
|
||||
"archive_ext": "tar",
|
||||
"sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-lin/sde -future --"
|
||||
},
|
||||
{
|
||||
"name": "macOS 15 Apple Clang",
|
||||
"os": "macos-15-intel",
|
||||
"simple_name": "macos",
|
||||
"compiler": "clang++",
|
||||
"comp": "clang",
|
||||
"shell": "bash",
|
||||
"archive_ext": "tar"
|
||||
},
|
||||
{
|
||||
"name": "macOS 15 Apple Clang M1",
|
||||
"os": "macos-15",
|
||||
"simple_name": "macos-m1",
|
||||
"compiler": "clang++",
|
||||
"comp": "clang",
|
||||
"shell": "bash",
|
||||
"archive_ext": "tar"
|
||||
},
|
||||
{
|
||||
"name": "Windows 2022 Mingw-w64 GCC x86_64",
|
||||
"os": "windows-2022",
|
||||
"simple_name": "windows",
|
||||
"compiler": "g++",
|
||||
"comp": "mingw",
|
||||
"msys_sys": "mingw64",
|
||||
"msys_env": "x86_64-gcc",
|
||||
"shell": "msys2 {0}",
|
||||
"ext": ".exe",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"binaries": [
|
||||
"x86-64",
|
||||
"x86-64-sse41-popcnt",
|
||||
"x86-64-avx2",
|
||||
"x86-64-bmi2",
|
||||
"x86-64-avxvnni",
|
||||
"x86-64-avx512",
|
||||
"x86-64-vnni512",
|
||||
"x86-64-avx512icl",
|
||||
"apple-silicon",
|
||||
"armv8",
|
||||
"armv8-dotprod"
|
||||
],
|
||||
"exclude": [
|
||||
{
|
||||
"binaries": "x86-64",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-sse41-popcnt",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx2",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-bmi2",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avxvnni",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx512",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-vnni512",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx512icl",
|
||||
"config": {
|
||||
"os": "macos-15"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avxvnni",
|
||||
"config": {
|
||||
"os": "macos-15-intel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-avx512",
|
||||
"config": {
|
||||
"os": "macos-15-intel"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "x86-64-vnni512",
|
||||
"config": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "apple-silicon",
|
||||
"config": {
|
||||
"os": "windows-2022"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "apple-silicon",
|
||||
"config": {
|
||||
"os": "windows-11-arm"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "apple-silicon",
|
||||
"config": {
|
||||
"os": "ubuntu-20.04"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binaries": "apple-silicon",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"include": [
|
||||
{
|
||||
"config": {
|
||||
"name": "Ubuntu 22.04 GCC Universal",
|
||||
"os": "ubuntu-22.04",
|
||||
"simple_name": "linux",
|
||||
"archive_ext": "tar.gz"
|
||||
},
|
||||
"binaries": "x86-64-universal"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"name": "Windows 2022 Mingw-w64 GCC Universal",
|
||||
"os": "windows-2022",
|
||||
"simple_name": "windows",
|
||||
"ext": ".exe",
|
||||
"archive_ext": "zip"
|
||||
},
|
||||
"binaries": "x86-64-universal"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"name": "Ubuntu 22.04 GCC Universal arm64",
|
||||
"os": "ubuntu-22.04-arm",
|
||||
"simple_name": "linux",
|
||||
"archive_ext": "tar.gz"
|
||||
},
|
||||
"binaries": "arm64-universal"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"name": "Ubuntu 22.04 GCC Universal riscv64",
|
||||
"os": "ubuntu-22.04",
|
||||
"simple_name": "linux",
|
||||
"archive_ext": "tar.gz"
|
||||
},
|
||||
"binaries": "riscv64-universal"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"name": "Windows 11 Clang Universal arm64",
|
||||
"os": "windows-11-arm",
|
||||
"simple_name": "windows",
|
||||
"ext": ".exe",
|
||||
"archive_ext": "zip"
|
||||
},
|
||||
"binaries": "arm64-universal"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"name": "macOS 15 Apple Clang Universal",
|
||||
"os": "macos-15",
|
||||
"simple_name": "macos",
|
||||
"archive_ext": "tar.gz"
|
||||
},
|
||||
"binaries": "universal"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Compilation
|
||||
name: Android Compilation
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
required: true
|
||||
jobs:
|
||||
Compilation:
|
||||
name: ${{ matrix.config.name }} ${{ matrix.binaries }}
|
||||
name: "Android build: ${{ matrix.config.name }} ${{ matrix.binaries }}"
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
env:
|
||||
COMPCXX: ${{ matrix.config.compiler }}
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
working-directory: src
|
||||
shell: ${{ matrix.config.shell }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
@@ -86,10 +86,10 @@ jobs:
|
||||
mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT
|
||||
|
||||
- name: Remove non src files
|
||||
run: git clean -fx
|
||||
run: git clean -fxd
|
||||
|
||||
- name: Upload artifact for (pre)-release
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
|
||||
path: |
|
||||
|
||||
@@ -22,7 +22,6 @@ jobs:
|
||||
- { 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" }
|
||||
@@ -38,7 +37,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -74,7 +73,6 @@ jobs:
|
||||
run: |
|
||||
export CXXFLAGS="-Werror"
|
||||
if [ "${{ matrix.ver }}" -ge 20 ]; then
|
||||
export LDFLAGS="-fuse-ld=lld"
|
||||
apt install -y lld
|
||||
fi
|
||||
make clean
|
||||
|
||||
@@ -12,6 +12,10 @@ on:
|
||||
- "**.cpp"
|
||||
- "**.h"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
@@ -20,12 +24,12 @@ jobs:
|
||||
name: Clang-Format
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Run clang-format style check
|
||||
uses: jidicula/clang-format-action@4726374d1aa3c6aecf132e5197e498979588ebc8 # @v4.15.0
|
||||
uses: jidicula/clang-format-action@654a770daa28443dd111d133e4083e21c1075674 # v4.18.0
|
||||
id: clang-format
|
||||
continue-on-error: true
|
||||
with:
|
||||
@@ -34,24 +38,25 @@ jobs:
|
||||
|
||||
- name: Comment on PR
|
||||
if: steps.clang-format.outcome == 'failure'
|
||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0
|
||||
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
|
||||
with:
|
||||
message: |
|
||||
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/plucky/clang-format-20.
|
||||
For the exact version please see https://packages.ubuntu.com/questing/clang-format-20.
|
||||
An easier way to install it might be through https://apt.llvm.org/#llvmsh.
|
||||
|
||||
_(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
|
||||
comment_tag: execution
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
comment-tag: execution
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Comment on PR
|
||||
if: steps.clang-format.outcome != 'failure'
|
||||
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0
|
||||
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
|
||||
with:
|
||||
message: |
|
||||
_(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
|
||||
create_if_not_exists: false
|
||||
comment_tag: execution
|
||||
create-if-not-exists: false
|
||||
comment-tag: execution
|
||||
mode: delete
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -9,6 +9,10 @@ on:
|
||||
schedule:
|
||||
- cron: "17 18 * * 1"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
@@ -29,13 +33,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -50,6 +54,6 @@ jobs:
|
||||
run: make -j build
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
name: Compilation
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
matrix:
|
||||
type: string
|
||||
required: true
|
||||
jobs:
|
||||
Compilation:
|
||||
name: ${{ matrix.config.name }} ${{ matrix.binaries }}
|
||||
runs-on: ${{ matrix.config.os }}
|
||||
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:
|
||||
working-directory: src
|
||||
shell: ${{ matrix.config.shell }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install fixed GCC on Linux
|
||||
if: runner.os == 'Linux'
|
||||
uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3
|
||||
with:
|
||||
version: 11
|
||||
|
||||
- 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: Download SDE package
|
||||
if: runner.os == 'Linux' || runner.os == 'Windows'
|
||||
uses: petarpetrovt/setup-sde@f0fa5971dc275704531e94264dd23250c442aa41 # @v2.4
|
||||
with:
|
||||
environmentVariableName: SDE_DIR
|
||||
sdeVersion: 9.33.0
|
||||
|
||||
- name: Download the used network from the fishtest framework
|
||||
run: make net
|
||||
|
||||
- name: Check compiler
|
||||
run: $COMPCXX -v
|
||||
|
||||
- name: Test help target
|
||||
run: make help
|
||||
|
||||
- name: Check git
|
||||
run: git --version
|
||||
|
||||
- name: Check compiler
|
||||
run: $COMPCXX -v
|
||||
|
||||
- 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 RUN_PREFIX="$SDE"
|
||||
make strip ARCH=$BINARY COMP=$COMP
|
||||
RUN_PREFIX="$SDE" ../tests/signature.sh $benchref
|
||||
mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT
|
||||
|
||||
- name: Remove non src files
|
||||
run: git clean -fx
|
||||
|
||||
- name: Upload artifact for (pre)-release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!.output
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout SF repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
path: Stockfish
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
run: make -j build debug=yes
|
||||
|
||||
- name: Checkout fastchess repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: Disservin/fastchess
|
||||
path: fastchess
|
||||
|
||||
@@ -11,13 +11,13 @@ jobs:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout Stockfish
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
path: Stockfish
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout include-what-you-use
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: include-what-you-use/include-what-you-use
|
||||
ref: f25caa280dc3277c4086ec345ad279a2463fea0f
|
||||
|
||||
@@ -7,9 +7,12 @@ jobs:
|
||||
Matetrack:
|
||||
name: Matetrack
|
||||
runs-on: ubuntu-22.04
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout SF repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
path: Stockfish
|
||||
@@ -20,11 +23,11 @@ jobs:
|
||||
run: make -j profile-build
|
||||
|
||||
- name: Checkout matetrack repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: vondele/matetrack
|
||||
path: matetrack
|
||||
ref: 2d96fa3373f90edb032b7ea7468473fb9e6f0343
|
||||
ref: 1e7f6c6fff8e2f23923880a94fae596281346395
|
||||
persist-credentials: false
|
||||
|
||||
- name: matetrack install deps
|
||||
@@ -33,7 +36,7 @@ jobs:
|
||||
|
||||
- name: cache syzygy
|
||||
id: cache-syzygy
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: |
|
||||
matetrack/3-4-5-wdl/
|
||||
@@ -47,17 +50,54 @@ 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/
|
||||
|
||||
|
||||
# matecheck/python chess is not good at handling crashing engines. Do a basic check first.
|
||||
- name: Test engine functionality
|
||||
working-directory: Stockfish/src
|
||||
run: |
|
||||
python3 ../tests/instrumented.py --none ./stockfish
|
||||
|
||||
- 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 matecheckout1.out
|
||||
! grep "issues were detected" matecheckout1.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 matecheck1.out
|
||||
! grep "issues were detected" matecheck1.out > /dev/null
|
||||
|
||||
- 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
|
||||
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 matecheck4.out
|
||||
! grep "issues were detected" matecheck4.out > /dev/null
|
||||
|
||||
- name: Run matetrack th4 gameplay
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --time 3 --timeinc 0.01 --threads 4 | tee matecheck4g.out
|
||||
! grep "issues were detected" matecheck4g.out > /dev/null
|
||||
|
||||
- name: Run matetrack th4 go-mate
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile matetrack.epd matedtrack.epd --bmMax 2 --mate 0 --threads 4 | tee matecheck4gm.out
|
||||
! grep "issues were detected" matecheck4gm.out > /dev/null
|
||||
total=$(grep "Total FENs:" matecheck4gm.out | awk '{print $3}')
|
||||
bmates=$(grep "Best mates:" matecheck4gm.out | awk '{print $3}')
|
||||
if [ $bmates -ne $total ]; then
|
||||
echo "At least one go-mate search did not yield expected mate, see matecheck4gm.out" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run matetrack th1 multiPV
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile matetrack.epd matedtrack.epd --bmMax 10 --nodes 5000 --multiPV 20 --checkMultiPVs | tee matecheck1mpv.out
|
||||
! grep "issues were detected" matecheck1mpv.out > /dev/null
|
||||
|
||||
- name: Run matetrack th4 multiPV
|
||||
working-directory: matetrack
|
||||
run: |
|
||||
python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile matetrack.epd matedtrack.epd --bmMax 10 --nodes 5000 --threads 4 --multiPV 20 --checkMultiPVs | tee matecheck4mpv.out
|
||||
! grep "issues were detected" matecheck4mpv.out > /dev/null
|
||||
|
||||
- name: Run matetrack th1 with --syzygy50MoveRule false
|
||||
working-directory: matetrack
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
working-directory: src
|
||||
shell: ${{ matrix.config.shell }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
|
||||
+106
-41
@@ -11,16 +11,24 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- tools
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
Prerelease:
|
||||
name: Prerelease
|
||||
if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'))
|
||||
runs-on: ubuntu-latest
|
||||
needs: [Matrix, ARMCompilation, UniversalCompilation]
|
||||
permissions:
|
||||
contents: write # For deleting/creating a prerelease
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
# returns null if no pre-release exists
|
||||
- name: Get Commit SHA of Latest Pre-release
|
||||
@@ -53,66 +61,114 @@ jobs:
|
||||
id: commit_date
|
||||
run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Official Release?
|
||||
id: official_release
|
||||
# Check for "Official release version of Stockfish" in the commit message
|
||||
run: |
|
||||
if git log -1 --pretty=%B | grep -q "Official release version of Stockfish"; then
|
||||
echo "OFFICIAL_RELEASE=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "OFFICIAL_RELEASE=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
# Get recent commits for release body
|
||||
- name: Get commits in this push
|
||||
id: recent_commits
|
||||
run: |
|
||||
COMMITS=$(git log \
|
||||
--format="- [\`%h\`](https://github.com/${{ github.repository }}/commit/%H) *%s*" \
|
||||
${{ github.event.before }}..HEAD)
|
||||
echo "COMMITS<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$COMMITS" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
# Create a new pre-release, the other upload_binaries.yml will upload the binaries
|
||||
# to this pre-release.
|
||||
- name: Create Prerelease
|
||||
if: github.ref_name == 'master' && env.CHANGES == '0'
|
||||
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
|
||||
if: github.ref_name == 'master' && env.CHANGES == '0' && env.OFFICIAL_RELEASE == 'false'
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
|
||||
tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
|
||||
prerelease: true
|
||||
|
||||
body: |
|
||||
Precompiled binaries of the latest Stockfish development version, built automatically on every commit.
|
||||
|
||||
> [!NOTE]
|
||||
> For stable, thoroughly tested builds use the [official releases](https://github.com/official-stockfish/Stockfish/releases).
|
||||
> Pre-releases may contain bugs.
|
||||
|
||||
*Some platforms ship a **universal binary** that automatically selects the best
|
||||
instruction set for your CPU - no manual selection needed.*
|
||||
|
||||
---
|
||||
|
||||
## Commits
|
||||
|
||||
${{ steps.recent_commits.outputs.COMMITS }}
|
||||
|
||||
[View all commits →](https://github.com/${{ github.repository }}/commits/master)
|
||||
Matrix:
|
||||
name: Prepare matrices
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
arm_matrix: ${{ steps.set-arm-matrix.outputs.arm_matrix }}
|
||||
universal_matrix: ${{ steps.set-universal-matrix.outputs.universal_matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- id: set-matrix
|
||||
run: |
|
||||
TASKS=$(echo $(cat .github/ci/matrix.json) )
|
||||
echo "MATRIX=$TASKS" >> $GITHUB_OUTPUT
|
||||
- id: set-arm-matrix
|
||||
run: |
|
||||
TASKS_ARM=$(echo $(cat .github/ci/arm_matrix.json) )
|
||||
echo "ARM_MATRIX=$TASKS_ARM" >> $GITHUB_OUTPUT
|
||||
Compilation:
|
||||
needs: [Matrix]
|
||||
uses: ./.github/workflows/compilation.yml
|
||||
with:
|
||||
matrix: ${{ needs.Matrix.outputs.matrix }}
|
||||
ARM_MATRIX=$(jq -c '.' .github/ci/arm_matrix.json)
|
||||
echo "ARM_MATRIX=$ARM_MATRIX" >> $GITHUB_OUTPUT
|
||||
- id: set-universal-matrix
|
||||
run: |
|
||||
UNIVERSAL_MATRIX=$(jq -c '.' .github/ci/universal_matrix.json)
|
||||
echo "UNIVERSAL_MATRIX=$UNIVERSAL_MATRIX" >> $GITHUB_OUTPUT
|
||||
# Testing Jobs
|
||||
IWYU:
|
||||
name: IWYU
|
||||
uses: ./.github/workflows/iwyu.yml
|
||||
Sanitizers:
|
||||
name: Sanitizers
|
||||
if: ${{ always() }}
|
||||
uses: ./.github/workflows/sanitizers.yml
|
||||
Tests:
|
||||
name: Tests
|
||||
if: ${{ always() }}
|
||||
uses: ./.github/workflows/tests.yml
|
||||
Matetrack:
|
||||
name: Matetrack
|
||||
if: ${{ always() }}
|
||||
uses: ./.github/workflows/matetrack.yml
|
||||
Games:
|
||||
name: Games
|
||||
if: ${{ always() }}
|
||||
uses: ./.github/workflows/games.yml
|
||||
CompilerCheck:
|
||||
name: Compiler check
|
||||
if: ${{ always() }}
|
||||
uses: ./.github/workflows/avx2_compilers.yml
|
||||
WASMCompilation:
|
||||
name: WASM compilation
|
||||
if: ${{ always() }}
|
||||
uses: ./.github/workflows/wasm_compilation.yml
|
||||
# Release Jobs
|
||||
ARMCompilation:
|
||||
needs: [Matrix]
|
||||
name: Android builds
|
||||
needs: [Matrix, Sanitizers, Tests, Matetrack, Games, CompilerCheck]
|
||||
uses: ./.github/workflows/arm_compilation.yml
|
||||
with:
|
||||
matrix: ${{ needs.Matrix.outputs.arm_matrix }}
|
||||
IWYU:
|
||||
uses: ./.github/workflows/iwyu.yml
|
||||
Sanitizers:
|
||||
uses: ./.github/workflows/sanitizers.yml
|
||||
Tests:
|
||||
uses: ./.github/workflows/tests.yml
|
||||
Matetrack:
|
||||
uses: ./.github/workflows/matetrack.yml
|
||||
Games:
|
||||
uses: ./.github/workflows/games.yml
|
||||
Binaries:
|
||||
if: github.repository == 'official-stockfish/Stockfish'
|
||||
needs: [Matrix, Prerelease, Compilation]
|
||||
uses: ./.github/workflows/upload_binaries.yml
|
||||
with:
|
||||
matrix: ${{ needs.Matrix.outputs.matrix }}
|
||||
permissions:
|
||||
contents: write # For deleting/creating a (pre)release
|
||||
secrets:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
UniversalCompilation:
|
||||
name: "Universal builds"
|
||||
needs: [Matrix, Sanitizers, Tests, Matetrack, Games, CompilerCheck]
|
||||
uses: ./.github/workflows/universal_compilation.yml
|
||||
ARM_Binaries:
|
||||
if: github.repository == 'official-stockfish/Stockfish'
|
||||
needs: [Matrix, Prerelease, ARMCompilation]
|
||||
name: Android uploads
|
||||
needs: [Prerelease, Matrix]
|
||||
uses: ./.github/workflows/upload_binaries.yml
|
||||
with:
|
||||
matrix: ${{ needs.Matrix.outputs.arm_matrix }}
|
||||
@@ -120,5 +176,14 @@ jobs:
|
||||
contents: write # For deleting/creating a (pre)release
|
||||
secrets:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
CompilerCheck:
|
||||
uses: ./.github/workflows/avx2_compilers.yml
|
||||
Universal_Binaries:
|
||||
if: github.repository == 'official-stockfish/Stockfish'
|
||||
name: Universal binaries
|
||||
needs: [Prerelease, Matrix]
|
||||
uses: ./.github/workflows/upload_binaries.yml
|
||||
with:
|
||||
matrix: ${{ needs.Matrix.outputs.universal_matrix }}
|
||||
permissions:
|
||||
contents: write # For deleting/creating a (pre)release
|
||||
secrets:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+49
-23
@@ -39,15 +39,6 @@ jobs:
|
||||
comp: ndk
|
||||
run_armv7_tests: true
|
||||
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++
|
||||
@@ -56,6 +47,14 @@ jobs:
|
||||
base_image: "ppc64le/alpine:latest"
|
||||
platform: linux/ppc64le
|
||||
shell: bash
|
||||
- name: Linux GCC loongarch64
|
||||
os: ubuntu-22.04
|
||||
compiler: g++
|
||||
comp: gcc
|
||||
run_loongarch64_tests: true
|
||||
base_image: "loongarch64/alpine:3.21"
|
||||
platform: linux/loong64
|
||||
shell: bash
|
||||
- name: macOS 15 Apple Clang
|
||||
os: macos-15-intel
|
||||
compiler: clang++
|
||||
@@ -112,7 +111,7 @@ jobs:
|
||||
working-directory: src
|
||||
shell: ${{ matrix.config.shell }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
@@ -139,11 +138,11 @@ jobs:
|
||||
|
||||
- name: Set up QEMU
|
||||
if: matrix.config.base_image
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: matrix.config.base_image
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build Docker container
|
||||
if: matrix.config.base_image
|
||||
@@ -157,11 +156,17 @@ jobs:
|
||||
|
||||
- name: Download required macOS packages
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install coreutils gcc@11
|
||||
run: brew install coreutils
|
||||
|
||||
- name: Install GCC on macOS
|
||||
if: runner.os == 'macOS' && matrix.config.comp == 'gcc'
|
||||
run: |
|
||||
brew install gcc@11 || true
|
||||
g++-11 --version
|
||||
|
||||
- name: Setup msys and install required packages
|
||||
if: runner.os == 'Windows'
|
||||
uses: msys2/setup-msys2@v2
|
||||
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1
|
||||
with:
|
||||
msystem: ${{ matrix.config.msys_sys }}
|
||||
install: mingw-w64-${{ matrix.config.msys_env }} make git expect
|
||||
@@ -306,6 +311,13 @@ jobs:
|
||||
make -j4 ARCH=apple-silicon build
|
||||
../tests/signature.sh $benchref
|
||||
|
||||
- name: Test apple-silicon build with system g++ (macOS g++ == clang++)
|
||||
if: matrix.config.run_m1_tests
|
||||
run: |
|
||||
make clean
|
||||
env -u COMPCXX make -j4 ARCH=apple-silicon build COMP=gcc
|
||||
../tests/signature.sh $benchref
|
||||
|
||||
# armv8 tests
|
||||
|
||||
- name: Test armv8 build
|
||||
@@ -350,15 +362,6 @@ jobs:
|
||||
make -j4 ARCH=armv7-neon build
|
||||
../tests/signature.sh $benchref
|
||||
|
||||
# riscv64 tests
|
||||
|
||||
- name: Test riscv64 build
|
||||
if: matrix.config.run_riscv64_tests
|
||||
run: |
|
||||
echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=riscv64 build" > script.sh
|
||||
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
|
||||
../tests/signature.sh $benchref
|
||||
|
||||
# ppc64 tests
|
||||
|
||||
- name: Test ppc64 build
|
||||
@@ -368,6 +371,29 @@ jobs:
|
||||
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
|
||||
../tests/signature.sh $benchref
|
||||
|
||||
# loongarch64 tests
|
||||
|
||||
- name: Test loongarch64 build
|
||||
if: matrix.config.run_loongarch64_tests
|
||||
run: |
|
||||
echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=loongarch64 build" > script.sh
|
||||
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
|
||||
../tests/signature.sh $benchref
|
||||
|
||||
- name: Test loongarch64-lsx build
|
||||
if: matrix.config.run_loongarch64_tests
|
||||
run: |
|
||||
echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=loongarch64-lsx build" > script.sh
|
||||
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
|
||||
../tests/signature.sh $benchref
|
||||
|
||||
- name: Test loongarch64-lasx build
|
||||
if: matrix.config.run_loongarch64_tests
|
||||
run: |
|
||||
echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=loongarch64-lasx build" > script.sh
|
||||
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
|
||||
../tests/signature.sh $benchref
|
||||
|
||||
# Other tests
|
||||
|
||||
- name: Check perft and search reproducibility
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
name: Universal Compilation
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
UniversalLinux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-22.04
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install GCC 15
|
||||
uses: egor-tensin/setup-gcc@a2861a8b8538f49cf2850980acccf6b05a1b2ae4 # v2.0
|
||||
with:
|
||||
version: 15
|
||||
|
||||
- name: Download SDE package
|
||||
uses: petarpetrovt/setup-sde@31aa4a8e85e109bef00f1d838613fcc6ec421271 # v5.0
|
||||
with:
|
||||
environmentVariableName: SDE_DIR
|
||||
sdeVersion: 9.58.0
|
||||
|
||||
- name: Build universal binary
|
||||
run: make -j4 -C src ARCH=x86-64-universal profile-build RUN_PREFIX="$SDE_DIR/sde -future --"
|
||||
|
||||
- name: Strip binary
|
||||
run: strip src/stockfish
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/stockfish stockfish-linux-x86-64-universal
|
||||
|
||||
- name: Extract the bench number from the commit history
|
||||
run: |
|
||||
for hash in $(git rev-list -100 HEAD); do
|
||||
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
|
||||
done
|
||||
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
|
||||
|
||||
- name: Check arch selection under SDE
|
||||
run: ./scripts/check_universal.sh ./stockfish-linux-x86-64-universal "$SDE_DIR/sde" "$benchref"
|
||||
|
||||
- name: Remove non-src files
|
||||
run: git clean -fxd -- src
|
||||
|
||||
- name: Upload artifact for (pre)-release
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: linux x86-64-universal
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!.output
|
||||
|
||||
UniversalWindows:
|
||||
name: Windows
|
||||
runs-on: windows-2022
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup msys and install required packages
|
||||
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1
|
||||
with:
|
||||
msystem: mingw64
|
||||
install: mingw-w64-x86_64-gcc make git zip
|
||||
|
||||
- name: Download SDE package
|
||||
uses: petarpetrovt/setup-sde@31aa4a8e85e109bef00f1d838613fcc6ec421271 # v5.0
|
||||
with:
|
||||
environmentVariableName: SDE_DIR
|
||||
sdeVersion: 9.58.0
|
||||
|
||||
- name: Build universal binary
|
||||
run: |
|
||||
SDE_PATH=$(cygpath "$SDE_DIR")
|
||||
make -j4 -C src ARCH=x86-64-universal profile-build RUN_PREFIX="$SDE_PATH/sde.exe -future --"
|
||||
|
||||
- name: Strip binary
|
||||
run: strip src/stockfish.exe
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/stockfish.exe stockfish-windows-x86-64-universal.exe
|
||||
|
||||
- name: Extract the bench number from the commit history
|
||||
run: |
|
||||
for hash in $(git rev-list -100 HEAD); do
|
||||
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
|
||||
done
|
||||
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
|
||||
|
||||
- name: Check arch selection under SDE
|
||||
run: ./scripts/check_universal.sh ./stockfish-windows-x86-64-universal.exe "$SDE_DIR/sde" "$benchref"
|
||||
|
||||
- name: Remove non-src files
|
||||
run: git clean -fxd -- src
|
||||
|
||||
- name: Upload artifact for (pre)-release
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: windows x86-64-universal
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!.output
|
||||
|
||||
UniversalLinuxARM64:
|
||||
name: Linux arm64
|
||||
runs-on: ubuntu-22.04-arm
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install GCC 15
|
||||
uses: egor-tensin/setup-gcc@a2861a8b8538f49cf2850980acccf6b05a1b2ae4 # v2.0
|
||||
with:
|
||||
version: 15
|
||||
|
||||
- name: Install qemu-user
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y qemu-user
|
||||
|
||||
- name: Build universal binary
|
||||
run: make -j4 -C src ARCH=arm64-universal profile-build
|
||||
|
||||
- name: Strip binary
|
||||
run: strip src/stockfish
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/stockfish stockfish-linux-arm64-universal
|
||||
|
||||
- name: Extract the bench number from the commit history
|
||||
run: |
|
||||
for hash in $(git rev-list -100 HEAD); do
|
||||
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
|
||||
done
|
||||
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
|
||||
|
||||
- name: Check arch selection under qemu
|
||||
run: ./scripts/check_universal_arm.sh ./stockfish-linux-arm64-universal "$benchref"
|
||||
|
||||
- name: Remove non-src files
|
||||
run: git clean -fxd -- src
|
||||
|
||||
- name: Upload artifact for (pre)-release
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: linux arm64-universal
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!.output
|
||||
|
||||
UniversalWindowsARM64:
|
||||
name: Windows arm64
|
||||
runs-on: windows-11-arm
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup msys and install required packages
|
||||
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1
|
||||
with:
|
||||
msystem: clangarm64
|
||||
install: mingw-w64-clang-aarch64-clang make git zip
|
||||
|
||||
- name: Build universal binary
|
||||
run: make -j4 -C src ARCH=arm64-universal profile-build COMP=clang COMPCXX=clang++
|
||||
|
||||
- name: Strip binary
|
||||
run: strip src/stockfish.exe
|
||||
|
||||
- name: Extract the bench number from the commit history
|
||||
run: |
|
||||
for hash in $(git rev-list -100 HEAD); do
|
||||
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
|
||||
done
|
||||
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
|
||||
|
||||
- name: Verify bench and compiler
|
||||
working-directory: src
|
||||
run: |
|
||||
../tests/signature.sh $benchref
|
||||
./stockfish compiler 2>&1 | grep -q DOTPROD || { echo "compiler output missing DOTPROD" >&2; exit 1; }
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/stockfish.exe stockfish-windows-arm64-universal.exe
|
||||
|
||||
- name: Remove non-src files
|
||||
run: git clean -fxd -- src
|
||||
|
||||
- name: Upload artifact for (pre)-release
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: windows arm64-universal
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!.output
|
||||
|
||||
UniversalMacOS:
|
||||
name: macOS
|
||||
runs-on: macos-15
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
# macos-15 is Apple silicon; Rosetta 2 is needed to exercise the x86-64 slice
|
||||
- name: Install Rosetta 2
|
||||
run: softwareupdate --install-rosetta --agree-to-license
|
||||
|
||||
- name: Build universal binary
|
||||
run: make -j4 -C src macos-lipo COMP=clang
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/stockfish stockfish-macos-universal
|
||||
|
||||
- name: Verify binary runs natively and under Rosetta
|
||||
run: ./scripts/check_universal_macos.sh ./stockfish-macos-universal
|
||||
|
||||
- name: Remove non-src files
|
||||
run: git clean -fxd -- src
|
||||
|
||||
- name: Upload artifact for (pre)-release
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: macos universal
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!.output
|
||||
|
||||
UniversalLinuxRISCV64:
|
||||
name: Linux riscv64
|
||||
runs-on: ubuntu-22.04
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
env:
|
||||
QEMU_DEB: https://launchpad.net/ubuntu/+archive/primary/+files/qemu-user_9.2.1+ds-1ubuntu5_amd64.deb
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install clang and the riscv cross runtime
|
||||
run: |
|
||||
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc >/dev/null
|
||||
echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-20 main" | sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y clang-20 lld-20 llvm-20 g++-riscv64-linux-gnu
|
||||
echo "/usr/lib/llvm-20/bin" >> "$GITHUB_PATH"
|
||||
- name: Install qemu
|
||||
run: |
|
||||
curl -sL -o /tmp/qemu.deb "$QEMU_DEB"
|
||||
dpkg -x /tmp/qemu.deb /tmp/qemu
|
||||
/tmp/qemu/usr/bin/qemu-riscv64 --version
|
||||
echo "/tmp/qemu/usr/bin" >> "$GITHUB_PATH"
|
||||
- name: Build universal binary
|
||||
run: |
|
||||
make -j4 -C src ARCH=riscv64-universal build \
|
||||
COMP=clang COMPCXX=clang++ \
|
||||
EXTRACXXFLAGS="--target=riscv64-linux-gnu" \
|
||||
EXTRALDFLAGS="--target=riscv64-linux-gnu -fuse-ld=lld" \
|
||||
OBJCOPY=llvm-objcopy
|
||||
- name: Strip binary
|
||||
run: llvm-strip src/stockfish
|
||||
|
||||
- name: Rename binary
|
||||
run: mv src/stockfish stockfish-linux-riscv64-universal
|
||||
|
||||
- name: Extract the bench number from the commit history
|
||||
run: |
|
||||
for hash in $(git rev-list -100 HEAD); do
|
||||
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
|
||||
done
|
||||
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
|
||||
- name: Check arch selection under qemu
|
||||
run: |
|
||||
QEMU=qemu-riscv64 QEMU_LD_PREFIX=/usr/riscv64-linux-gnu \
|
||||
./scripts/check_universal_riscv.sh ./stockfish-linux-riscv64-universal "$benchref"
|
||||
- name: Remove non-src files
|
||||
run: git clean -fxd -- src
|
||||
|
||||
- name: Upload artifact for (pre)-release
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: linux riscv64-universal
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!.output
|
||||
@@ -24,12 +24,12 @@ jobs:
|
||||
run:
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download artifact from compilation
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
|
||||
path: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
|
||||
@@ -59,19 +59,19 @@ jobs:
|
||||
cp CONTRIBUTING.md ../stockfish/
|
||||
|
||||
- name: Create tar
|
||||
if: ${{ !startsWith(matrix.config.os, 'windows') }}
|
||||
if: ${{ startsWith(matrix.config.archive_ext, 'tar') }}
|
||||
run: |
|
||||
chmod +x ./stockfish/stockfish-$NAME-$BINARY$EXT
|
||||
tar -cvf stockfish-$NAME-$BINARY.tar stockfish
|
||||
tar -cavf stockfish-$NAME-$BINARY."${{ matrix.config.archive_ext }}" stockfish
|
||||
|
||||
- name: Create zip
|
||||
if: ${{ startsWith(matrix.config.os, 'windows') }}
|
||||
if: ${{ matrix.config.archive_ext == 'zip' }}
|
||||
run: |
|
||||
zip -r stockfish-$NAME-$BINARY.zip stockfish
|
||||
|
||||
- name: Release
|
||||
if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'
|
||||
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}
|
||||
token: ${{ secrets.token }}
|
||||
@@ -92,10 +92,20 @@ jobs:
|
||||
CHANGES=$(git rev-list HEAD..origin/master --count)
|
||||
echo "CHANGES=$CHANGES" >> $GITHUB_ENV
|
||||
|
||||
- name: Official Release?
|
||||
id: official_release
|
||||
# Check for "Official release version of Stockfish" in the commit message
|
||||
run: |
|
||||
if git log -1 --pretty=%B | grep -q "Official release version of Stockfish"; then
|
||||
echo "OFFICIAL_RELEASE=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "OFFICIAL_RELEASE=false" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Prerelease
|
||||
if: github.ref_name == 'master' && env.CHANGES == '0'
|
||||
if: github.ref_name == 'master' && env.CHANGES == '0' && env.OFFICIAL_RELEASE == 'false'
|
||||
continue-on-error: true
|
||||
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
|
||||
tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: WASM Compilation
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
name: wasm (${{ matrix.arch }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
arch: [wasm32, wasm32-relaxed-simd]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: src
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install emsdk
|
||||
working-directory: .
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/emscripten-core/emsdk.git
|
||||
cd emsdk
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
|
||||
- name: Extract the bench number from the commit history
|
||||
run: |
|
||||
for hash in $(git rev-list -100 HEAD); do
|
||||
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
|
||||
done
|
||||
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
|
||||
|
||||
- name: Download the used network from the fishtest framework
|
||||
run: make net
|
||||
|
||||
- name: Build ${{ matrix.arch }} build
|
||||
run: |
|
||||
source $GITHUB_WORKSPACE/emsdk/emsdk_env.sh
|
||||
em++ --version
|
||||
net=$(grep -oE 'nn-[0-9a-f]+\.nnue' evaluate.h | head -1)
|
||||
make clean
|
||||
COMPCXX=em++ make -j build ARCH=${{ matrix.arch }} EXTRALDFLAGS="--preload-file $net"
|
||||
|
||||
- name: Verify bench under node
|
||||
run: EXE=./stockfish.js RUN_PREFIX=node ../tests/signature.sh $benchref
|
||||
+6
-1
@@ -2,6 +2,11 @@
|
||||
**/*.o
|
||||
**/*.s
|
||||
src/.depend
|
||||
src/temp_builds
|
||||
src/universal/network_dump.inc
|
||||
.build_sha.txt
|
||||
.build_date.txt
|
||||
.build_diffindex.txt
|
||||
|
||||
# Built binary
|
||||
src/stockfish*
|
||||
@@ -14,4 +19,4 @@ src/-lstdc++.res
|
||||
tsan.supp
|
||||
__pycache__/
|
||||
tests/syzygy
|
||||
tests/bench_tmp.epd
|
||||
tests/bench_tmp.epd
|
||||
|
||||
@@ -12,9 +12,11 @@ Hisayori Noda (nodchip)
|
||||
# All other authors of Stockfish code (in alphabetical order)
|
||||
87flowers
|
||||
Aditya (absimaldata)
|
||||
Adrian Ladoni (AdrianGHUB15)
|
||||
Adrian Petrescu (apetresc)
|
||||
Ahmed Kerimov (wcdbmv)
|
||||
Ajith Chandy Jose (ajithcj)
|
||||
AK-Khan02
|
||||
Alain Savard (Rocky640)
|
||||
Alayan Feh (Alayan-stk-2)
|
||||
Alexander Kure
|
||||
@@ -58,13 +60,15 @@ Ciekce
|
||||
clefrks
|
||||
Clemens L. (rn5f107s2)
|
||||
Cody Ho (aesrentai)
|
||||
Con Kirby (ConKirby)
|
||||
CSTENTOR
|
||||
Dale Weiler (graphitemaster)
|
||||
Dalton Hanaway (dhanaway)
|
||||
Dan Schmidt (dfannius)
|
||||
Daniel Axtens (daxtens)
|
||||
Daniel Dugovic (ddugovic)
|
||||
Daniel Monroe (daniel-monroe)
|
||||
Daniel Samek (DanSamek)
|
||||
Dan Schmidt (dfannius)
|
||||
Dariusz Orzechowski (dorzechowski)
|
||||
David (dav1312)
|
||||
David Zar
|
||||
@@ -89,9 +93,9 @@ Fanael Linithien (Fanael)
|
||||
fanon
|
||||
Fauzi Akram Dabat (fauzi2)
|
||||
Felix Wittmann
|
||||
gamander
|
||||
Gabriele Lombardo (gabe)
|
||||
Gahtan Nahdi
|
||||
gamander
|
||||
Gary Heckman (gheckman)
|
||||
George Sobala (gsobala)
|
||||
gguliash
|
||||
@@ -102,8 +106,8 @@ Gontran Lemaire (gonlem)
|
||||
Goodkov Vasiliy Aleksandrovich (goodkov)
|
||||
Gregor Cramer
|
||||
GuardianRM
|
||||
Guy Vreuls (gvreuls)
|
||||
Günther Demetz (pb00067, pb00068)
|
||||
Guy Vreuls (gvreuls)
|
||||
Henri Wiechers
|
||||
Hiraoka Takuya (HiraokaTakuya)
|
||||
homoSapiensSapiens
|
||||
@@ -116,8 +120,8 @@ Jan Ondruš (hxim)
|
||||
Jared Kish (Kurtbusch, kurt22i)
|
||||
Jarrod Torriero (DU-jdto)
|
||||
Jasper Shovelton (Beanie496)
|
||||
Jean-Francois Romang (jromang)
|
||||
Jean Gauthier (OuaisBla)
|
||||
Jean-Francois Romang (jromang)
|
||||
Jekaa
|
||||
Jerry Donald Watson (jerrydonaldwatson)
|
||||
jjoshua2
|
||||
@@ -125,10 +129,10 @@ Jonathan Buladas Dumale (SFisGOD)
|
||||
Jonathan Calovski (Mysseno)
|
||||
Jonathan McDermid (jonathanmcdermid)
|
||||
Joost VandeVondele (vondele)
|
||||
Jörg Oster (joergoster)
|
||||
Joseph Ellis (jhellis3)
|
||||
Joseph R. Prostko
|
||||
Jost Triller (tsoj)
|
||||
Jörg Oster (joergoster)
|
||||
Julian Willemer (NightlyKing)
|
||||
jundery
|
||||
Justin Blanchard (UncombedCoconut)
|
||||
@@ -177,15 +181,18 @@ Michel Van den Bergh (vdbergh)
|
||||
Miguel Lahoz (miguel-l)
|
||||
Mikael Bäckman (mbootsector)
|
||||
Mike Babigian (Farseer)
|
||||
Miloslav Macůrek (maelic13)
|
||||
Mira
|
||||
Miroslav Fontán (Hexik)
|
||||
Moez Jellouli (MJZ1977)
|
||||
Mohammed Li (tthsqe12)
|
||||
Mukhammedali Beriktassuly (Berektassuly)
|
||||
Muzhen J (XInTheDark)
|
||||
Mafanwei
|
||||
Nathan Rugg (nmrugg)
|
||||
Nguyen Pham (nguyenpham)
|
||||
Nicklas Persson (NicklasPersson)
|
||||
Nick Pelling (nickpelling)
|
||||
Nicklas Persson (NicklasPersson)
|
||||
Nicolas Duhamel (nikloskoda)
|
||||
Niklas Fiekas (niklasf)
|
||||
Nikolay Kostov (NikolayIT)
|
||||
@@ -193,10 +200,11 @@ Norman Schmidt (FireFather)
|
||||
notruck
|
||||
Nour Berakdar (Nonlinear)
|
||||
Ofek Shochat (OfekShochat, ghostway)
|
||||
Ondrej Mosnáček (WOnder93)
|
||||
Ondřej Mišina (AndrovT)
|
||||
Oskar Werkelin Ahlin
|
||||
Ömer Faruk Tutkun (OmerFarukTutkun)
|
||||
Ondřej Mišina (AndrovT)
|
||||
Ondrej Mosnáček (WOnder93)
|
||||
Onur Zungur (zungur)
|
||||
Oskar Werkelin Ahlin
|
||||
Pablo Vazquez
|
||||
Panthee
|
||||
Pascal Romaret
|
||||
@@ -205,6 +213,7 @@ Patrick Jansen (mibere)
|
||||
Patrick Leonhardt (Yoshie2000)
|
||||
Peter Schneider (pschneider1968)
|
||||
Peter Zsifkovits (CoffeeOne)
|
||||
Pieter te Brake (pieterteb)
|
||||
PikaCat
|
||||
Praveen Kumar Tummala (praveentml)
|
||||
Prokop Randáček (ProkopRandacek)
|
||||
@@ -217,11 +226,13 @@ Richard Lloyd (Richard-Lloyd)
|
||||
Robert Nürnberg (robertnurnberg)
|
||||
Rodrigo Exterckötter Tjäder
|
||||
Rodrigo Roim (roim)
|
||||
Ronald de Man (syzygy1, syzygy)
|
||||
Ron Britvich (Britvich)
|
||||
Ronald de Man (syzygy1, syzygy)
|
||||
rqs
|
||||
Rui Coelho (ruicoelhopedro)
|
||||
rustam-cpp
|
||||
Ryan Hirsch
|
||||
Ryan Lefkowitz (rlefko)
|
||||
Ryan Schmitt
|
||||
Ryan Takker
|
||||
Sami Kiminki (skiminki)
|
||||
@@ -251,10 +262,12 @@ theo77186
|
||||
TierynnB
|
||||
Timothy Herchen (anematode)
|
||||
Ting-Hsuan Huang (fffelix-huang)
|
||||
tobi437a
|
||||
Tobias Steinmann
|
||||
Tomasz Sobczyk (Sopel97)
|
||||
Tom Truscott
|
||||
Tom Vijlbrief (tomtor)
|
||||
Tomasz Sobczyk (Sopel97)
|
||||
Tony Wang (TonyCongqianWang)
|
||||
Torsten Franz (torfranz, tfranzer)
|
||||
Torsten Hellwig (Torom)
|
||||
Tracey Emery (basepr1me)
|
||||
@@ -264,9 +277,12 @@ Uri Blass (uriblass)
|
||||
Vince Negri (cuddlestmonkey)
|
||||
Viren
|
||||
Wencey Wang
|
||||
Will Miles (willm)
|
||||
windfishballad
|
||||
xefoci7612
|
||||
Xiang Wang (KatyushaScarlet)
|
||||
Yen-Chao Shen (lemteay)
|
||||
ZlomenyMesic
|
||||
zz4032
|
||||
|
||||
# Additionally, we acknowledge the authors and maintainers of fishtest,
|
||||
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Verify that the universal binary selects the correct per-arch build under
|
||||
# Intel SDE emulation for a range of target CPUs
|
||||
#
|
||||
# Usage: check_universal.sh STOCKFISH_EXE SDE_EXE EXPECTED_BENCH
|
||||
|
||||
set -eu
|
||||
|
||||
if [ $# -ne 3 ]; then
|
||||
echo "Usage: $0 STOCKFISH_EXE SDE_EXE EXPECTED_BENCH" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
STOCKFISH_EXE=$1
|
||||
SDE_EXE=$2
|
||||
EXPECTED_BENCH=$3
|
||||
|
||||
if [ "$(uname)" = 'Linux' ]; then
|
||||
# Windows 11 doesn't support these old arches
|
||||
PAIRS="
|
||||
p4p:x86-64
|
||||
nhm:x86-64-sse41-popcnt
|
||||
"
|
||||
else
|
||||
PAIRS=''
|
||||
fi
|
||||
|
||||
PAIRS="$PAIRS
|
||||
snb:x86-64-sse41-popcnt
|
||||
ivb:x86-64-sse41-popcnt
|
||||
hsw:x86-64-bmi2
|
||||
skl:x86-64-bmi2
|
||||
skx:x86-64-avx512
|
||||
clx:x86-64-vnni512
|
||||
icl:x86-64-avx512icl
|
||||
adl:x86-64-avxvnni
|
||||
"
|
||||
|
||||
BINARY_SIZE=$(wc -c < "$STOCKFISH_EXE")
|
||||
MAX_SIZE=$((150 * 1024 * 1024))
|
||||
if [ "$BINARY_SIZE" -gt "$MAX_SIZE" ]; then
|
||||
printf 'check_universal.sh: binary size %d bytes exceeds 150 MB limit\n' "$BINARY_SIZE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORK=$(mktemp -d)
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
|
||||
i=0
|
||||
for pair in $PAIRS; do
|
||||
i=$((i + 1))
|
||||
cpu=${pair%%:*}
|
||||
expected_compiler=${pair##*:}
|
||||
(
|
||||
compiler_out=$("$SDE_EXE" "-$cpu" -- "$STOCKFISH_EXE" compiler 2>&1 || true)
|
||||
bench_out=$("$SDE_EXE" "-$cpu" -- "$STOCKFISH_EXE" bench 2>&1 >/dev/null || true)
|
||||
actual_compiler=$(printf '%s\n' "$compiler_out" | awk -F: '/Compilation architecture/ {
|
||||
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
|
||||
}')
|
||||
actual_bench=$(printf '%s\n' "$bench_out" | awk -F: '/Nodes searched/ {
|
||||
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
|
||||
}')
|
||||
if [ "$actual_compiler" != "$expected_compiler" ] || [ "$actual_bench" != "$EXPECTED_BENCH" ]; then
|
||||
printf '===== CPU %s output (expected %s/%s, got %s/%s) =====\n' \
|
||||
"$cpu" "$expected_compiler" "$EXPECTED_BENCH" "${actual_compiler:--}" "$actual_bench" > "$WORK/$i.fail"
|
||||
else
|
||||
printf 'CPU %s ok\n' "$cpu" > "$WORK/$i.log"
|
||||
fi
|
||||
) &
|
||||
done
|
||||
wait
|
||||
|
||||
FAIL=0
|
||||
i=0
|
||||
for pair in $PAIRS; do
|
||||
i=$((i + 1))
|
||||
if [ -f "$WORK/$i.fail" ]; then
|
||||
cat "$WORK/$i.fail" >&2
|
||||
FAIL=1
|
||||
else
|
||||
cat "$WORK/$i.log" >&2
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$FAIL" != 0 ]; then
|
||||
echo "check_universal.sh: failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "check_universal.sh: Good! Universal binary has correct hardware detection."
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Verify that the arm64 universal binary selects the correct per-arch build
|
||||
# under qemu-aarch64 emulation for a range of target CPUs.
|
||||
#
|
||||
# Usage: check_universal_arm.sh STOCKFISH_EXE EXPECTED_BENCH
|
||||
|
||||
set -eu
|
||||
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Usage: $0 STOCKFISH_EXE EXPECTED_BENCH" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
STOCKFISH_EXE=$1
|
||||
EXPECTED_BENCH=$2
|
||||
|
||||
PAIRS="
|
||||
cortex-a53:armv8
|
||||
max:armv8-dotprod
|
||||
"
|
||||
|
||||
BINARY_SIZE=$(wc -c < "$STOCKFISH_EXE")
|
||||
MAX_SIZE=$((150 * 1024 * 1024))
|
||||
if [ "$BINARY_SIZE" -gt "$MAX_SIZE" ]; then
|
||||
printf 'check_universal_arm.sh: binary size %d bytes exceeds 150 MB limit\n' "$BINARY_SIZE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FAIL=0
|
||||
for pair in $PAIRS; do
|
||||
cpu=${pair%%:*}
|
||||
expected_compiler=${pair##*:}
|
||||
compiler_out=$(qemu-aarch64 -cpu "$cpu" -- "$STOCKFISH_EXE" compiler 2>&1 || true)
|
||||
bench_out=$(qemu-aarch64 -cpu "$cpu" -- "$STOCKFISH_EXE" bench 2>&1 || true)
|
||||
actual_compiler=$(printf '%s\n' "$compiler_out" | awk -F: '/Compilation architecture/ {
|
||||
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
|
||||
}')
|
||||
actual_bench=$(printf '%s\n' "$bench_out" | awk -F: '/Nodes searched/ {
|
||||
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
|
||||
}')
|
||||
if [ "$actual_compiler" != "$expected_compiler" ] || [ "$actual_bench" != "$EXPECTED_BENCH" ]; then
|
||||
printf '===== CPU %s output (expected %s/%s, got %s/%s) =====\n' \
|
||||
"$cpu" "$expected_compiler" "$EXPECTED_BENCH" "${actual_compiler:--}" "$actual_bench" >&2
|
||||
printf 'Full "compiler" output: %s\n' "$compiler_out"
|
||||
printf 'Full "bench" output: %s\n' "$bench_out"
|
||||
FAIL=1
|
||||
else
|
||||
printf 'CPU %s ok\n' "$cpu" >&2
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$FAIL" != 0 ]; then
|
||||
echo "check_universal_arm.sh: failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "check_universal_arm.sh: Good! Universal binary has correct hardware detection."
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Usage: check_universal_macos.sh STOCKFISH_EXE [EXPECTED_BENCH]
|
||||
|
||||
set -eu
|
||||
|
||||
STOCKFISH_EXE=$1
|
||||
|
||||
extract() {
|
||||
awk -F: -v lbl="$1" '$0 ~ lbl {
|
||||
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
|
||||
}'
|
||||
}
|
||||
|
||||
FAIL=0
|
||||
|
||||
BINARY_SIZE=$(wc -c < "$STOCKFISH_EXE")
|
||||
MAX_SIZE=$((150 * 1024 * 1024))
|
||||
if [ "$BINARY_SIZE" -gt "$MAX_SIZE" ]; then
|
||||
printf 'check_universal_macos.sh: binary size %d bytes exceeds 150 MB limit\n' "$BINARY_SIZE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
native_bench=$(arch -arm64 "$STOCKFISH_EXE" bench 2>&1 | extract "Nodes searched" || true)
|
||||
if [ -z "$native_bench" ]; then
|
||||
echo "check_universal_macos.sh: arm64 run produced no bench" >&2
|
||||
FAIL=1
|
||||
else
|
||||
printf 'native (arm64) bench %s\n' "$native_bench" >&2
|
||||
fi
|
||||
|
||||
x86_bench=$(arch -x86_64 "$STOCKFISH_EXE" bench 2>&1 | extract "Nodes searched" || true)
|
||||
if [ -z "$x86_bench" ]; then
|
||||
echo "check_universal_macos.sh: x86_64 (Rosetta) run produced no bench (crashed?)" >&2
|
||||
FAIL=1
|
||||
else
|
||||
printf 'x86_64 (Rosetta) bench %s\n' "$x86_bench" >&2
|
||||
fi
|
||||
|
||||
# Should match
|
||||
if [ "$native_bench" != "$x86_bench" ]; then
|
||||
printf 'check_universal_macos.sh: slice bench mismatch: arm64=%s x86_64=%s\n' \
|
||||
"$native_bench" "$x86_bench" >&2
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
# Check that under Rosetta, SSE41 is detected
|
||||
x86_compiler=$(arch -x86_64 "$STOCKFISH_EXE" compiler 2>&1 || true)
|
||||
if printf '%s\n' "$x86_compiler" | grep -q 'SSE41'; then
|
||||
printf 'x86_64 (Rosetta) compiler reports SSE41 ok\n' >&2
|
||||
else
|
||||
echo "check_universal_macos.sh: x86_64 compiler output missing SSE41" >&2
|
||||
printf '%s\n' "$x86_compiler" >&2
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
if [ "$FAIL" != 0 ]; then
|
||||
echo "check_universal_macos.sh: failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "check_universal_macos.sh: Good! Universal macOS binary works."
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Verify that the riscv64 universal binary selects correct build
|
||||
# and benches correctly under qemu-riscv64 with various vlens
|
||||
#
|
||||
# Usage: check_universal_riscv.sh STOCKFISH_EXE EXPECTED_BENCH
|
||||
|
||||
set -eu
|
||||
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Usage: $0 STOCKFISH_EXE EXPECTED_BENCH" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
STOCKFISH_EXE=$1
|
||||
EXPECTED_BENCH=$2
|
||||
QEMU=${QEMU:-qemu-riscv64}
|
||||
export QEMU_LD_PREFIX=${QEMU_LD_PREFIX:-/usr/riscv64-linux-gnu}
|
||||
|
||||
EXTS=zba=true,zbb=true,zbs=true,zicond=true
|
||||
PAIRS="
|
||||
rv64:riscv64
|
||||
rv64,v=true,$EXTS,vlen=128:riscv64-rva23
|
||||
rv64,v=true,$EXTS,vlen=256:riscv64-rva23
|
||||
rv64,v=true,$EXTS,vlen=512:riscv64-rva23
|
||||
rv64,v=true,$EXTS,vlen=1024:riscv64-rva23
|
||||
"
|
||||
|
||||
BINARY_SIZE=$(wc -c < "$STOCKFISH_EXE")
|
||||
MAX_SIZE=$((150 * 1024 * 1024))
|
||||
if [ "$BINARY_SIZE" -gt "$MAX_SIZE" ]; then
|
||||
printf 'check_universal_riscv.sh: binary size %d bytes exceeds 150 MB limit\n' "$BINARY_SIZE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
|
||||
idx=0
|
||||
for pair in $PAIRS; do
|
||||
idx=$((idx + 1))
|
||||
cpu=${pair%%:*}
|
||||
(
|
||||
comp=$($QEMU -cpu "$cpu" "$STOCKFISH_EXE" compiler 2>&1 | awk -F: '/Compilation architecture/ {
|
||||
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit }')
|
||||
bench=$($QEMU -cpu "$cpu" "$STOCKFISH_EXE" bench 2>&1 | awk -F: '/Nodes searched/ {
|
||||
gsub(/[^0-9]/, "", $2); print $2; exit }')
|
||||
printf '%s|%s|%s\n' "$cpu" "$comp" "$bench" > "$tmp/$idx"
|
||||
) &
|
||||
done
|
||||
wait
|
||||
|
||||
FAIL=0
|
||||
idx=0
|
||||
for pair in $PAIRS; do
|
||||
idx=$((idx + 1))
|
||||
expected=${pair##*:}
|
||||
IFS='|' read -r cpu comp bench < "$tmp/$idx"
|
||||
if [ "$comp" = "$expected" ] && [ "$bench" = "$EXPECTED_BENCH" ]; then
|
||||
printf 'CPU %-26s ok (%s, bench %s)\n' "$cpu" "$comp" "$bench" >&2
|
||||
else
|
||||
printf 'CPU %-26s FAIL: expected %s/%s, got %s/%s\n' \
|
||||
"$cpu" "$expected" "$EXPECTED_BENCH" "${comp:--}" "${bench:--}" >&2
|
||||
FAIL=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$FAIL" != 0 ]; then
|
||||
echo "check_universal_riscv.sh: failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "check_universal_riscv.sh: Good! Universal binary has correct hardware detection."
|
||||
+370
-139
@@ -1,163 +1,394 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Returns properties of the native system.
|
||||
# best architecture as supported by the CPU
|
||||
# filename of the best binary uploaded as an artifact during CI
|
||||
# Returns the best architecture supported by the CPU (as expected by src/Makefile ARCH=).
|
||||
#
|
||||
# Output format:
|
||||
# "<true_arch>\n"
|
||||
#
|
||||
|
||||
# Check if all the given flags are present in the CPU flags list
|
||||
check_flags() {
|
||||
for flag; do
|
||||
printf '%s\n' "$flags" | grep -q -w "$flag" || return 1
|
||||
done
|
||||
# ---------------------------
|
||||
# Helpers (POSIX)
|
||||
# ---------------------------
|
||||
|
||||
# Test hooks (optional env overrides)
|
||||
# GP_UNAME_S: override `uname -s`
|
||||
# GP_UNAME_M: override `uname -m`
|
||||
# GP_CPUINFO: path to a cpuinfo-like fixture file (defaults to /proc/cpuinfo)
|
||||
# GP_BITS: override getconf LONG_BIT result (32/64)
|
||||
# GP_SYSCTL_FEATURES: override sysctl feature strings on Darwin x86_64
|
||||
|
||||
cpuinfo_path=${GP_CPUINFO:-/proc/cpuinfo}
|
||||
|
||||
# Normalize to a single-line, space-separated string.
|
||||
normalize_ws() {
|
||||
printf '%s\n' "$*" | tr '\n\t' ' ' | tr -s ' '
|
||||
}
|
||||
|
||||
# Set the CPU flags list
|
||||
# remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1
|
||||
die() {
|
||||
printf '%s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Populate $flags from /proc/cpuinfo when available,
|
||||
# removing underscores and dots to reduce naming variations.
|
||||
get_flags() {
|
||||
flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo)
|
||||
if [ -r "$cpuinfo_path" ]; then
|
||||
flags=$(
|
||||
awk '
|
||||
/^flags[ \t]*:|^Features[ \t]*:/ {
|
||||
if (!found) {
|
||||
gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, "");
|
||||
line=$0
|
||||
found=1
|
||||
}
|
||||
}
|
||||
END { print line }
|
||||
' "$cpuinfo_path" 2>/dev/null
|
||||
)
|
||||
else
|
||||
flags=''
|
||||
fi
|
||||
flags=$(printf '%s\n' "$flags" | tr '[:upper:]' '[:lower:]')
|
||||
flags=$(normalize_ws "$flags")
|
||||
}
|
||||
|
||||
# Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid
|
||||
check_znver_1_2() {
|
||||
vendor_id=$(awk '/^vendor_id/{print $3; exit}' /proc/cpuinfo)
|
||||
cpu_family=$(awk '/^cpu family/{print $4; exit}' /proc/cpuinfo)
|
||||
[ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true
|
||||
# Populate $flags from the RISC-V isa string; split the single-letter
|
||||
# base (e.g., rv64imafdcv), and keep multi-letter extensions.
|
||||
get_riscv_flags() {
|
||||
if [ -r "$cpuinfo_path" ]; then
|
||||
isa=$(awk -F: '/^isa[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null)
|
||||
else
|
||||
isa=''
|
||||
fi
|
||||
isa=$(printf '%s\n' "$isa" | tr '[:upper:]' '[:lower:]')
|
||||
flags=$(printf '%s\n' "$isa" | awk '{
|
||||
gsub(/^[ \t]*rv(32|64)/, "");
|
||||
n = split($0, ext, "_");
|
||||
out = "";
|
||||
for (i = 1; i <= length(ext[1]); i++) out = out " " substr(ext[1], i, 1);
|
||||
for (i = 2; i <= n; i++) out = out " " ext[i];
|
||||
print out;
|
||||
}')
|
||||
flags=$(normalize_ws "$flags")
|
||||
}
|
||||
|
||||
# Set the file CPU loongarch64 architecture
|
||||
# Populate $flags from sysctl on Darwin x86_64.
|
||||
get_sysctl_flags() {
|
||||
if [ -n "${GP_SYSCTL_FEATURES:-}" ]; then
|
||||
flags=$(printf '%s\n' "$GP_SYSCTL_FEATURES")
|
||||
else
|
||||
flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features 2>/dev/null)
|
||||
fi
|
||||
flags=$(printf '%s\n' "$flags" | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '._')
|
||||
flags=$(normalize_ws "$flags")
|
||||
}
|
||||
|
||||
# Best-effort bitness for fallback arch selection.
|
||||
get_bits() {
|
||||
if [ -n "${GP_BITS:-}" ]; then
|
||||
bits=$GP_BITS
|
||||
else
|
||||
bits=$(getconf LONG_BIT 2>/dev/null)
|
||||
fi
|
||||
case $bits in
|
||||
32|64) : ;;
|
||||
*) bits=64 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Extract ARM architecture level (5/6/7/8/...) from /proc/cpuinfo when present.
|
||||
get_arm_arch_level() {
|
||||
[ -r "$cpuinfo_path" ] || return 1
|
||||
awk '
|
||||
/^CPU architecture[ \t]*:/{
|
||||
s=$0
|
||||
sub(/^[^:]*:[ \t]*/, "", s)
|
||||
if (match(s, /[0-9]+/)) { print substr(s, RSTART, RLENGTH); exit }
|
||||
}
|
||||
/^Processor[ \t]*:/{
|
||||
s=$0
|
||||
sub(/^[^:]*:[ \t]*/, "", s)
|
||||
if (match(s, /ARMv[0-9]+/)) { print substr(s, RSTART+4, RLENGTH-4); exit }
|
||||
}
|
||||
' "$cpuinfo_path" 2>/dev/null
|
||||
}
|
||||
|
||||
# Best-effort ARM architecture level (5/6/7/8/...) with a minimal fallback.
|
||||
# Prefer /proc/cpuinfo when available; fall back to uname -m only when it encodes it.
|
||||
get_arm_level() {
|
||||
arm_level=$(get_arm_arch_level || :)
|
||||
if [ -n "$arm_level" ]; then
|
||||
printf '%s\n' "$arm_level"
|
||||
return 0
|
||||
fi
|
||||
case ${1:-} in
|
||||
armv5*) printf '5\n' ;;
|
||||
armv6*) printf '6\n' ;;
|
||||
armv7*) printf '7\n' ;;
|
||||
armv8l) printf '8\n' ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Whole-token membership check.
|
||||
has_flag() {
|
||||
case " $flags " in
|
||||
*" $1 "*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
match_flags() {
|
||||
for f; do
|
||||
has_flag "$f" || return 1
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
match_any_flags() {
|
||||
for f; do
|
||||
has_flag "$f" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# SSE3 is often exposed as "pni" in /proc/cpuinfo.
|
||||
match_sse3() {
|
||||
match_any_flags sse3 pni
|
||||
}
|
||||
|
||||
# AMD Excavator/Zen1/2 exclusion logic (used for bmi2 tier).
|
||||
# https://web.archive.org/web/20250821132355/https://en.wikichip.org/wiki/amd/cpuid
|
||||
# All of Bulldozer through Excavator are matched here, but pre-Excavator doesn't support bmi2 anyway
|
||||
has_slow_bmi2() (
|
||||
[ -r "$cpuinfo_path" ] || exit 1
|
||||
vendor_id=$(awk '/^vendor_id/{print $3; exit}' "$cpuinfo_path" 2>/dev/null)
|
||||
cpu_family=$(awk '/^cpu family/{print $4; exit}' "$cpuinfo_path" 2>/dev/null)
|
||||
[ "$vendor_id" = "AuthenticAMD" ] \
|
||||
&& { [ "$cpu_family" = "21" ] || [ "$cpu_family" = "23" ]; }
|
||||
)
|
||||
|
||||
match_not_slow_bmi2_and_flags() {
|
||||
has_slow_bmi2 && return 1
|
||||
match_flags "$@"
|
||||
}
|
||||
|
||||
match_sse3_popcnt() {
|
||||
has_flag popcnt || return 1
|
||||
match_sse3
|
||||
}
|
||||
|
||||
match_true() { return 0; }
|
||||
|
||||
# Generic selector: reads lines like "arch|predicate|arg1 arg2 ..."
|
||||
# First match wins; blank lines and lines starting with '#' are ignored.
|
||||
select_arch_from_table() {
|
||||
while IFS='|' read -r arch pred args; do
|
||||
[ -z "$arch" ] && continue
|
||||
case $arch in \#*) continue ;; esac
|
||||
|
||||
if [ -n "$args" ]; then
|
||||
# Intentional splitting of args into words for the predicate.
|
||||
# shellcheck disable=SC2086
|
||||
$pred $args && { printf '%s\n' "$arch"; return 0; }
|
||||
else
|
||||
$pred && { printf '%s\n' "$arch"; return 0; }
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------
|
||||
# Arch selection (table-driven)
|
||||
# ---------------------------
|
||||
|
||||
set_arch_loongarch64() {
|
||||
if check_flags 'lasx'; then
|
||||
true_arch='loongarch64-lasx'
|
||||
elif check_flags 'lsx'; then
|
||||
true_arch='loongarch64-lsx'
|
||||
else
|
||||
true_arch='loongarch64'
|
||||
fi
|
||||
true_arch=$(
|
||||
select_arch_from_table <<'EOF'
|
||||
loongarch64-lasx|match_flags|lasx
|
||||
loongarch64-lsx|match_flags|lsx
|
||||
loongarch64|match_true|
|
||||
EOF
|
||||
)
|
||||
}
|
||||
|
||||
set_arch_riscv64() {
|
||||
get_riscv_flags
|
||||
true_arch=$(
|
||||
select_arch_from_table <<'EOF'
|
||||
riscv64-rva23|match_flags|v zba zbb zbs zicond
|
||||
riscv64|match_true|
|
||||
EOF
|
||||
)
|
||||
}
|
||||
|
||||
# Set the file CPU x86_64 architecture
|
||||
set_arch_x86_64() {
|
||||
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
|
||||
true_arch='x86-64-avx2'
|
||||
elif check_flags 'sse41' && check_flags 'popcnt'; then
|
||||
true_arch='x86-64-sse41-popcnt'
|
||||
else
|
||||
true_arch='x86-64'
|
||||
fi
|
||||
true_arch=$(
|
||||
select_arch_from_table <<'EOF'
|
||||
# Strongest -> weakest (first match wins)
|
||||
x86-64-avx512icl|match_flags|avx512f avx512cd avx512vl avx512dq avx512bw avx512ifma avx512vbmi avx512vbmi2 avx512vpopcntdq avx512bitalg avx512vnni vpclmulqdq gfni vaes
|
||||
x86-64-vnni512|match_flags|avx512vnni avx512dq avx512f avx512bw avx512vl
|
||||
x86-64-avx512|match_flags|avx512f avx512bw
|
||||
x86-64-avxvnni|match_flags|avxvnni
|
||||
x86-64-bmi2|match_not_slow_bmi2_and_flags|bmi2
|
||||
x86-64-avx2|match_flags|avx2
|
||||
x86-64-sse41-popcnt|match_flags|sse41 popcnt
|
||||
x86-64-ssse3|match_flags|ssse3
|
||||
x86-64-sse3-popcnt|match_sse3_popcnt|
|
||||
x86-64|match_true|
|
||||
EOF
|
||||
)
|
||||
}
|
||||
|
||||
set_arch_x86_32() {
|
||||
true_arch=$(
|
||||
select_arch_from_table <<'EOF'
|
||||
x86-32-sse41-popcnt|match_flags|sse41 popcnt
|
||||
x86-32-sse2|match_flags|sse2
|
||||
x86-32|match_true|
|
||||
EOF
|
||||
)
|
||||
}
|
||||
|
||||
# PPC64 needs a little parsing to distinguish vsx vs altivec.
|
||||
set_arch_ppc_64() {
|
||||
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
|
||||
true_arch='ppc-64-vsx'
|
||||
else
|
||||
true_arch='ppc-64-altivec'
|
||||
fi
|
||||
else
|
||||
true_arch='ppc-64'
|
||||
fi
|
||||
if [ -r "$cpuinfo_path" ] && grep -q "altivec" "$cpuinfo_path" 2>/dev/null; then
|
||||
# Typical: "cpu : POWER8E" (extract the number after POWER)
|
||||
power=$(
|
||||
awk -F: '/^cpu[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null \
|
||||
| sed -n 's/.*[Pp][Oo][Ww][Ee][Rr][^0-9]*\([0-9][0-9]*\).*/\1/p'
|
||||
)
|
||||
if [ -z "$power" ]; then
|
||||
power=$(
|
||||
awk -F: '/^cpu[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null \
|
||||
| sed -n 's/.*\([0-9][0-9]*\).*/\1/p'
|
||||
)
|
||||
fi
|
||||
case $power in
|
||||
''|*[!0-9]*)
|
||||
true_arch='ppc-64-altivec'
|
||||
;;
|
||||
*)
|
||||
if [ "$power" -gt 7 ] 2>/dev/null; then
|
||||
true_arch='ppc-64-vsx'
|
||||
else
|
||||
true_arch='ppc-64-altivec'
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
else
|
||||
true_arch='ppc-64'
|
||||
fi
|
||||
}
|
||||
|
||||
# Check the system type
|
||||
uname_s=$(uname -s)
|
||||
uname_m=$(uname -m)
|
||||
# ---------------------------
|
||||
# OS / machine dispatch
|
||||
# ---------------------------
|
||||
|
||||
uname_s=$(uname -s 2>/dev/null)
|
||||
uname_m=$(uname -m 2>/dev/null)
|
||||
uname_s=${GP_UNAME_S:-$uname_s}
|
||||
uname_m=${GP_UNAME_M:-$uname_m}
|
||||
|
||||
case $uname_s in
|
||||
'Darwin') # Mac OSX system
|
||||
case $uname_m in
|
||||
'arm64')
|
||||
true_arch='apple-silicon'
|
||||
file_arch='m1-apple-silicon'
|
||||
;;
|
||||
'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-avx512' ]; then
|
||||
file_arch='x86-64-bmi2'
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
file_os='macos'
|
||||
file_ext='tar'
|
||||
;;
|
||||
'Linux') # Linux system
|
||||
get_flags
|
||||
case $uname_m in
|
||||
'x86_64')
|
||||
file_os='ubuntu'
|
||||
check_znver_1_2
|
||||
set_arch_x86_64
|
||||
;;
|
||||
'i686')
|
||||
file_os='ubuntu'
|
||||
true_arch='x86-32'
|
||||
;;
|
||||
'ppc64'*)
|
||||
file_os='ubuntu'
|
||||
set_arch_ppc_64
|
||||
;;
|
||||
'aarch64')
|
||||
file_os='android'
|
||||
true_arch='armv8'
|
||||
if check_flags 'asimddp'; then
|
||||
true_arch="$true_arch-dotprod"
|
||||
fi
|
||||
;;
|
||||
'armv7'*)
|
||||
file_os='android'
|
||||
true_arch='armv7'
|
||||
if check_flags 'neon'; then
|
||||
true_arch="$true_arch-neon"
|
||||
fi
|
||||
;;
|
||||
'loongarch64'*)
|
||||
file_os='linux'
|
||||
set_arch_loongarch64
|
||||
;;
|
||||
*) # Unsupported machine type, exit with error
|
||||
printf 'Unsupported machine type: %s\n' "$uname_m"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
file_ext='tar'
|
||||
;;
|
||||
'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
|
||||
file_os='windows'
|
||||
file_ext='zip'
|
||||
;;
|
||||
*)
|
||||
# Unknown system type, exit with error
|
||||
printf 'Unsupported system type: %s\n' "$uname_s"
|
||||
exit 1
|
||||
;;
|
||||
Darwin)
|
||||
case $uname_m in
|
||||
arm64)
|
||||
true_arch='apple-silicon'
|
||||
;;
|
||||
x86_64)
|
||||
get_sysctl_flags
|
||||
set_arch_x86_64
|
||||
;;
|
||||
*)
|
||||
get_bits
|
||||
if [ "$bits" = "32" ]; then
|
||||
true_arch='general-32'
|
||||
else
|
||||
true_arch='general-64'
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
Linux)
|
||||
get_flags
|
||||
case $uname_m in
|
||||
x86_64)
|
||||
set_arch_x86_64
|
||||
;;
|
||||
i?86)
|
||||
set_arch_x86_32
|
||||
;;
|
||||
ppc64*)
|
||||
set_arch_ppc_64
|
||||
;;
|
||||
aarch64|arm64)
|
||||
true_arch='armv8'
|
||||
if match_flags asimddp; then
|
||||
true_arch='armv8-dotprod'
|
||||
fi
|
||||
;;
|
||||
armv5*|armv6*|armv7*|armv8l|arm*)
|
||||
arm_level=$(get_arm_level "$uname_m" || :)
|
||||
case $arm_level in
|
||||
5|6)
|
||||
true_arch='general-32'
|
||||
;;
|
||||
7|8)
|
||||
true_arch='armv7'
|
||||
if match_flags neon; then
|
||||
true_arch='armv7-neon'
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
true_arch='general-32'
|
||||
if match_flags neon; then
|
||||
true_arch='armv7-neon'
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
loongarch64*)
|
||||
set_arch_loongarch64
|
||||
;;
|
||||
riscv64)
|
||||
set_arch_riscv64
|
||||
;;
|
||||
e2k*)
|
||||
true_arch='e2k'
|
||||
;;
|
||||
ppc|ppc32|powerpc)
|
||||
true_arch='ppc-32'
|
||||
;;
|
||||
*)
|
||||
# Don't hard-fail: fall back to general-* so ARCH=native still builds
|
||||
get_bits
|
||||
if [ "$bits" = "32" ]; then
|
||||
true_arch='general-32'
|
||||
else
|
||||
true_arch='general-64'
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
MINGW*ARM64*)
|
||||
# Windows ARM64 (MSYS2/MinGW)
|
||||
# Can't reliably detect ARM CPU features here
|
||||
true_arch='armv8-dotprod'
|
||||
;;
|
||||
|
||||
CYGWIN*|MINGW*|MSYS*)
|
||||
# Windows x86_64 (MSYS2/Cygwin/MinGW)
|
||||
get_flags
|
||||
set_arch_x86_64
|
||||
;;
|
||||
|
||||
*)
|
||||
die "Unsupported system type: $uname_s"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$file_arch" ]; then
|
||||
file_arch=$true_arch
|
||||
fi
|
||||
|
||||
file_name="stockfish-$file_os-$file_arch.$file_ext"
|
||||
|
||||
printf '%s %s\n' "$true_arch" "$file_name"
|
||||
printf '%s\n' "$true_arch"
|
||||
|
||||
+18
-10
@@ -1,11 +1,11 @@
|
||||
#!/bin/sh
|
||||
|
||||
wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -qO-") || \
|
||||
(command -v curl > /dev/null 2>&1 && echo "curl -skL"))
|
||||
# download commands with a 5min time-out to ensure things fail if the server stalls
|
||||
wget_or_curl=$( (command -v wget >/dev/null 2>&1 && echo "wget -qO- --timeout=300 --tries=1") ||
|
||||
(command -v curl >/dev/null 2>&1 && echo "curl -skL --max-time 300"))
|
||||
|
||||
|
||||
sha256sum=$( (command -v shasum > /dev/null 2>&1 && echo "shasum -a 256") || \
|
||||
(command -v sha256sum > /dev/null 2>&1 && echo "sha256sum"))
|
||||
sha256sum=$( (command -v shasum >/dev/null 2>&1 && echo "shasum -a 256") ||
|
||||
(command -v sha256sum >/dev/null 2>&1 && echo "sha256sum"))
|
||||
|
||||
if [ -z "$sha256sum" ]; then
|
||||
>&2 echo "sha256sum not found, NNUE files will be assumed valid."
|
||||
@@ -44,7 +44,7 @@ fetch_network() {
|
||||
|
||||
if [ -z "$wget_or_curl" ]; then
|
||||
>&2 printf "%s\n" "Neither wget or curl is installed." \
|
||||
"Install one of these tools to download NNUE files automatically."
|
||||
"Install one of these tools to download NNUE files automatically."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -52,14 +52,16 @@ fetch_network() {
|
||||
"https://tests.stockfishchess.org/api/nn/$_filename" \
|
||||
"https://github.com/official-stockfish/networks/raw/master/$_filename"; do
|
||||
echo "Downloading from $url ..."
|
||||
if $wget_or_curl "$url" > "$_filename"; then
|
||||
if $wget_or_curl "$url" >"$_filename"; then
|
||||
if validate_network "$_filename"; then
|
||||
echo "Successfully validated $_filename"
|
||||
else
|
||||
echo "Downloaded $_filename is invalid"
|
||||
rm -f $_filename
|
||||
echo "Downloaded $_filename is invalid, and has been removed."
|
||||
continue
|
||||
fi
|
||||
else
|
||||
rm -f $_filename
|
||||
echo "Failed to download from $url"
|
||||
fi
|
||||
if [ -f "$_filename" ]; then
|
||||
@@ -72,5 +74,11 @@ fetch_network() {
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_network EvalFileDefaultNameBig && \
|
||||
fetch_network EvalFileDefaultNameSmall
|
||||
fetch_network EvalFileDefaultName
|
||||
|
||||
if [ "$1" = "0" ]; then
|
||||
DUMP_FILE=universal/network_dump.inc
|
||||
echo -n '"' > $DUMP_FILE
|
||||
hexdump -v -e '"\\" "x" 1/1 "%02X"' "$(get_nnue_filename EvalFileDefaultName)" >> $DUMP_FILE
|
||||
echo -n '"' >> $DUMP_FILE
|
||||
fi
|
||||
|
||||
+455
-86
@@ -25,6 +25,13 @@ ifeq ($(KERNEL),Linux)
|
||||
OS := $(shell uname -o)
|
||||
endif
|
||||
|
||||
### Target operating system (may differ from KERNEL when cross-compiling)
|
||||
TARGET_KERNEL := $(KERNEL)
|
||||
ifeq ($(COMP),ndk)
|
||||
TARGET_KERNEL := Linux
|
||||
OS := Android
|
||||
endif
|
||||
|
||||
### Command prefix to run the built executable (e.g. wine, sde, qemu)
|
||||
### Backward compatible alias: WINE_PATH (deprecated)
|
||||
ifneq ($(strip $(WINE_PATH)),)
|
||||
@@ -55,6 +62,8 @@ endif
|
||||
### Executable name
|
||||
ifeq ($(target_windows),yes)
|
||||
EXE = stockfish.exe
|
||||
else ifneq (,$(findstring wasm,$(ARCH)))
|
||||
EXE = stockfish.js
|
||||
else
|
||||
EXE = stockfish
|
||||
endif
|
||||
@@ -67,20 +76,22 @@ BINDIR = $(PREFIX)/bin
|
||||
PGOBENCH = $(RUN_PREFIX) ./$(EXE) bench
|
||||
|
||||
### Source and object files
|
||||
SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
|
||||
SRCS = attacks.cpp 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/network.cpp \
|
||||
nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp \
|
||||
nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp nnue/features/pp_3wide.cpp \
|
||||
engine.cpp score.cpp memory.cpp
|
||||
|
||||
HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \
|
||||
OTHER_SRCS = universal/entry_x86.cpp universal/entry_arm64.cpp universal/entry_riscv64.cpp universal/nnue_embed.cpp
|
||||
|
||||
HEADERS = attacks.h benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \
|
||||
nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/features/full_threats.h \
|
||||
nnue/layers/affine_transform.h nnue/layers/affine_transform_sparse_input.h \
|
||||
nnue/features/pp_3wide.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
|
||||
nnue/nnz_helper.h position.h search.h syzygy/tbprobe.h thread.h thread_native.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 shm.h shm_linux.h
|
||||
|
||||
OBJS = $(notdir $(SRCS:.cpp=.o))
|
||||
|
||||
@@ -120,7 +131,9 @@ VPATH = syzygy:nnue:nnue/features
|
||||
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
|
||||
# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions
|
||||
# lsx = yes/no --- -mlsx --- Use Loongson SIMD eXtension
|
||||
# lasx = yes/no --- -mlasx --- use Loongson Advanced SIMD eXtension
|
||||
# lasx = yes/no --- -mlasx --- Use Loongson Advanced SIMD eXtension
|
||||
# relaxedsimd = y/n --- -mrelaxed-simd --- Use WebAssembly relaxed SIMD extension
|
||||
# syzygy = yes/no --- -DNO_TABLEBASES --- Support Syzygy tablebase probing
|
||||
#
|
||||
# Note that Makefile is space sensitive, so when adding new architectures
|
||||
# or modifying existing flags, you have to make sure there are no extra spaces
|
||||
@@ -143,16 +156,23 @@ endif
|
||||
# explicitly check for the list of supported architectures (as listed with make help),
|
||||
# the user can override with `make ARCH=x86-64-avx512icl SUPPORTED_ARCH=true`
|
||||
ifeq ($(ARCH), $(filter $(ARCH), \
|
||||
x86-64-avx512icl x86-64-vnni512 x86-64-avx512 x86-64-avxvnni \
|
||||
x86-64-universal x86-64-avx512icl x86-64-vnni512 x86-64-avx512 x86-64-avxvnni \
|
||||
x86-64-bmi2 x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
|
||||
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \
|
||||
armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \
|
||||
loongarch64 loongarch64-lsx loongarch64-lasx))
|
||||
armv7 armv7-neon armv8 armv8-dotprod arm64-universal apple-silicon general-64 general-32 riscv64 \
|
||||
riscv64-rva23 riscv64-universal loongarch64 loongarch64-lsx loongarch64-lasx wasm32 wasm32-relaxed-simd))
|
||||
SUPPORTED_ARCH=true
|
||||
else
|
||||
SUPPORTED_ARCH=false
|
||||
endif
|
||||
|
||||
ifneq (,$(filter $(ARCH),x86-64-universal arm64-universal riscv64-universal))
|
||||
UNIVERSAL_BUILD=true
|
||||
else
|
||||
UNIVERSAL_BUILD=false
|
||||
endif
|
||||
|
||||
|
||||
optimize = yes
|
||||
debug = no
|
||||
sanitize = none
|
||||
@@ -175,8 +195,11 @@ vsx = no
|
||||
neon = no
|
||||
dotprod = no
|
||||
arm_version = 0
|
||||
rva23 = no
|
||||
lsx = no
|
||||
lasx = no
|
||||
relaxedsimd = no
|
||||
syzygy = yes
|
||||
STRIP = strip
|
||||
|
||||
ifneq ($(shell which clang-format-20 2> /dev/null),)
|
||||
@@ -363,6 +386,14 @@ ifeq ($(ARCH),armv8-dotprod)
|
||||
arm_version = 8
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),arm64-universal)
|
||||
arch = armv8
|
||||
prefetch = yes
|
||||
popcnt = yes
|
||||
neon = yes
|
||||
arm_version = 8
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),apple-silicon)
|
||||
arch = arm64
|
||||
prefetch = yes
|
||||
@@ -372,6 +403,27 @@ ifeq ($(ARCH),apple-silicon)
|
||||
arm_version = 8
|
||||
endif
|
||||
|
||||
# WASM is largely compiled using emcc's x86 compatibility layer, with occasional
|
||||
# WASM-specific intrinsics
|
||||
ifeq ($(ARCH),wasm32)
|
||||
arch = wasm32
|
||||
sse = yes
|
||||
sse2 = yes
|
||||
ssse3 = yes
|
||||
sse41 = yes
|
||||
syzygy = no
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),wasm32-relaxed-simd)
|
||||
arch = wasm32
|
||||
sse = yes
|
||||
sse2 = yes
|
||||
ssse3 = yes
|
||||
sse41 = yes
|
||||
relaxedsimd = yes
|
||||
syzygy = no
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),ppc-32)
|
||||
arch = ppc
|
||||
bits = 32
|
||||
@@ -408,10 +460,16 @@ ifeq ($(findstring e2k,$(ARCH)),e2k)
|
||||
popcnt = yes
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),riscv64)
|
||||
ifeq ($(findstring riscv64,$(ARCH)),riscv64)
|
||||
arch = riscv64
|
||||
endif
|
||||
|
||||
ifeq ($(ARCH),riscv64-rva23)
|
||||
rva23 = yes
|
||||
popcnt = yes
|
||||
prefetch = yes
|
||||
endif
|
||||
|
||||
ifeq ($(findstring loongarch64,$(ARCH)),loongarch64)
|
||||
arch = loongarch64
|
||||
prefetch = yes
|
||||
@@ -458,12 +516,9 @@ ifeq ($(COMP),gcc)
|
||||
CXXFLAGS += -m$(bits)
|
||||
LDFLAGS += -m$(bits)
|
||||
endif
|
||||
ifeq ($(ARCH),riscv64)
|
||||
CXXFLAGS += -latomic
|
||||
endif
|
||||
else ifeq ($(arch),loongarch64)
|
||||
CXXFLAGS += -latomic
|
||||
else
|
||||
else ifneq ($(arch),wasm32)
|
||||
CXXFLAGS += -m$(bits)
|
||||
LDFLAGS += -m$(bits)
|
||||
endif
|
||||
@@ -472,9 +527,11 @@ ifeq ($(COMP),gcc)
|
||||
LDFLAGS += -latomic
|
||||
endif
|
||||
|
||||
ifneq ($(KERNEL),Darwin)
|
||||
ifneq ($(TARGET_KERNEL),Darwin)
|
||||
ifneq ($(arch),wasm32)
|
||||
LDFLAGS += -Wl,--no-as-needed
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(target_windows),yes)
|
||||
@@ -497,14 +554,14 @@ ifeq ($(COMP),mingw)
|
||||
CXX=i686-w64-mingw32-c++-posix
|
||||
endif
|
||||
endif
|
||||
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations
|
||||
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations -fconstexpr-ops-limit=500000000
|
||||
endif
|
||||
|
||||
ifeq ($(COMP),icx)
|
||||
comp=icx
|
||||
CXX=icpx
|
||||
CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \
|
||||
-Wconditional-uninitialized -Wabi -Wdeprecated
|
||||
-Wconditional-uninitialized -Wabi -Wdeprecated -fconstexpr-steps=500000000
|
||||
endif
|
||||
|
||||
ifeq ($(COMP),clang)
|
||||
@@ -515,9 +572,9 @@ ifeq ($(COMP),clang)
|
||||
endif
|
||||
|
||||
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \
|
||||
-Wconditional-uninitialized
|
||||
-Wconditional-uninitialized -flax-vector-conversions=none -fconstexpr-steps=500000000
|
||||
|
||||
ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),)
|
||||
ifeq ($(filter $(TARGET_KERNEL),Darwin OpenBSD FreeBSD),)
|
||||
ifeq ($(target_windows),)
|
||||
ifneq ($(RTLIB),compiler-rt)
|
||||
LDFLAGS += -latomic
|
||||
@@ -530,9 +587,6 @@ ifeq ($(COMP),clang)
|
||||
CXXFLAGS += -m$(bits)
|
||||
LDFLAGS += -m$(bits)
|
||||
endif
|
||||
ifeq ($(ARCH),riscv64)
|
||||
CXXFLAGS += -latomic
|
||||
endif
|
||||
else ifeq ($(arch),loongarch64)
|
||||
CXXFLAGS += -latomic
|
||||
else
|
||||
@@ -541,13 +595,13 @@ ifeq ($(COMP),clang)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(KERNEL),Darwin)
|
||||
CXXFLAGS += -mmacosx-version-min=10.15
|
||||
LDFLAGS += -mmacosx-version-min=10.15
|
||||
ifeq ($(TARGET_KERNEL),Darwin)
|
||||
mac_target_flags := -mmacosx-version-min=10.15
|
||||
ifneq ($(arch),any)
|
||||
CXXFLAGS += -arch $(arch)
|
||||
LDFLAGS += -arch $(arch)
|
||||
mac_target_flags += -arch $(arch)
|
||||
endif
|
||||
CXXFLAGS += $(mac_target_flags)
|
||||
LDFLAGS += $(mac_target_flags)
|
||||
XCRUN = xcrun
|
||||
endif
|
||||
|
||||
@@ -580,6 +634,7 @@ ifeq ($(COMP),ndk)
|
||||
STRIP=llvm-strip
|
||||
endif
|
||||
endif
|
||||
OBJCOPY=llvm-objcopy
|
||||
LDFLAGS += -static-libstdc++
|
||||
endif
|
||||
|
||||
@@ -610,7 +665,7 @@ else ifeq ($(comp),clang)
|
||||
else
|
||||
profile_make = gcc-profile-make
|
||||
profile_use = gcc-profile-use
|
||||
ifeq ($(KERNEL),Darwin)
|
||||
ifeq ($(TARGET_KERNEL),Darwin)
|
||||
EXTRAPROFILEFLAGS = -fvisibility=hidden
|
||||
endif
|
||||
endif
|
||||
@@ -622,34 +677,35 @@ ifeq ($(COMP),gcc)
|
||||
ifneq ($(gccisclang),)
|
||||
profile_make = clang-profile-make
|
||||
profile_use = clang-profile-use
|
||||
CXXFLAGS += -fconstexpr-steps=500000000
|
||||
else
|
||||
CXXFLAGS += -Wstack-usage=128000
|
||||
CXXFLAGS += -Wstack-usage=128000 -fno-ipa-cp-clone -fconstexpr-ops-limit=500000000
|
||||
endif
|
||||
else ifeq ($(COMP),mingw)
|
||||
CXXFLAGS += -fno-ipa-cp-clone
|
||||
endif
|
||||
|
||||
### On mingw use Windows threads, otherwise POSIX
|
||||
ifneq ($(comp),mingw)
|
||||
CXXFLAGS += -DUSE_PTHREADS
|
||||
# On Android Bionic's C library comes with its own pthread implementation bundled in
|
||||
ifneq ($(OS),Android)
|
||||
# Haiku has pthreads in its libroot, so only link it in on other platforms
|
||||
ifneq ($(KERNEL),Haiku)
|
||||
ifneq ($(COMP),ndk)
|
||||
LDFLAGS += -lpthread
|
||||
# On Android Bionic's C library comes with its own pthread implementation bundled in
|
||||
ifneq ($(OS),Android)
|
||||
# Haiku has pthreads in its libroot, so only link it in on other platforms
|
||||
ifneq ($(KERNEL),Haiku)
|
||||
ifneq ($(COMP),ndk)
|
||||
ifneq ($(arch),wasm32)
|
||||
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
|
||||
add_lrt = yes
|
||||
ifeq ($(target_windows),yes)
|
||||
add_lrt = no
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET_KERNEL),Darwin)
|
||||
add_lrt = no
|
||||
endif
|
||||
|
||||
ifeq ($(add_lrt),yes)
|
||||
LDFLAGS += -lrt
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
@@ -679,7 +735,7 @@ ifeq ($(optimize),yes)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(KERNEL),Darwin)
|
||||
ifeq ($(TARGET_KERNEL),Darwin)
|
||||
ifeq ($(comp),$(filter $(comp),clang icx))
|
||||
CXXFLAGS += -mdynamic-no-pic
|
||||
endif
|
||||
@@ -714,7 +770,7 @@ else
|
||||
endif
|
||||
|
||||
ifeq ($(popcnt),yes)
|
||||
ifeq ($(arch),$(filter $(arch),ppc64 ppc64-altivec ppc64-vsx armv7 armv8 arm64))
|
||||
ifeq ($(arch),$(filter $(arch),ppc64 ppc64-altivec ppc64-vsx armv7 armv8 arm64 riscv64))
|
||||
CXXFLAGS += -DUSE_POPCNT
|
||||
else
|
||||
CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT
|
||||
@@ -800,7 +856,7 @@ endif
|
||||
|
||||
ifeq ($(neon),yes)
|
||||
CXXFLAGS += -DUSE_NEON=$(arm_version)
|
||||
ifeq ($(KERNEL),Linux)
|
||||
ifeq ($(TARGET_KERNEL),Linux)
|
||||
ifneq ($(COMP),ndk)
|
||||
ifneq ($(arch),armv8)
|
||||
CXXFLAGS += -mfpu=neon
|
||||
@@ -813,18 +869,32 @@ ifeq ($(dotprod),yes)
|
||||
CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD
|
||||
endif
|
||||
|
||||
ifeq ($(rva23),yes)
|
||||
CXXFLAGS += -march=rv64gcv1p0_zba_zbb_zbs_zicbop_zicond -DUSE_RVV
|
||||
endif
|
||||
|
||||
ifeq ($(lasx),yes)
|
||||
CXXFLAGS += -DUSE_LASX
|
||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
|
||||
CXXFLAGS += -mlasx
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(lsx),yes)
|
||||
CXXFLAGS += -DUSE_LSX
|
||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
|
||||
CXXFLAGS += -mlsx
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(arch),wasm32)
|
||||
CXXFLAGS += -pthread -msimd128 -DUSE_POPCNT -DNNUE_EMBEDDING_OFF -DUSE_SLOPPY_ATOMICS
|
||||
LDFLAGS += -pthread -sINITIAL_MEMORY=64MB -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=3MB
|
||||
ifeq ($(relaxedsimd),yes)
|
||||
CXXFLAGS += -mrelaxed-simd
|
||||
endif
|
||||
endif
|
||||
|
||||
### 3.7 pext
|
||||
ifeq ($(pext),yes)
|
||||
CXXFLAGS += -DUSE_PEXT
|
||||
@@ -833,19 +903,36 @@ ifeq ($(pext),yes)
|
||||
endif
|
||||
endif
|
||||
|
||||
### 3.8.1 Try to include git commit sha for versioning
|
||||
GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8)
|
||||
ifneq ($(GIT_SHA), )
|
||||
CXXFLAGS += -DGIT_SHA=$(GIT_SHA)
|
||||
### Syzygy tablebases
|
||||
ifeq ($(syzygy),no)
|
||||
CXXFLAGS += -DNO_TABLEBASES
|
||||
endif
|
||||
|
||||
### 3.8.2 Try to include git commit date for versioning
|
||||
GIT_DATE := $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null)
|
||||
ifneq ($(GIT_DATE), )
|
||||
CXXFLAGS += -DGIT_DATE=$(GIT_DATE)
|
||||
### 3.8.1 Try to include git info for versioning and avoid recompiles if nothing changes
|
||||
BUILD_SHA_FILE := .build_sha.txt
|
||||
BUILD_DATE_FILE := .build_date.txt
|
||||
BUILD_DIFFINDEX_FILE := .build_diffindex.txt
|
||||
GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8 || true)
|
||||
GIT_DATE := $(shell git show -s --date=format:%Y%m%d --format=%cd HEAD 2>/dev/null || true)
|
||||
GIT_DIFFINDEX := $(shell git diff-index --quiet HEAD -- 2>/dev/null; [ $$? -eq 1 ] && echo 1 || true)
|
||||
COMPILER_DATE := $(shell date +%Y%m%d 2>/dev/null)
|
||||
|
||||
BUILD_DATE := $(if $(GIT_DATE),$(GIT_DATE),$(COMPILER_DATE))
|
||||
|
||||
define cache_file_contents
|
||||
$(shell \
|
||||
if [ ! -f "$(1)" ] || [ "$$(cat "$(1)" 2>/dev/null)" != "$(2)" ]; then \
|
||||
printf '%s\n' "$(2)" > "$(1)"; \
|
||||
fi)
|
||||
endef
|
||||
|
||||
ifneq ($(filter $(MAKECMDGOALS),help strip install clean net objclean profileclean format config-sanity),$(MAKECMDGOALS))
|
||||
_ := $(call cache_file_contents,$(BUILD_SHA_FILE),$(GIT_SHA))
|
||||
_ := $(call cache_file_contents,$(BUILD_DATE_FILE),$(BUILD_DATE))
|
||||
_ := $(call cache_file_contents,$(BUILD_DIFFINDEX_FILE),$(BUILD_DIFFINDEX))
|
||||
endif
|
||||
|
||||
### 3.8.3 Try to include architecture
|
||||
### 3.8.2 Try to include architecture
|
||||
ifneq ($(ARCH), )
|
||||
CXXFLAGS += -DARCH=$(ARCH)
|
||||
endif
|
||||
@@ -854,27 +941,41 @@ endif
|
||||
### This is a mix of compile and link time options because the lto link phase
|
||||
### needs access to the optimization flags.
|
||||
ifeq ($(optimize),yes)
|
||||
ifeq ($(debug), no)
|
||||
ifeq ($(debug),no)
|
||||
ifeq ($(arch),wasm32)
|
||||
else ifneq ($(TARGET_KERNEL),Darwin)
|
||||
LLD_BIN := $(shell command -v ld.lld 2>/dev/null)
|
||||
ifeq ($(LLD_BIN),)
|
||||
LLD_BIN := $(shell command -v lld 2>/dev/null)
|
||||
endif
|
||||
ifneq ($(LLD_BIN),)
|
||||
ifeq ($(comp),clang)
|
||||
LDFLAGS += -fuse-ld=lld
|
||||
else ifeq ($(comp),gcc)
|
||||
ifneq ($(gccisclang),)
|
||||
LDFLAGS += -fuse-ld=lld
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(comp),$(filter $(comp),clang icx))
|
||||
CXXFLAGS += -flto=full
|
||||
ifeq ($(comp),icx)
|
||||
CXXFLAGS += -fwhole-program-vtables
|
||||
endif
|
||||
ifeq ($(target_windows),yes)
|
||||
CXXFLAGS += -fuse-ld=lld
|
||||
endif
|
||||
LDFLAGS += $(CXXFLAGS)
|
||||
|
||||
# GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be
|
||||
# GCC on some systems.
|
||||
else ifeq ($(comp),gcc)
|
||||
ifeq ($(gccisclang),)
|
||||
CXXFLAGS += -flto -flto-partition=one
|
||||
LDFLAGS += $(CXXFLAGS) -flto=jobserver
|
||||
else
|
||||
CXXFLAGS += -flto=full
|
||||
LDFLAGS += $(CXXFLAGS)
|
||||
endif
|
||||
ifeq ($(gccisclang),)
|
||||
CXXFLAGS += -flto -flto-partition=one
|
||||
LDFLAGS += $(CXXFLAGS) -flto=jobserver
|
||||
else
|
||||
CXXFLAGS += -flto=full
|
||||
LDFLAGS += $(CXXFLAGS)
|
||||
endif
|
||||
|
||||
# To use LTO and static linking on Windows,
|
||||
# the tool chain requires gcc version 10.1 or later.
|
||||
@@ -892,6 +993,13 @@ ifeq ($(OS), Android)
|
||||
LDFLAGS += -fPIE -pie
|
||||
endif
|
||||
|
||||
### 3.11 Inline settings
|
||||
ifeq ($(optimize), yes)
|
||||
ifeq ($(comp), clang)
|
||||
CXXFLAGS += -Xclang -mllvm -Xclang -inline-threshold=500
|
||||
endif
|
||||
endif
|
||||
|
||||
### ==========================================================================
|
||||
### Section 4. Public Targets
|
||||
### ==========================================================================
|
||||
@@ -907,6 +1015,7 @@ help:
|
||||
echo "help > Display architecture details" && \
|
||||
echo "profile-build > standard build with profile-guided optimization" && \
|
||||
echo "build > skip profile-guided optimization" && \
|
||||
echo "macos-lipo > macOS x86-64 + Apple silicon universal binary" && \
|
||||
echo "net > Download the default nnue nets" && \
|
||||
echo "strip > Strip executable" && \
|
||||
echo "install > Install executable" && \
|
||||
@@ -915,6 +1024,7 @@ help:
|
||||
echo "Supported archs:" && \
|
||||
echo "" && \
|
||||
echo "native > select the best architecture for the host processor (default)" && \
|
||||
echo "x86-64-universal > x86 64-bit with automatic runtime selection of the best architecture" && \
|
||||
echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lake or AMD Zen 4" && \
|
||||
echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \
|
||||
echo "x86-64-avx512 > x86 64-bit with avx512 support" && \
|
||||
@@ -935,6 +1045,7 @@ help:
|
||||
echo "ppc-32 > PPC 32-bit" && \
|
||||
echo "armv7 > ARMv7 32-bit" && \
|
||||
echo "armv7-neon > ARMv7 32-bit with popcnt and neon" && \
|
||||
echo "arm64-universal > ARMv8 64-bit with automatic runtime selection of the best architecture" && \
|
||||
echo "armv8 > ARMv8 64-bit with popcnt and neon" && \
|
||||
echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" && \
|
||||
echo "e2k > Elbrus 2000" && \
|
||||
@@ -942,6 +1053,8 @@ help:
|
||||
echo "general-64 > unspecified 64-bit" && \
|
||||
echo "general-32 > unspecified 32-bit" && \
|
||||
echo "riscv64 > RISC-V 64-bit" && \
|
||||
echo "riscv64-rva23 > RISC-V 64-bit RVA23 profile" && \
|
||||
echo "riscv64-universal > RISC-V 64-bit with automatic runtime selection of the best architecture" && \
|
||||
echo "loongarch64 > LoongArch 64-bit" && \
|
||||
echo "loongarch64-lsx > LoongArch 64-bit with SIMD eXtension" && \
|
||||
echo "loongarch64-lasx > LoongArch 64-bit with Advanced SIMD eXtension" && \
|
||||
@@ -956,14 +1069,15 @@ help:
|
||||
echo "" && \
|
||||
echo "Simple examples. If you don't know what to do, you likely want to run one of: " && \
|
||||
echo "" && \
|
||||
echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " && \
|
||||
echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems " && \
|
||||
echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " && \
|
||||
echo "make -j profile-build ARCH=native # a fast compile for local use" && \
|
||||
echo "make -j profile-build ARCH=x86-64-avx2 # A portable compile for most x86-64 systems " && \
|
||||
echo "make -j profile-build ARCH=x86-64 # A portable compile for x86-64 systems " && \
|
||||
echo "" && \
|
||||
echo "Advanced examples, for experienced users: " && \
|
||||
echo "" && \
|
||||
echo "make -j profile-build ARCH=x86-64-avxvnni" && \
|
||||
echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" && \
|
||||
echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc CXX=g++-12.0" && \
|
||||
echo 'make -j profile-build ARCH=x86-64-universal RUN_PREFIX="/path/to/sde -future --" CXX=g++-15' && \
|
||||
echo "make -j build ARCH=x86-64-ssse3 COMP=clang" && \
|
||||
echo ""
|
||||
ifneq ($(SUPPORTED_ARCH), true)
|
||||
@@ -972,7 +1086,7 @@ ifneq ($(SUPPORTED_ARCH), true)
|
||||
endif
|
||||
|
||||
|
||||
.PHONY: help analyze build profile-build strip install clean net \
|
||||
.PHONY: help analyze build profile-build macos-lipo strip install clean net \
|
||||
objclean profileclean config-sanity \
|
||||
icx-profile-use icx-profile-make \
|
||||
gcc-profile-use gcc-profile-make \
|
||||
@@ -982,6 +1096,7 @@ endif
|
||||
analyze: net config-sanity objclean
|
||||
$(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS)
|
||||
|
||||
ifeq ($(UNIVERSAL_BUILD),false)
|
||||
build: net config-sanity
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
|
||||
|
||||
@@ -1000,6 +1115,84 @@ profile-build: net config-sanity objclean profileclean
|
||||
@echo ""
|
||||
@echo "Step 4/4. Deleting profile data ..."
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean
|
||||
endif
|
||||
|
||||
# Default network filename, read from evaluate.h's EvalFileDefaultName.
|
||||
macos-lipo: NNUE_NET = $(shell grep -oE 'nn-[0-9a-f]+\.nnue' $(CURDIR)/evaluate.h | head -1)
|
||||
macos-lipo:
|
||||
@echo ""
|
||||
@echo "Step 1/4. Building x86-64 slice (network shared from Apple-silicon slice) ..."
|
||||
CXXFLAGS="-arch x86_64" $(MAKE) build ARCH=x86-64-universal COMP=$(COMP) MACOS_X86_SLICE=1
|
||||
mv $(EXE) $(EXE)-x86_64
|
||||
@echo ""
|
||||
@echo "Step 2/4. Building Apple silicon slice ..."
|
||||
$(MAKE) profile-build ARCH=apple-silicon COMP=$(COMP)
|
||||
mv $(EXE) $(EXE)-arm64
|
||||
@echo ""
|
||||
@echo "Step 3/4. Combining slices and locating the embedded network ..."
|
||||
lipo -create $(EXE)-x86_64 $(EXE)-arm64 -output $(EXE)
|
||||
$(SHELL) universal/patch_x86_slice.sh $(EXE) $(EXE)-x86_64 $(NNUE_NET)
|
||||
@echo ""
|
||||
@echo "Step 4/4. Re-combining slices with the patched x86-64 slice ..."
|
||||
lipo -create $(EXE)-x86_64 $(EXE)-arm64 -output $(EXE)
|
||||
@rm -f $(EXE)-x86_64 $(EXE)-arm64
|
||||
@echo "Universal macOS binary built: $(EXE)"
|
||||
|
||||
ifeq ($(TARGET_KERNEL),Darwin)
|
||||
# Apple uses ld64 which doesn't have --save-temps
|
||||
LTO_OBJ_SUFFIX := .lto.o
|
||||
SAVE_TEMPS := -Wl,-object_path_lto,stockfish.lto.o
|
||||
else ifeq ($(comp), clang)
|
||||
LTO_OBJ_SUFFIX := .lto.o
|
||||
ifeq ($(target_windows),yes)
|
||||
SAVE_TEMPS := -Wl,--lto-emit-asm # ld.lld doesn't have a save temps functionality
|
||||
use_lto_emit_asm := yes
|
||||
else
|
||||
SAVE_TEMPS := -Wl,--save-temps
|
||||
endif
|
||||
else
|
||||
LTO_OBJ_SUFFIX := .ltrans0.ltrans.o
|
||||
SAVE_TEMPS := -save-temps
|
||||
endif
|
||||
|
||||
ifeq ($(arch),armv8)
|
||||
ASM_FLAGS := -mcpu=neoverse-v2
|
||||
endif
|
||||
|
||||
MODNAME := $(subst -,_,$(notdir $(CURDIR)))
|
||||
|
||||
universal-object-pgo: objclean profileclean universalclean
|
||||
@echo ""
|
||||
@echo "Step 1/4. Building instrumented executable ..."
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(ENV_LDFLAGS) $(NNUE_EMBED_OBJ)' $(profile_make)
|
||||
@echo ""
|
||||
@echo "Step 2/4. Running benchmark for pgo-build ..."
|
||||
$(PGOBENCH) > PGOBENCH.out 2>&1
|
||||
tail -n 4 PGOBENCH.out
|
||||
@echo ""
|
||||
@echo "Step 3/4. Building optimized object files ..."
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(SAVE_TEMPS) $(NNUE_EMBED_OBJ)' $(profile_use)
|
||||
@echo ""
|
||||
ifeq ($(use_lto_emit_asm),yes)
|
||||
@echo "Step 4/4. Renaming ctors and extracting LTO-optimized relocatable object ..."
|
||||
awk -v MODNAME=$(MODNAME) -f ../../universal/rewrite_asm_sections.awk *.lto.s > renamed.s
|
||||
# -mcpu=neoverse-v2 (or another CPU supporting dotprod) is required for clang
|
||||
# to not complain when assembling
|
||||
$(CXX) -c renamed.s -o stockfish.o $(ASM_FLAGS)
|
||||
else
|
||||
@echo "Step 4/4. Extracting LTO-optimized relocatable object ..."
|
||||
cp "$(basename $(EXE))$(LTO_OBJ_SUFFIX)" stockfish.o
|
||||
endif
|
||||
|
||||
universal-object-nopgo: objclean universalclean
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(SAVE_TEMPS) $(NNUE_EMBED_OBJ)' all
|
||||
ifeq ($(use_lto_emit_asm),yes)
|
||||
awk -v MODNAME=$(MODNAME) -f ../../universal/rewrite_asm_sections.awk *.lto.s > renamed.s
|
||||
$(CXX) -c renamed.s -o stockfish.o $(ASM_FLAGS)
|
||||
else
|
||||
cp "$(basename $(EXE))$(LTO_OBJ_SUFFIX)" stockfish.o
|
||||
endif
|
||||
|
||||
strip:
|
||||
$(STRIP) $(EXE)
|
||||
@@ -1010,12 +1203,12 @@ install:
|
||||
$(STRIP) $(BINDIR)/$(EXE)
|
||||
|
||||
# clean all
|
||||
clean: objclean profileclean
|
||||
clean: objclean profileclean universalclean
|
||||
@rm -f .depend *~ core
|
||||
|
||||
# clean binaries and objects
|
||||
objclean:
|
||||
@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
|
||||
@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o $(BUILD_SHA_FILE) $(BUILD_DATE_FILE) $(BUILD_DIFFINDEX_FILE)
|
||||
|
||||
# clean auxiliary profiling files
|
||||
profileclean:
|
||||
@@ -1027,12 +1220,16 @@ profileclean:
|
||||
@rm -f stockfish.res
|
||||
@rm -f ./-lstdc++.res
|
||||
|
||||
universalclean:
|
||||
@rm -rf $(TEMP_DIR)
|
||||
@rm -f universal/network_dump.inc
|
||||
|
||||
# evaluation network (nnue)
|
||||
net:
|
||||
@$(SHELL) ../scripts/net.sh
|
||||
|
||||
format:
|
||||
$(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file
|
||||
$(CLANG-FORMAT) -i $(SRCS) $(OTHER_SRCS) $(HEADERS) -style=file
|
||||
|
||||
### ==========================================================================
|
||||
### Section 5. Private Targets
|
||||
@@ -1070,6 +1267,7 @@ config-sanity: net
|
||||
echo "arm_version: '$(arm_version)'" && \
|
||||
echo "lsx: '$(lsx)'" && \
|
||||
echo "lasx: '$(lasx)'" && \
|
||||
echo "syzygy: '$(syzygy)'" && \
|
||||
echo "target_windows: '$(target_windows)'" && \
|
||||
echo "" && \
|
||||
echo "Flags:" && \
|
||||
@@ -1085,7 +1283,8 @@ config-sanity: net
|
||||
(test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
|
||||
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \
|
||||
test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || \
|
||||
test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64") && \
|
||||
test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64" || \
|
||||
test "$(arch)" = "wasm32") && \
|
||||
(test "$(bits)" = "32" || test "$(bits)" = "64") && \
|
||||
(test "$(prefetch)" = "yes" || test "$(prefetch)" = "no") && \
|
||||
(test "$(popcnt)" = "yes" || test "$(popcnt)" = "no") && \
|
||||
@@ -1104,6 +1303,7 @@ config-sanity: net
|
||||
(test "$(neon)" = "yes" || test "$(neon)" = "no") && \
|
||||
(test "$(lsx)" = "yes" || test "$(lsx)" = "no") && \
|
||||
(test "$(lasx)" = "yes" || test "$(lasx)" = "no") && \
|
||||
(test "$(syzygy)" = "yes" || test "$(syzygy)" = "no") && \
|
||||
(test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || \
|
||||
test "$(comp)" = "clang" || test "$(comp)" = "armv7a-linux-androideabi16-clang" || \
|
||||
test "$(comp)" = "aarch64-linux-android21-clang")
|
||||
@@ -1111,9 +1311,11 @@ config-sanity: net
|
||||
$(EXE): $(OBJS)
|
||||
+$(CXX) -o $@ $(OBJS) $(LDFLAGS)
|
||||
|
||||
# Force recompilation to ensure version info is up-to-date
|
||||
misc.o: FORCE
|
||||
FORCE:
|
||||
%.o: %.cpp
|
||||
$(strip $(CXX) $(CPPFLAGS) $(CXXFLAGS)) -c -o $@ $<
|
||||
|
||||
misc.o: misc.cpp $(BUILD_SHA_FILE) $(BUILD_DATE_FILE) $(BUILD_DIFFINDEX_FILE)
|
||||
$(strip $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(if $(GIT_SHA),-DGIT_SHA=$(GIT_SHA)) $(if $(GIT_DATE),-DGIT_DATE=$(GIT_DATE))) $(if $(GIT_DIFFINDEX),-DGIT_DIFFINDEX=$(GIT_DIFFINDEX)) -c -o $@ $<
|
||||
|
||||
clang-profile-make:
|
||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
|
||||
@@ -1160,5 +1362,172 @@ icx-profile-use:
|
||||
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
|
||||
|
||||
ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean format config-sanity))
|
||||
ifeq ($(UNIVERSAL_BUILD),false)
|
||||
-include .depend
|
||||
endif
|
||||
endif
|
||||
|
||||
### ==========================================================================
|
||||
### Section 6. Universal binary driver
|
||||
### ==========================================================================
|
||||
#
|
||||
# Usage:
|
||||
# make -j ARCH=x86-64-universal build # non-PGO
|
||||
# make -j ARCH=x86-64-universal profile-build # PGO (use RUN_PREFIX for intel SDE)
|
||||
# make -j ARCH=arm64-universal build
|
||||
|
||||
TEMP_DIR := $(CURDIR)/temp_builds
|
||||
UNIVERSAL_EXE := $(CURDIR)/$(EXE)
|
||||
|
||||
ifeq ($(UNIVERSAL_BUILD),true)
|
||||
|
||||
UNIVERSAL_SRC_DIR := $(CURDIR)/universal
|
||||
NNUE_EMBED_OBJ := $(TEMP_DIR)/nnue_embed.o
|
||||
NET_SENTINEL := $(TEMP_DIR)/.net-done
|
||||
|
||||
ifeq ($(arch),x86_64)
|
||||
UNIVERSAL_ARCHES := x86-64 x86-64-sse41-popcnt x86-64-avx2 x86-64-bmi2 \
|
||||
x86-64-avxvnni x86-64-avx512 x86-64-vnni512 x86-64-avx512icl
|
||||
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_x86.o
|
||||
UNIVERSAL_ENTRY_SRC := $(UNIVERSAL_SRC_DIR)/entry_x86.cpp
|
||||
BASELINE_ARCH := x86-64
|
||||
else ifeq ($(arch),riscv64)
|
||||
UNIVERSAL_ARCHES := riscv64 riscv64-rva23
|
||||
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_riscv64.o
|
||||
UNIVERSAL_ENTRY_SRC := $(UNIVERSAL_SRC_DIR)/entry_riscv64.cpp
|
||||
BASELINE_ARCH := riscv64
|
||||
else
|
||||
UNIVERSAL_ARCHES := armv8 armv8-dotprod
|
||||
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_arm64.o
|
||||
UNIVERSAL_ENTRY_SRC := $(UNIVERSAL_SRC_DIR)/entry_arm64.cpp
|
||||
BASELINE_ARCH := armv8
|
||||
endif
|
||||
|
||||
# Top-level tracked items in src/ (directories and files)
|
||||
TRACKED_TOP := $(sort $(foreach f,$(SRCS) $(HEADERS) Makefile incbin,$(firstword $(subst /, ,$(f)))))
|
||||
|
||||
# Baseline build must come first in the final link (Windows --allow-multiple-definition
|
||||
# uses the first copy of inline duplicates)
|
||||
ARCH_OBJS := $(TEMP_DIR)/$(BASELINE_ARCH)/stockfish.o \
|
||||
$(patsubst %,$(TEMP_DIR)/%/stockfish.o,$(filter-out $(BASELINE_ARCH),$(UNIVERSAL_ARCHES)))
|
||||
|
||||
UNIVERSAL_FINAL_FLAGS := -fno-exceptions -Os \
|
||||
-Wno-c++26-extensions -Wno-c23-extensions # suppress #embed warnings
|
||||
|
||||
ifeq ($(TARGET_KERNEL),Darwin)
|
||||
UNIVERSAL_FINAL_FLAGS += $(mac_target_flags)
|
||||
else ifeq ($(TARGET_KERNEL),Linux)
|
||||
UNIVERSAL_FINAL_FLAGS += -static-libstdc++ -static-libgcc
|
||||
ifeq ($(COMP),ndk)
|
||||
UNIVERSAL_FINAL_FLAGS += -static
|
||||
else
|
||||
UNIVERSAL_FINAL_FLAGS += -lrt -lpthread
|
||||
endif
|
||||
else # windows
|
||||
UNIVERSAL_FINAL_FLAGS += -Wl,--allow-multiple-definition -static
|
||||
ifeq ($(COMP),clang)
|
||||
UNIVERSAL_FINAL_FLAGS += -lpthread
|
||||
endif
|
||||
endif
|
||||
|
||||
# $(call arch-namespace,x86-64-avx2) => Stockfish_x86_64_avx2
|
||||
arch-suffix = $(subst -,_,$(1))
|
||||
arch-namespace = Stockfish_$(call arch-suffix,$(1))
|
||||
# Itanium ABI mangled name for Stockfish_<suffix>::main(int, char**)
|
||||
arch-mangled = _ZN$(shell printf %s '$(call arch-namespace,$(1))' | wc -c | tr -d ' ')$(call arch-namespace,$(1))4mainEiPPc
|
||||
# Mach-O initializer section name: strip "x86-64" or "x86-64-" and prefix with "_i_"
|
||||
# (Mach-O section names are restricted to <=16 chars, hence this naming scheme)
|
||||
arch-mac-section = _i_$(subst -,_,$(patsubst x86-64,,$(patsubst x86-64-%,%,$(1))))
|
||||
|
||||
ifeq ($(TARGET_KERNEL),Darwin)
|
||||
arch-defmain = -Wl,-alias,_$(call arch-mangled,$(1)),_main
|
||||
# lld-link, used on Windows+clang, doesn't accept --defsym, so
|
||||
# we define main directly in main.cpp instead and delete it later.
|
||||
else ifeq ($(target_windows)+$(COMP),yes+clang)
|
||||
arch-defmain = -DUNIVERSAL_NEEDS_MAIN_SHIM
|
||||
else
|
||||
arch-defmain = -Wl,--defsym=main=$(call arch-mangled,$(1))
|
||||
endif
|
||||
|
||||
EMBED_DIR_SUPPORTED := $(shell echo 'int main(){}' | $(CXX) -x c++ --embed-dir=. -o /dev/null -c - 2>/dev/null && echo 1 || echo 0)
|
||||
ifeq ($(EMBED_DIR_SUPPORTED),1)
|
||||
EMBED_DIR = --embed-dir=$(CURDIR)
|
||||
endif
|
||||
|
||||
# macos-lipo sets MACOS_X86_SLICE=1 when building the x86-64 slice so that the
|
||||
# slice reads the network out of the Apple-silicon slice instead of embedding
|
||||
# its own copy (see universal/nnue_embed.cpp and universal/patch_x86_slice.sh).
|
||||
ifeq ($(MACOS_X86_SLICE),1)
|
||||
SLICE_DEF := -DUNIVERSAL_BINARY_MACOS_X86_SLICE
|
||||
endif
|
||||
|
||||
arch-cxxflags = $(EMBED_DIR) -DStockfish=$(call arch-namespace,$(1)) -DUNIVERSAL_BINARY $(SLICE_DEF) \
|
||||
$(call arch-defmain,$(1)) -Wno-c++26-extensions -Wno-c23-extensions
|
||||
|
||||
UOBJ_TGT ?= universal-object-pgo
|
||||
ifeq ($(COMP),clang)
|
||||
OBJCOPY ?= llvm-objcopy
|
||||
else
|
||||
OBJCOPY ?= objcopy
|
||||
endif
|
||||
|
||||
.PHONY: build profile-build universal-clean
|
||||
build: UOBJ_TGT := universal-object-nopgo
|
||||
build: $(UNIVERSAL_EXE)
|
||||
profile-build: UOBJ_TGT := universal-object-pgo
|
||||
profile-build: $(UNIVERSAL_EXE)
|
||||
|
||||
$(TEMP_DIR):
|
||||
@mkdir -p $@
|
||||
|
||||
# Download nnue files once in src/ (reused by every sub-arch via --embed-dir).
|
||||
$(NET_SENTINEL): | $(TEMP_DIR)
|
||||
@cd $(CURDIR) && $(SHELL) ../scripts/net.sh $(EMBED_DIR_SUPPORTED)
|
||||
@touch $@
|
||||
|
||||
$(NNUE_EMBED_OBJ): $(UNIVERSAL_SRC_DIR)/nnue_embed.cpp $(NET_SENTINEL) | $(TEMP_DIR)
|
||||
$(CXX) -O2 -std=c++20 -Wno-c++26-extensions $(SLICE_DEF) $(mac_target_flags) $(EXTRACXXFLAGS) $(EMBED_DIR) -c $< -o $@
|
||||
|
||||
$(UNIVERSAL_ENTRY_OBJ): $(UNIVERSAL_ENTRY_SRC) | $(TEMP_DIR)
|
||||
$(CXX) -O2 -std=c++20 $(mac_target_flags) $(EXTRACXXFLAGS) -c $< -o $@
|
||||
|
||||
# Symlink tracked top-level items from src/ into temp_builds/<arch>/.
|
||||
$(TEMP_DIR)/%/.setup: | $(TEMP_DIR)
|
||||
@rm -rf $(TEMP_DIR)/$*
|
||||
@mkdir -p $(TEMP_DIR)/$*
|
||||
@$(foreach f,$(TRACKED_TOP),ln -s $(CURDIR)/$(f) $(TEMP_DIR)/$*/$(f);)
|
||||
@touch $@
|
||||
|
||||
# Per-arch stockfish.o
|
||||
.SECONDARY: $(patsubst %,$(TEMP_DIR)/%/.setup,$(UNIVERSAL_ARCHES))
|
||||
|
||||
$(TEMP_DIR)/%/stockfish.o: $(TEMP_DIR)/%/.setup $(NNUE_EMBED_OBJ) $(NET_SENTINEL)
|
||||
+ENV_CXXFLAGS='$(ENV_CXXFLAGS) $(call arch-cxxflags,$*)' \
|
||||
$(MAKE) -C $(TEMP_DIR)/$* $(UOBJ_TGT) \
|
||||
ARCH=$* CXX=$(CXX) COMP=$(COMP) \
|
||||
NNUE_EMBED_OBJ=$(NNUE_EMBED_OBJ)
|
||||
ifeq ($(TARGET_KERNEL),Darwin)
|
||||
# Rename the initializer section
|
||||
ld -r -rename_section __DATA __mod_init_func __DATA $(call arch-mac-section,$*) $@ -o $@.tmp
|
||||
mv $@.tmp $@
|
||||
else
|
||||
# Drop COMDAT groups; clang LTO leaves them around internalized LOCAL
|
||||
# template instantiations which causes problems at the final link
|
||||
$(OBJCOPY) -R .group $@
|
||||
ifeq ($(use_lto_emit_asm),yes)
|
||||
# Delete per-arch main shim
|
||||
$(OBJCOPY) -N main $@
|
||||
else
|
||||
# Rename .init_array (Linux) or .ctors (Windows) so we can manually invoke them
|
||||
$(OBJCOPY) --rename-section .init_array=$(call arch-suffix,$*)_init $@
|
||||
$(OBJCOPY) --rename-section .ctors=$(call arch-suffix,$*)_init $@
|
||||
# Make the array inert
|
||||
$(OBJCOPY) --set-section-flags $(call arch-suffix,$*)_init=alloc,data $@
|
||||
endif
|
||||
endif
|
||||
|
||||
$(UNIVERSAL_EXE): $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS)
|
||||
$(CXX) -o $@ $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS) $(UNIVERSAL_FINAL_FLAGS) $(EXTRALDFLAGS)
|
||||
@echo "Universal binary built: $@"
|
||||
|
||||
endif # UNIVERSAL_BUILD
|
||||
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include "attacks.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
namespace Stockfish::Attacks {
|
||||
|
||||
#ifdef USE_DUAL_HYPERBOLA_QUINT
|
||||
alignas(64) DualMagic DualMagics[SQUARE_NB];
|
||||
#endif
|
||||
|
||||
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||
Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
namespace {
|
||||
|
||||
#ifndef USE_DUAL_HYPERBOLA_QUINT
|
||||
alignas(64) Magic Magics[SQUARE_NB][2];
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
[[maybe_unused]] static Bitboard line_mask(Square sq, Direction d1, Direction d2) {
|
||||
Bitboard mask = 0, dest;
|
||||
for (Direction d : {d1, d2})
|
||||
{
|
||||
Square s = sq;
|
||||
while ((dest = safe_destination(s, d)))
|
||||
{
|
||||
mask |= dest;
|
||||
s += d;
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
#ifdef USE_HYPERBOLA_QUINT
|
||||
static void init_magics(Magic magics[][2]) {
|
||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||
{
|
||||
Magic& rook = magics[s][ROOK - BISHOP];
|
||||
rook.mask1 = line_mask(s, NORTH, SOUTH);
|
||||
rook.mask2 = line_mask(s, EAST, WEST);
|
||||
|
||||
Magic& bishop = magics[s][BISHOP - BISHOP];
|
||||
bishop.mask1 = line_mask(s, NORTH_EAST, SOUTH_WEST);
|
||||
bishop.mask2 = line_mask(s, NORTH_WEST, SOUTH_EAST);
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(USE_DUAL_HYPERBOLA_QUINT)
|
||||
|
||||
// Sliding attacks within a rank, indexed by the slider's file and the
|
||||
// 8-bit rank occupancy, yielding the 8-bit attack set on that rank
|
||||
constexpr auto RankAttacks = []() {
|
||||
std::array<std::array<u8, 256>, FILE_NB> table{};
|
||||
for (int file = 0; file < 8; ++file)
|
||||
for (int occ = 0; occ < 256; ++occ)
|
||||
table[file][occ] = u8(sliding_attack(ROOK, Square(file), occ));
|
||||
return table;
|
||||
}();
|
||||
|
||||
static void init_dual_magics(DualMagic magics[]) {
|
||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||
{
|
||||
DualMagic& m = magics[s];
|
||||
m.maskFile = line_mask(s, NORTH, SOUTH);
|
||||
m.maskDiag = line_mask(s, NORTH_EAST, SOUTH_WEST);
|
||||
m.maskNone = 0;
|
||||
m.maskAntidiag = line_mask(s, NORTH_WEST, SOUTH_EAST);
|
||||
m.r = square_bb(s) * 2;
|
||||
m.rr = square_bb(Square(63 - int(s))) * 2;
|
||||
m.rankAttacksLookup = RankAttacks[int(file_of(s))].data();
|
||||
m.shift = 8 * int(rank_of(s));
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
namespace {
|
||||
void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) {
|
||||
|
||||
int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
|
||||
{728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
|
||||
|
||||
Bitboard occupancy[4096];
|
||||
int epoch[4096] = {}, cnt = 0;
|
||||
Bitboard reference[4096] = {};
|
||||
int size = 0;
|
||||
|
||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||
{
|
||||
Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));
|
||||
|
||||
Magic& m = magics[s][pt - BISHOP];
|
||||
Bitboard attacks = sliding_attack(pt, s, 0);
|
||||
m.mask = attacks & ~edges;
|
||||
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
|
||||
m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size;
|
||||
size = 0;
|
||||
|
||||
Bitboard b = 0;
|
||||
do
|
||||
{
|
||||
occupancy[size] = b;
|
||||
reference[size] = sliding_attack(pt, s, b);
|
||||
|
||||
size++;
|
||||
b = (b - m.mask) & m.mask;
|
||||
} while (b);
|
||||
|
||||
PRNG rng(seeds[Is64Bit][rank_of(s)]);
|
||||
|
||||
for (int i = 0; i < size;)
|
||||
{
|
||||
for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;)
|
||||
m.magic = rng.sparse_rand<Bitboard>();
|
||||
|
||||
for (++cnt, i = 0; i < size; ++i)
|
||||
{
|
||||
unsigned idx = m.index(occupancy[i]);
|
||||
|
||||
if (epoch[idx] < cnt)
|
||||
{
|
||||
epoch[idx] = cnt;
|
||||
m.attacks[idx] = reference[i];
|
||||
}
|
||||
else if (m.attacks[idx] != reference[i])
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(USE_DUAL_HYPERBOLA_QUINT) && !defined(USE_HYPERBOLA_QUINT)
|
||||
static std::array<Bitboard, 0x19000> RookTable;
|
||||
static std::array<Bitboard, 0x1480> BishopTable;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void init() {
|
||||
|
||||
#ifdef USE_HYPERBOLA_QUINT
|
||||
init_magics(Magics);
|
||||
#elif defined(USE_DUAL_HYPERBOLA_QUINT)
|
||||
init_dual_magics(DualMagics);
|
||||
#else
|
||||
init_magics(ROOK, RookTable.data(), Magics);
|
||||
init_magics(BISHOP, BishopTable.data(), Magics);
|
||||
#endif
|
||||
|
||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||
{
|
||||
for (PieceType pt : {BISHOP, ROOK})
|
||||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
||||
{
|
||||
if (PseudoAttacks[pt][s1] & s2)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef USE_DUAL_HYPERBOLA_QUINT
|
||||
const Magic& magic(Square s, PieceType pt) {
|
||||
assert((pt == BISHOP || pt == ROOK) && is_ok(s));
|
||||
return Magics[s][pt - BISHOP];
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Stockfish::Attacks
|
||||
+360
@@ -0,0 +1,360 @@
|
||||
/*
|
||||
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 ATTACKS_H_INCLUDED
|
||||
#define ATTACKS_H_INCLUDED
|
||||
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
|
||||
#include "types.h"
|
||||
#include "bitboard.h"
|
||||
|
||||
#ifdef __aarch64__
|
||||
#include <arm_acle.h>
|
||||
#define USE_HYPERBOLA_QUINT
|
||||
#elif defined(__loongarch__) && __loongarch_grlen == 64
|
||||
#define USE_HYPERBOLA_QUINT
|
||||
#elif defined(USE_AVX2)
|
||||
#include <immintrin.h>
|
||||
#define USE_DUAL_HYPERBOLA_QUINT
|
||||
#endif
|
||||
|
||||
namespace Stockfish::Attacks {
|
||||
|
||||
void init();
|
||||
|
||||
#ifdef USE_HYPERBOLA_QUINT
|
||||
|
||||
inline Bitboard reverse_bb(Bitboard bb) {
|
||||
#if __has_builtin(__builtin_bitreverse64)
|
||||
return __builtin_bitreverse64(bb);
|
||||
#else
|
||||
#ifdef __aarch64__
|
||||
#if defined(__GNUC__) && !defined(__clang__) \
|
||||
&& (__GNUC__ < 12 || (__GNUC__ == 12 && __GNUC_MINOR__ < 2))
|
||||
// no rbit in arm_acle.h
|
||||
Bitboard out;
|
||||
asm("rbit %0, %1" : "=r"(out) : "r"(bb));
|
||||
return out;
|
||||
#else
|
||||
return __rbitll(bb);
|
||||
#endif
|
||||
#else // loongarch
|
||||
Bitboard out;
|
||||
asm("bitrev.d %0, %1" : "=r"(out) : "r"(bb));
|
||||
return out;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
// Hyperbola quintessence implementation for ARM, thanks to the availability of an
|
||||
// efficient bit reversal instruction.
|
||||
// See https://www.chessprogramming.org/Hyperbola_Quintessence
|
||||
struct Magic {
|
||||
// For rooks: file attacks, rank attacks. For bishops: diagonal/antidiagonal
|
||||
Bitboard mask1, mask2;
|
||||
|
||||
Bitboard hyperbola(Square s, Bitboard occupied, Bitboard mask) const {
|
||||
Bitboard o = occupied & mask;
|
||||
Bitboard fwd = o - square_bb(s);
|
||||
Bitboard rev = reverse_bb(o) - square_bb(Square(63 - int(s)));
|
||||
return (fwd ^ reverse_bb(rev)) & mask;
|
||||
}
|
||||
|
||||
Bitboard attacks_bb(Square s, Bitboard occupied) const {
|
||||
return hyperbola(s, occupied, mask1) | hyperbola(s, occupied, mask2);
|
||||
}
|
||||
};
|
||||
|
||||
const Magic& magic(Square s, PieceType pt);
|
||||
|
||||
#elif defined(USE_DUAL_HYPERBOLA_QUINT)
|
||||
|
||||
struct alignas(32) DualMagic {
|
||||
// file, diagonal, unused, antidiagonal
|
||||
Bitboard maskFile, maskDiag, maskNone, maskAntidiag;
|
||||
// Precomputed 2 * square_bb(sq), 2 * reverse(square_bb(sq))
|
||||
Bitboard r, rr;
|
||||
|
||||
const u8* RESTRICT rankAttacksLookup;
|
||||
// 8 * rank_of(sq)
|
||||
int shift;
|
||||
|
||||
// We always compute [bishop, rook] attacks at once, then rely on
|
||||
// compiler's DCE and CSE to eliminate unneeded re-computations or extractions.
|
||||
//
|
||||
// When using hyperbola quintessence, file, diagonal and antidiagonal attacks
|
||||
// can use a byte reversal rather than a full bit reversal (because all squares
|
||||
// reside in different bytes). Rank attacks cannot. Thus, for rank attacks
|
||||
// only, we use a compact lookup table indexed by the 8 bits of the rank's occupancy.
|
||||
std::pair<Bitboard, Bitboard> both_attacks_bb(Bitboard occupied) const {
|
||||
// Byteswap within 128-bit elements
|
||||
const auto bswap = [](__m256i v) {
|
||||
return _mm256_shuffle_epi8(v, _mm256_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
|
||||
13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
|
||||
10, 11, 12, 13, 14, 15));
|
||||
};
|
||||
|
||||
// Each lane contains a mask and we follow the same HQ algorithm as
|
||||
// given above in the ARM64 code path
|
||||
const __m256i mask = _mm256_load_si256(reinterpret_cast<const __m256i*>(this));
|
||||
const __m256i rs = _mm256_set1_epi64x(r);
|
||||
const __m256i rrs = _mm256_set1_epi64x(rr);
|
||||
|
||||
__m256i o = _mm256_and_si256(mask, _mm256_set1_epi64x(occupied));
|
||||
__m256i fwd = _mm256_sub_epi64(o, rs);
|
||||
__m256i rev = bswap(_mm256_sub_epi64(bswap(o), rrs));
|
||||
__m256i result = _mm256_and_si256(_mm256_xor_si256(fwd, rev), mask);
|
||||
|
||||
// Lane 0: rook attacks (file only); lane 1: bishop attacks
|
||||
__m128i rookBishop =
|
||||
_mm_or_si128(_mm256_extracti128_si256(result, 1), _mm256_castsi256_si128(result));
|
||||
|
||||
Bitboard rowOccupancy = rankAttacksLookup[(occupied >> shift) & 0xff];
|
||||
Bitboard rankAttacks = rowOccupancy << shift;
|
||||
|
||||
// [bishop, rook]
|
||||
return {_mm_extract_epi64(rookBishop, 1), _mm_cvtsi128_si64(rookBishop) + rankAttacks};
|
||||
}
|
||||
};
|
||||
|
||||
extern DualMagic DualMagics[SQUARE_NB];
|
||||
|
||||
inline const DualMagic& dual_magic(Square s) { return DualMagics[s]; }
|
||||
|
||||
#else
|
||||
// Magic holds all magic bitboards relevant data for a single square
|
||||
struct Magic {
|
||||
Bitboard mask;
|
||||
Bitboard* attacks;
|
||||
Bitboard magic;
|
||||
unsigned shift;
|
||||
|
||||
// Compute the attack's index using the 'magic bitboards' approach
|
||||
unsigned index(Bitboard occupied) const {
|
||||
if (Is64Bit)
|
||||
return unsigned(((occupied & mask) * magic) >> shift);
|
||||
|
||||
unsigned lo = unsigned(occupied) & unsigned(mask);
|
||||
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
|
||||
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
|
||||
}
|
||||
|
||||
Bitboard attacks_bb([[maybe_unused]] Square s, Bitboard occupied) const {
|
||||
return attacks[index(occupied)];
|
||||
}
|
||||
};
|
||||
|
||||
const Magic& magic(Square s, PieceType pt);
|
||||
|
||||
#endif
|
||||
|
||||
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
inline Bitboard line_bb(Square s1, Square s2) {
|
||||
assert(is_ok(s1) && is_ok(s2));
|
||||
return LineBB[s1][s2];
|
||||
}
|
||||
|
||||
inline Bitboard between_bb(Square s1, Square s2) {
|
||||
assert(is_ok(s1) && is_ok(s2));
|
||||
return BetweenBB[s1][s2];
|
||||
}
|
||||
|
||||
inline Bitboard ray_pass_bb(Square s1, Square s2) {
|
||||
assert(is_ok(s1) && is_ok(s2));
|
||||
return RayPassBB[s1][s2];
|
||||
}
|
||||
|
||||
// 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, dest = 0;
|
||||
constexpr Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
|
||||
constexpr Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
|
||||
|
||||
for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
|
||||
{
|
||||
Square s = sq;
|
||||
while ((dest = safe_destination(s, d)))
|
||||
{
|
||||
attacks |= dest;
|
||||
s += d;
|
||||
if (occupied & dest)
|
||||
{
|
||||
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] = pseudo_attacks(KING, s1);
|
||||
attacks[KNIGHT][s1] = pseudo_attacks(KNIGHT, s1);
|
||||
attacks[QUEEN][s1] = attacks[BISHOP][s1] = pseudo_attacks(BISHOP, s1);
|
||||
attacks[QUEEN][s1] |= attacks[ROOK][s1] = pseudo_attacks(ROOK, s1);
|
||||
}
|
||||
|
||||
return attacks;
|
||||
}();
|
||||
|
||||
inline constexpr auto PawnPushOrAttacks = []() constexpr {
|
||||
std::array<std::array<Bitboard, SQUARE_NB>, COLOR_NB> attacks{};
|
||||
|
||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||
{
|
||||
attacks[WHITE][s1] = pawn_single_push_bb(WHITE, square_bb(s1)) | PseudoAttacks[WHITE][s1];
|
||||
attacks[BLACK][s1] = pawn_single_push_bb(BLACK, square_bb(s1)) | PseudoAttacks[BLACK][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 past an occupied square.
|
||||
template<PieceType Pt>
|
||||
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
|
||||
|
||||
assert(Pt != PAWN && is_ok(s));
|
||||
|
||||
#ifdef USE_DUAL_HYPERBOLA_QUINT
|
||||
const auto [bishop, rook] = dual_magic(s).both_attacks_bb(occupied);
|
||||
|
||||
switch (Pt)
|
||||
{
|
||||
case BISHOP :
|
||||
return bishop;
|
||||
case ROOK :
|
||||
return rook;
|
||||
case QUEEN :
|
||||
return bishop | rook;
|
||||
default :
|
||||
return PseudoAttacks[Pt][s];
|
||||
}
|
||||
#else
|
||||
switch (Pt)
|
||||
{
|
||||
case BISHOP :
|
||||
case ROOK :
|
||||
return magic(s, Pt).attacks_bb(s, occupied);
|
||||
case QUEEN :
|
||||
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
||||
default :
|
||||
return PseudoAttacks[Pt][s];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::pair<Bitboard, Bitboard> both_attacks_bb(Square s, Bitboard occupied) {
|
||||
#ifdef USE_DUAL_HYPERBOLA_QUINT
|
||||
return dual_magic(s).both_attacks_bb(occupied);
|
||||
#else
|
||||
return {attacks_bb<BISHOP>(s, occupied), attacks_bb<ROOK>(s, occupied)};
|
||||
#endif
|
||||
}
|
||||
|
||||
// Returns the attacks by the given piece
|
||||
// assuming the board is occupied according to the passed Bitboard.
|
||||
// Sliding piece attacks do not continue past 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<QUEEN>(s, occupied);
|
||||
default :
|
||||
return PseudoAttacks[pt][s];
|
||||
}
|
||||
}
|
||||
|
||||
inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) {
|
||||
return type_of(pc) == PAWN ? PseudoAttacks[color_of(pc)][s]
|
||||
: attacks_bb(type_of(pc), s, occupied);
|
||||
}
|
||||
|
||||
} // namespace Stockfish::Attacks
|
||||
|
||||
#endif // #ifndef ATTACKS_H_INCLUDED
|
||||
+5
-13
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "benchmark.h"
|
||||
#include "numa.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
@@ -64,6 +65,7 @@ const std::vector<std::string> Defaults = {
|
||||
"r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
|
||||
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
|
||||
"4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
|
||||
"1r6/1P4bk/3qr1p1/N6p/3pp2P/6R1/3Q1PP1/1R4K1 w - - 1 42",
|
||||
|
||||
// Positions with high numbers of changed threats
|
||||
"k7/2n1n3/1nbNbn2/2NbRBn1/1nbRQR2/2NBRBN1/3N1N2/7K w - - 0 1",
|
||||
@@ -482,15 +484,8 @@ BenchmarkSetup setup_benchmark(std::istream& is) {
|
||||
|
||||
float totalTime = 0;
|
||||
for (const auto& game : BenchmarkPositions)
|
||||
{
|
||||
int ply = 1;
|
||||
for (int i = 0; i < static_cast<int>(game.size()); ++i)
|
||||
{
|
||||
const float correctedTime = float(getCorrectedTime(ply));
|
||||
totalTime += correctedTime;
|
||||
ply += 1;
|
||||
}
|
||||
}
|
||||
for (usize i = 0; i < game.size(); ++i)
|
||||
totalTime += float(getCorrectedTime(int(i + 1)));
|
||||
|
||||
float timeScaleFactor = static_cast<float>(desiredTimeS * 1000) / totalTime;
|
||||
|
||||
@@ -501,11 +496,8 @@ BenchmarkSetup setup_benchmark(std::istream& is) {
|
||||
for (const std::string& fen : game)
|
||||
{
|
||||
setup.commands.emplace_back("position fen " + fen);
|
||||
|
||||
const int correctedTime = static_cast<int>(getCorrectedTime(ply) * timeScaleFactor);
|
||||
const int correctedTime = static_cast<int>(getCorrectedTime(ply++) * timeScaleFactor);
|
||||
setup.commands.emplace_back("go movetime " + std::to_string(correctedTime));
|
||||
|
||||
ply += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,31 +18,8 @@
|
||||
|
||||
#include "bitboard.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
uint8_t PopCnt16[1 << 16];
|
||||
uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||
Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
alignas(64) Magic Magics[SQUARE_NB][2];
|
||||
|
||||
namespace {
|
||||
|
||||
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 an ASCII representation of a bitboard suitable
|
||||
// to be printed to standard output. Useful for debugging.
|
||||
std::string Bitboards::pretty(Bitboard b) {
|
||||
@@ -64,126 +41,4 @@ std::string Bitboards::pretty(Bitboard b) {
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
// Initializes various bitboard tables. It is called at
|
||||
// startup and relies on global objects to be already zero-initialized.
|
||||
void Bitboards::init() {
|
||||
|
||||
for (unsigned i = 0; i < (1 << 16); ++i)
|
||||
PopCnt16[i] = uint8_t(std::bitset<16>(i).count());
|
||||
|
||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
||||
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
|
||||
|
||||
init_magics(ROOK, RookTable, Magics);
|
||||
init_magics(BISHOP, BishopTable, Magics);
|
||||
|
||||
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
|
||||
{
|
||||
for (PieceType pt : {BISHOP, ROOK})
|
||||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
||||
{
|
||||
if (PseudoAttacks[pt][s1] & s2)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
// 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
|
||||
// the so called "fancy" approach.
|
||||
void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) {
|
||||
|
||||
#ifndef USE_PEXT
|
||||
// Optimal PRNG seeds to pick the correct magics in the shortest time
|
||||
int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
|
||||
{728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
|
||||
|
||||
Bitboard occupancy[4096];
|
||||
int epoch[4096] = {}, cnt = 0;
|
||||
#endif
|
||||
Bitboard reference[4096];
|
||||
int size = 0;
|
||||
|
||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||
{
|
||||
// Board edges are not considered in the relevant occupancies
|
||||
Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));
|
||||
|
||||
// Given a square 's', the mask is the bitboard of sliding attacks from
|
||||
// 's' computed on an empty board. The index must be big enough to contain
|
||||
// all the attacks for each possible subset of the mask and so is 2 power
|
||||
// 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 = Bitboards::sliding_attack(pt, s, 0) & ~edges;
|
||||
#ifndef USE_PEXT
|
||||
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
|
||||
#endif
|
||||
// Set the offset for the attacks table of the square. We have individual
|
||||
// table sizes for each square with "Fancy Magic Bitboards".
|
||||
m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size;
|
||||
size = 0;
|
||||
|
||||
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and
|
||||
// store the corresponding sliding attack bitboard in reference[].
|
||||
Bitboard b = 0;
|
||||
do
|
||||
{
|
||||
#ifndef USE_PEXT
|
||||
occupancy[size] = b;
|
||||
#endif
|
||||
reference[size] = Bitboards::sliding_attack(pt, s, b);
|
||||
|
||||
if (HasPext)
|
||||
m.attacks[pext(b, m.mask)] = reference[size];
|
||||
|
||||
size++;
|
||||
b = (b - m.mask) & m.mask;
|
||||
} while (b);
|
||||
|
||||
#ifndef USE_PEXT
|
||||
PRNG rng(seeds[Is64Bit][rank_of(s)]);
|
||||
|
||||
// Find a magic for square 's' picking up an (almost) random number
|
||||
// until we find the one that passes the verification test.
|
||||
for (int i = 0; i < size;)
|
||||
{
|
||||
for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;)
|
||||
m.magic = rng.sparse_rand<Bitboard>();
|
||||
|
||||
// A good magic must map every possible occupancy to an index that
|
||||
// looks up the correct sliding attack in the attacks[s] database.
|
||||
// Note that we build up the database for square 's' as a side
|
||||
// effect of verifying the magic. Keep track of the attempt count
|
||||
// and save it in epoch[], little speed-up trick to avoid resetting
|
||||
// m.attacks[] after every failed attempt.
|
||||
for (++cnt, i = 0; i < size; ++i)
|
||||
{
|
||||
unsigned idx = m.index(occupancy[i]);
|
||||
|
||||
if (epoch[idx] < cnt)
|
||||
{
|
||||
epoch[idx] = cnt;
|
||||
m.attacks[idx] = reference[i];
|
||||
}
|
||||
else if (m.attacks[idx] != reference[i])
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
+66
-248
@@ -20,26 +20,31 @@
|
||||
#define BITBOARD_H_INCLUDED
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <initializer_list>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
#include "types.h"
|
||||
#include "misc.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
namespace Bitboards {
|
||||
|
||||
void init();
|
||||
std::string pretty(Bitboard b);
|
||||
|
||||
} // namespace Stockfish::Bitboards
|
||||
|
||||
#ifdef USE_AVX512
|
||||
// clang-format off
|
||||
inline 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);
|
||||
// clang-format on
|
||||
#endif
|
||||
|
||||
constexpr Bitboard FileABB = 0x0101010101010101ULL;
|
||||
constexpr Bitboard FileBBB = FileABB << 1;
|
||||
constexpr Bitboard FileCBB = FileABB << 2;
|
||||
@@ -58,45 +63,9 @@ constexpr Bitboard Rank6BB = Rank1BB << (8 * 5);
|
||||
constexpr Bitboard Rank7BB = Rank1BB << (8 * 6);
|
||||
constexpr Bitboard Rank8BB = Rank1BB << (8 * 7);
|
||||
|
||||
extern uint8_t PopCnt16[1 << 16];
|
||||
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
|
||||
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
|
||||
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
|
||||
|
||||
// Magic holds all magic bitboards relevant data for a single square
|
||||
struct Magic {
|
||||
Bitboard mask;
|
||||
Bitboard* attacks;
|
||||
#ifndef USE_PEXT
|
||||
Bitboard magic;
|
||||
unsigned shift;
|
||||
#endif
|
||||
|
||||
// Compute the attack's index using the 'magic bitboards' approach
|
||||
unsigned index(Bitboard occupied) const {
|
||||
|
||||
#ifdef USE_PEXT
|
||||
return unsigned(pext(occupied, mask));
|
||||
#else
|
||||
if (Is64Bit)
|
||||
return unsigned(((occupied & mask) * magic) >> shift);
|
||||
|
||||
unsigned lo = unsigned(occupied) & unsigned(mask);
|
||||
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
|
||||
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
|
||||
#endif
|
||||
}
|
||||
|
||||
Bitboard attacks_bb(Bitboard occupied) const { return attacks[index(occupied)]; }
|
||||
};
|
||||
|
||||
extern Magic Magics[SQUARE_NB][2];
|
||||
|
||||
constexpr Bitboard square_bb(Square s) {
|
||||
assert(is_ok(s));
|
||||
return (1ULL << s);
|
||||
return 1ULL << s;
|
||||
}
|
||||
|
||||
|
||||
@@ -155,73 +124,58 @@ constexpr Bitboard pawn_attacks_bb(Bitboard b) {
|
||||
: shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
|
||||
}
|
||||
|
||||
|
||||
// Returns a bitboard representing an entire line (from board edge
|
||||
// to board edge) that intersects the two given squares. If the given squares
|
||||
// are not on a same file/rank/diagonal, the function returns 0. For instance,
|
||||
// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal.
|
||||
inline Bitboard line_bb(Square s1, Square s2) {
|
||||
|
||||
assert(is_ok(s1) && is_ok(s2));
|
||||
return LineBB[s1][s2];
|
||||
constexpr Bitboard pawn_single_push_bb(Color c, Bitboard b) {
|
||||
return c == WHITE ? shift<NORTH>(b) : shift<SOUTH>(b);
|
||||
}
|
||||
|
||||
inline constexpr auto PawnPairBB = []() {
|
||||
std::array<Bitboard, SQUARE_NB> result{};
|
||||
for (Square s = SQ_A1; s <= SQ_H8; ++s)
|
||||
{
|
||||
Bitboard file = file_bb(s);
|
||||
Bitboard files = file | shift<EAST>(file) | shift<WEST>(file);
|
||||
result[s] = files & ~(Rank1BB | Rank8BB) & ~square_bb(s);
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
|
||||
// Returns a bitboard representing the squares in the semi-open
|
||||
// segment between the squares s1 and s2 (excluding s1 but including s2). If the
|
||||
// given squares are not on a same file/rank/diagonal, it returns s2. For instance,
|
||||
// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but
|
||||
// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick
|
||||
// allows to generate non-king evasion moves faster: the defending piece must either
|
||||
// interpose itself to cover the check or capture the checking piece.
|
||||
inline Bitboard between_bb(Square s1, Square s2) {
|
||||
// Returns the squares that can host a pawn forming a "pawn pair" with a pawn
|
||||
// on s: own file plus adjacent files, restricted to ranks 2-7, excluding s.
|
||||
// The geometry is color-independent.
|
||||
constexpr Bitboard pawn_pair_bb(Square s) { return PawnPairBB[s]; }
|
||||
|
||||
assert(is_ok(s1) && is_ok(s2));
|
||||
return BetweenBB[s1][s2];
|
||||
}
|
||||
constexpr int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
|
||||
|
||||
// distance() functions return the distance between x and y, defined as the
|
||||
// number of steps for a king in x to reach y.
|
||||
template<typename T>
|
||||
constexpr int constexpr_popcount(T v) {
|
||||
static_assert(std::is_integral_v<T>, "constexpr_popcount is undefined for non-integral types");
|
||||
|
||||
template<typename T1 = Square>
|
||||
inline int distance(Square x, Square y);
|
||||
if constexpr (sizeof(T) <= 8)
|
||||
{
|
||||
u64 b = static_cast<std::make_unsigned_t<T>>(v);
|
||||
|
||||
template<>
|
||||
inline int distance<File>(Square x, Square y) {
|
||||
return std::abs(file_of(x) - file_of(y));
|
||||
}
|
||||
b = b - ((b >> 1) & 0x5555555555555555ULL);
|
||||
b = (b & 0x3333333333333333ULL) + ((b >> 2) & 0x3333333333333333ULL);
|
||||
b = (b + (b >> 4)) & 0x0F0F0F0F0F0F0F0FULL;
|
||||
|
||||
template<>
|
||||
inline int distance<Rank>(Square x, Square y) {
|
||||
return std::abs(rank_of(x) - rank_of(y));
|
||||
}
|
||||
return static_cast<int>((b * 0x0101010101010101ULL) >> 56);
|
||||
}
|
||||
else
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
template<>
|
||||
inline int distance<Square>(Square x, Square y) {
|
||||
return SquareDistance[x][y];
|
||||
}
|
||||
for (; v; v >>= static_cast<T>(1))
|
||||
if (v & static_cast<T>(1))
|
||||
++result;
|
||||
|
||||
inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
|
||||
|
||||
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Counts the number of non-zero bits in a bitboard.
|
||||
inline int popcount(Bitboard b) {
|
||||
|
||||
#ifndef USE_POPCNT
|
||||
|
||||
std::uint16_t indices[4];
|
||||
std::memcpy(indices, &b, sizeof(b));
|
||||
return PopCnt16[indices[0]] + PopCnt16[indices[1]] + PopCnt16[indices[2]]
|
||||
+ PopCnt16[indices[3]];
|
||||
|
||||
#elif defined(_MSC_VER)
|
||||
#ifdef _MSC_VER
|
||||
|
||||
return int(_mm_popcnt_u64(b));
|
||||
|
||||
@@ -232,6 +186,17 @@ inline int popcount(Bitboard b) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline constexpr int lsb_index64[64] = {
|
||||
0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42,
|
||||
21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43,
|
||||
31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63};
|
||||
|
||||
constexpr int constexpr_lsb(u64 bb) {
|
||||
assert(bb != 0);
|
||||
constexpr u64 debruijn64 = 0x03F79D71B4CB0A89ULL;
|
||||
return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58];
|
||||
}
|
||||
|
||||
// Returns the least significant bit in a non-zero bitboard.
|
||||
inline Square lsb(Bitboard b) {
|
||||
assert(b);
|
||||
@@ -252,12 +217,12 @@ inline Square lsb(Bitboard b) {
|
||||
|
||||
if (b & 0xffffffff)
|
||||
{
|
||||
_BitScanForward(&idx, int32_t(b));
|
||||
_BitScanForward(&idx, i32(b));
|
||||
return Square(idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
_BitScanForward(&idx, int32_t(b >> 32));
|
||||
_BitScanForward(&idx, i32(b >> 32));
|
||||
return Square(idx + 32);
|
||||
}
|
||||
#endif
|
||||
@@ -287,12 +252,12 @@ inline Square msb(Bitboard b) {
|
||||
|
||||
if (b >> 32)
|
||||
{
|
||||
_BitScanReverse(&idx, int32_t(b >> 32));
|
||||
_BitScanReverse(&idx, i32(b >> 32));
|
||||
return Square(idx + 32);
|
||||
}
|
||||
else
|
||||
{
|
||||
_BitScanReverse(&idx, int32_t(b));
|
||||
_BitScanReverse(&idx, i32(b));
|
||||
return Square(idx);
|
||||
}
|
||||
#endif
|
||||
@@ -316,153 +281,6 @@ 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
|
||||
|
||||
+69
-74
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <filesystem>
|
||||
#include <deque>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
@@ -33,7 +34,6 @@
|
||||
#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"
|
||||
@@ -48,9 +48,8 @@ namespace Stockfish {
|
||||
|
||||
namespace NN = Eval::NNUE;
|
||||
|
||||
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||
int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency()));
|
||||
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
|
||||
@@ -58,27 +57,26 @@ int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency()));
|
||||
// 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) : ""),
|
||||
Engine::Engine(std::optional<std::filesystem::path> path) :
|
||||
binaryDirectory(path ? CommandLine::get_binary_directory(*path) : std::filesystem::path{}),
|
||||
numaContext(NumaConfig::from_system(DefaultNumaPolicy)),
|
||||
states(new std::deque<StateInfo>(1)),
|
||||
threads(),
|
||||
networks(numaContext,
|
||||
// Heap-allocate because sizeof(NN::Networks) is large
|
||||
std::make_unique<NN::Networks>(NN::EvalFile{EvalFileDefaultNameBig, "None", ""},
|
||||
NN::EvalFile{EvalFileDefaultNameSmall, "None", ""})) {
|
||||
networkFile{std::nullopt, ""},
|
||||
network(numaContext) {
|
||||
|
||||
pos.set(StartFEN, false, &states->back());
|
||||
|
||||
options.add( //
|
||||
"Debug Log File", Option("", [](const Option& o) {
|
||||
start_logger(o);
|
||||
start_logger(path_from_utf8(std::string(o)));
|
||||
return std::nullopt;
|
||||
}));
|
||||
|
||||
options.add( //
|
||||
"NumaPolicy", Option("auto", [this](const Option& o) {
|
||||
set_numa_config_from_option(o);
|
||||
if (!set_numa_config_from_option(o))
|
||||
return "NumaPolicy: invalid value '" + std::string(o) + "', keeping previous config.";
|
||||
return numa_config_information_as_string() + "\n"
|
||||
+ thread_allocation_information_as_string();
|
||||
}));
|
||||
@@ -136,30 +134,27 @@ Engine::Engine(std::optional<std::string> path) :
|
||||
options.add("SyzygyProbeLimit", Option(7, 0, 7));
|
||||
|
||||
options.add( //
|
||||
"EvalFile", Option(EvalFileDefaultNameBig, [this](const Option& o) {
|
||||
load_big_network(o);
|
||||
"EvalFile", Option(EvalFileDefaultName, [this](const Option& o) {
|
||||
load_network(path_from_utf8(std::string(o)));
|
||||
return std::nullopt;
|
||||
}));
|
||||
|
||||
options.add( //
|
||||
"EvalFileSmall", Option(EvalFileDefaultNameSmall, [this](const Option& o) {
|
||||
load_small_network(o);
|
||||
return std::nullopt;
|
||||
}));
|
||||
|
||||
load_networks();
|
||||
network = get_default_network();
|
||||
threads.clear();
|
||||
threads.ensure_network_replicated();
|
||||
resize_threads();
|
||||
}
|
||||
|
||||
std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) {
|
||||
verify_networks();
|
||||
std::variant<u64, PositionSetError>
|
||||
Engine::perft(const std::string& fen, Depth depth, bool isChess960) {
|
||||
verify_network();
|
||||
|
||||
return Benchmark::perft(fen, depth, isChess960);
|
||||
}
|
||||
|
||||
void Engine::go(Search::LimitsType& limits) {
|
||||
assert(limits.perft == 0);
|
||||
verify_networks();
|
||||
verify_network();
|
||||
|
||||
threads.start_thinking(options, pos, states, limits);
|
||||
}
|
||||
@@ -171,7 +166,7 @@ void Engine::search_clear() {
|
||||
tt.clear(threads);
|
||||
threads.clear();
|
||||
|
||||
// @TODO wont work with multiple instances
|
||||
// TODO: does not work with multiple instances
|
||||
Tablebases::init(options["SyzygyPath"]); // Free mapped files
|
||||
}
|
||||
|
||||
@@ -191,32 +186,37 @@ void Engine::set_on_bestmove(std::function<void(std::string_view, std::string_vi
|
||||
updateContext.onBestmove = std::move(f);
|
||||
}
|
||||
|
||||
void Engine::set_on_verify_networks(std::function<void(std::string_view)>&& f) {
|
||||
onVerifyNetworks = std::move(f);
|
||||
void Engine::set_on_verify_network(std::function<void(std::string_view)>&& f) {
|
||||
onVerifyNetwork = std::move(f);
|
||||
}
|
||||
|
||||
void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); }
|
||||
|
||||
void Engine::set_position(const std::string& fen, const std::vector<std::string>& moves) {
|
||||
std::optional<PositionSetError> Engine::set_position(const std::string& fen,
|
||||
const std::vector<std::string>& moves) {
|
||||
// Drop the old state and create a new one
|
||||
states = StateListPtr(new std::deque<StateInfo>(1));
|
||||
pos.set(fen, options["UCI_Chess960"], &states->back());
|
||||
states = StateListPtr(new std::deque<StateInfo>(1));
|
||||
auto err = pos.set(fen, options["UCI_Chess960"], &states->back());
|
||||
if (err.has_value())
|
||||
return err;
|
||||
|
||||
for (const auto& move : moves)
|
||||
{
|
||||
auto m = UCIEngine::to_move(pos, move);
|
||||
|
||||
if (m == Move::none())
|
||||
break;
|
||||
return PositionSetError("Illegal move: " + move);
|
||||
|
||||
states->emplace_back();
|
||||
pos.do_move(m, states->back());
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// modifiers
|
||||
|
||||
void Engine::set_numa_config_from_option(const std::string& o) {
|
||||
bool Engine::set_numa_config_from_option(const std::string& o) {
|
||||
if (o == "auto" || o == "system")
|
||||
{
|
||||
numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy));
|
||||
@@ -232,17 +232,21 @@ void Engine::set_numa_config_from_option(const std::string& o) {
|
||||
}
|
||||
else
|
||||
{
|
||||
numaContext.set_numa_config(NumaConfig::from_string(o));
|
||||
auto parsed = NumaConfig::from_string(o);
|
||||
if (!parsed.has_value())
|
||||
return false;
|
||||
numaContext.set_numa_config(std::move(*parsed));
|
||||
}
|
||||
|
||||
// Force reallocation of threads in case affinities need to change.
|
||||
resize_threads();
|
||||
threads.ensure_network_replicated();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Engine::resize_threads() {
|
||||
threads.wait_for_search_finished();
|
||||
threads.set(numaContext.get_numa_config(), {options, threads, tt, sharedHists, networks},
|
||||
threads.set(numaContext.get_numa_config(), {options, threads, tt, sharedHists, network},
|
||||
updateContext);
|
||||
|
||||
// Reallocate the hash with the new threadpool size
|
||||
@@ -250,7 +254,7 @@ void Engine::resize_threads() {
|
||||
threads.ensure_network_replicated();
|
||||
}
|
||||
|
||||
void Engine::set_tt_size(size_t mb) {
|
||||
void Engine::set_tt_size(usize mb) {
|
||||
wait_for_search_finished();
|
||||
tt.resize(mb, threads);
|
||||
}
|
||||
@@ -259,12 +263,12 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; }
|
||||
|
||||
// network related
|
||||
|
||||
void Engine::verify_networks() const {
|
||||
networks->big.verify(options["EvalFile"], onVerifyNetworks);
|
||||
networks->small.verify(options["EvalFileSmall"], onVerifyNetworks);
|
||||
void Engine::verify_network() const {
|
||||
const auto file = path_from_utf8(std::string(options["EvalFile"]));
|
||||
network->verify(onVerifyNetwork, networkFile, file);
|
||||
|
||||
auto statuses = networks.get_status_and_errors();
|
||||
for (size_t i = 0; i < statuses.size(); ++i)
|
||||
auto statuses = network.get_status_and_errors();
|
||||
for (usize i = 0; i < statuses.size(); ++i)
|
||||
{
|
||||
const auto [status, error] = statuses[i];
|
||||
std::string message = "Network replica " + std::to_string(i + 1) + ": ";
|
||||
@@ -290,38 +294,29 @@ void Engine::verify_networks() const {
|
||||
message += " " + *error;
|
||||
}
|
||||
|
||||
onVerifyNetworks(message);
|
||||
onVerifyNetwork(message);
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::load_networks() {
|
||||
networks.modify_and_replicate([this](NN::Networks& networks_) {
|
||||
networks_.big.load(binaryDirectory, options["EvalFile"]);
|
||||
networks_.small.load(binaryDirectory, options["EvalFileSmall"]);
|
||||
});
|
||||
std::unique_ptr<Eval::NNUE::Network> Engine::get_default_network() {
|
||||
|
||||
auto network_ = std::make_unique<NN::Network>();
|
||||
|
||||
network_->load(binaryDirectory, std::filesystem::path{}, networkFile);
|
||||
|
||||
return network_;
|
||||
}
|
||||
|
||||
void Engine::load_network(const std::filesystem::path& file) {
|
||||
network.modify_and_replicate(
|
||||
[this, &file](NN::Network& network_) { network_.load(binaryDirectory, file, networkFile); });
|
||||
threads.clear();
|
||||
threads.ensure_network_replicated();
|
||||
}
|
||||
|
||||
void Engine::load_big_network(const std::string& file) {
|
||||
networks.modify_and_replicate(
|
||||
[this, &file](NN::Networks& networks_) { networks_.big.load(binaryDirectory, file); });
|
||||
threads.clear();
|
||||
threads.ensure_network_replicated();
|
||||
}
|
||||
|
||||
void Engine::load_small_network(const std::string& file) {
|
||||
networks.modify_and_replicate(
|
||||
[this, &file](NN::Networks& networks_) { networks_.small.load(binaryDirectory, file); });
|
||||
threads.clear();
|
||||
threads.ensure_network_replicated();
|
||||
}
|
||||
|
||||
void Engine::save_network(const std::pair<std::optional<std::string>, std::string> files[2]) {
|
||||
networks.modify_and_replicate([&files](NN::Networks& networks_) {
|
||||
networks_.big.save(files[0].first);
|
||||
networks_.small.save(files[1].first);
|
||||
});
|
||||
void Engine::save_network(const std::optional<std::filesystem::path>& file) {
|
||||
network.modify_and_replicate(
|
||||
[&file, this](NN::Network& network_) { network_.save(networkFile, file); });
|
||||
}
|
||||
|
||||
// utility functions
|
||||
@@ -331,9 +326,9 @@ void Engine::trace_eval() const {
|
||||
Position p;
|
||||
p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back());
|
||||
|
||||
verify_networks();
|
||||
verify_network();
|
||||
|
||||
sync_cout << "\n" << Eval::trace(p, *networks) << sync_endl;
|
||||
sync_cout << "\n" << Eval::trace(p, *network) << sync_endl;
|
||||
}
|
||||
|
||||
const OptionsMap& Engine::get_options() const { return options; }
|
||||
@@ -341,7 +336,7 @@ OptionsMap& Engine::get_options() { return options; }
|
||||
|
||||
std::string Engine::fen() const { return pos.fen(); }
|
||||
|
||||
void Engine::flip() { pos.flip(); }
|
||||
std::optional<PositionSetError> Engine::flip() { return pos.flip(); }
|
||||
|
||||
std::string Engine::visualize() const {
|
||||
std::stringstream ss;
|
||||
@@ -351,11 +346,11 @@ std::string Engine::visualize() const {
|
||||
|
||||
int Engine::get_hashfull(int maxAge) const { return tt.hashfull(maxAge); }
|
||||
|
||||
std::vector<std::pair<size_t, size_t>> Engine::get_bound_thread_count_by_numa_node() const {
|
||||
auto counts = threads.get_bound_thread_count_by_numa_node();
|
||||
const NumaConfig& cfg = numaContext.get_numa_config();
|
||||
std::vector<std::pair<size_t, size_t>> ratios;
|
||||
NumaIndex n = 0;
|
||||
std::vector<std::pair<usize, usize>> Engine::get_bound_thread_count_by_numa_node() const {
|
||||
auto counts = threads.get_bound_thread_count_by_numa_node();
|
||||
const NumaConfig& cfg = numaContext.get_numa_config();
|
||||
std::vector<std::pair<usize, usize>> ratios;
|
||||
NumaIndex n = 0;
|
||||
for (; n < counts.size(); ++n)
|
||||
ratios.emplace_back(counts[n], cfg.num_cpus_in_numa_node(n));
|
||||
if (!counts.empty())
|
||||
@@ -395,7 +390,7 @@ std::string Engine::thread_binding_information_as_string() const {
|
||||
std::string Engine::thread_allocation_information_as_string() const {
|
||||
std::stringstream ss;
|
||||
|
||||
size_t threadsSize = threads.size();
|
||||
usize threadsSize = threads.size();
|
||||
ss << "Using " << threadsSize << (threadsSize > 1 ? " threads" : " thread");
|
||||
|
||||
auto boundThreadsByNodeStr = thread_binding_information_as_string();
|
||||
|
||||
+31
-27
@@ -19,18 +19,21 @@
|
||||
#ifndef ENGINE_H_INCLUDED
|
||||
#define ENGINE_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "misc.h"
|
||||
#include "history.h"
|
||||
#include "nnue/network.h"
|
||||
#include "nnue/nnue_misc.h"
|
||||
#include "numa.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
@@ -47,7 +50,7 @@ class Engine {
|
||||
using InfoFull = Search::InfoFull;
|
||||
using InfoIter = Search::InfoIteration;
|
||||
|
||||
Engine(std::optional<std::string> path = std::nullopt);
|
||||
Engine(std::optional<std::filesystem::path> path = std::nullopt);
|
||||
|
||||
// Cannot be movable due to components holding backreferences to fields
|
||||
Engine(const Engine&) = delete;
|
||||
@@ -57,7 +60,7 @@ class Engine {
|
||||
|
||||
~Engine() { wait_for_search_finished(); }
|
||||
|
||||
std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960);
|
||||
std::variant<u64, PositionSetError> perft(const std::string& fen, Depth depth, bool isChess960);
|
||||
|
||||
// non blocking call to start searching
|
||||
void go(Search::LimitsType&);
|
||||
@@ -67,13 +70,14 @@ class Engine {
|
||||
// blocking call to wait for search to finish
|
||||
void wait_for_search_finished();
|
||||
// set a new position, moves are in UCI format
|
||||
void set_position(const std::string& fen, const std::vector<std::string>& moves);
|
||||
std::optional<PositionSetError> set_position(const std::string& fen,
|
||||
const std::vector<std::string>& moves);
|
||||
|
||||
// modifiers
|
||||
|
||||
void set_numa_config_from_option(const std::string& o);
|
||||
bool set_numa_config_from_option(const std::string& o);
|
||||
void resize_threads();
|
||||
void set_tt_size(size_t mb);
|
||||
void set_tt_size(usize mb);
|
||||
void set_ponderhit(bool);
|
||||
void search_clear();
|
||||
|
||||
@@ -81,15 +85,14 @@ class Engine {
|
||||
void set_on_update_full(std::function<void(const InfoFull&)>&&);
|
||||
void set_on_iter(std::function<void(const InfoIter&)>&&);
|
||||
void set_on_bestmove(std::function<void(std::string_view, std::string_view)>&&);
|
||||
void set_on_verify_networks(std::function<void(std::string_view)>&&);
|
||||
void set_on_verify_network(std::function<void(std::string_view)>&&);
|
||||
|
||||
// network related
|
||||
|
||||
void verify_networks() const;
|
||||
void load_networks();
|
||||
void load_big_network(const std::string& file);
|
||||
void load_small_network(const std::string& file);
|
||||
void save_network(const std::pair<std::optional<std::string>, std::string> files[2]);
|
||||
void verify_network() const;
|
||||
std::unique_ptr<Eval::NNUE::Network> get_default_network();
|
||||
void load_network(const std::filesystem::path& file);
|
||||
void save_network(const std::optional<std::filesystem::path>& file);
|
||||
|
||||
// utility functions
|
||||
|
||||
@@ -100,30 +103,31 @@ class Engine {
|
||||
|
||||
int get_hashfull(int maxAge = 0) const;
|
||||
|
||||
std::string fen() const;
|
||||
void flip();
|
||||
std::string visualize() const;
|
||||
std::vector<std::pair<size_t, size_t>> get_bound_thread_count_by_numa_node() const;
|
||||
std::string get_numa_config_as_string() const;
|
||||
std::string numa_config_information_as_string() const;
|
||||
std::string thread_allocation_information_as_string() const;
|
||||
std::string thread_binding_information_as_string() const;
|
||||
std::string fen() const;
|
||||
std::optional<PositionSetError> flip();
|
||||
std::string visualize() const;
|
||||
std::vector<std::pair<usize, usize>> get_bound_thread_count_by_numa_node() const;
|
||||
std::string get_numa_config_as_string() const;
|
||||
std::string numa_config_information_as_string() const;
|
||||
std::string thread_allocation_information_as_string() const;
|
||||
std::string thread_binding_information_as_string() const;
|
||||
|
||||
private:
|
||||
const std::string binaryDirectory;
|
||||
const std::filesystem::path binaryDirectory;
|
||||
|
||||
NumaReplicationContext numaContext;
|
||||
|
||||
Position pos;
|
||||
StateListPtr states;
|
||||
|
||||
OptionsMap options;
|
||||
ThreadPool threads;
|
||||
TranspositionTable tt;
|
||||
LazyNumaReplicatedSystemWide<Eval::NNUE::Networks> networks;
|
||||
OptionsMap options;
|
||||
ThreadPool threads;
|
||||
TranspositionTable tt;
|
||||
Eval::NNUE::EvalFile networkFile;
|
||||
LazyNumaReplicatedSystemWide<Eval::NNUE::Network> network;
|
||||
|
||||
Search::SearchManager::UpdateContext updateContext;
|
||||
std::function<void(std::string_view)> onVerifyNetworks;
|
||||
std::function<void(std::string_view)> onVerifyNetwork;
|
||||
std::map<NumaIndex, SharedHistories> sharedHists;
|
||||
};
|
||||
|
||||
|
||||
+18
-37
@@ -26,8 +26,8 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
|
||||
#include "misc.h"
|
||||
#include "nnue/network.h"
|
||||
#include "nnue/nnue_misc.h"
|
||||
#include "position.h"
|
||||
@@ -37,20 +37,9 @@
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
// Returns a static, purely materialistic evaluation of the position from
|
||||
// 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 = 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) { 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.
|
||||
Value Eval::evaluate(const Eval::NNUE::Networks& networks,
|
||||
Value Eval::evaluate(const Eval::NNUE::Network& network,
|
||||
const Position& pos,
|
||||
Eval::NNUE::AccumulatorStack& accumulators,
|
||||
Eval::NNUE::AccumulatorCaches& caches,
|
||||
@@ -58,27 +47,17 @@ 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] = network.evaluate(pos, accumulators, caches);
|
||||
|
||||
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) < 277))
|
||||
{
|
||||
std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big);
|
||||
nnue = (125 * psqt + 131 * positional) / 128;
|
||||
smallNet = false;
|
||||
}
|
||||
Value nnue = psqt + positional;
|
||||
|
||||
// Blend optimism and eval with nnue complexity
|
||||
int nnueComplexity = std::abs(psqt - positional);
|
||||
optimism += optimism * nnueComplexity / 476;
|
||||
nnue -= nnue * nnueComplexity / 18236;
|
||||
optimism += optimism * i64(nnueComplexity) / 476;
|
||||
nnue -= nnue * i64(nnueComplexity) / 18236;
|
||||
|
||||
int material = 534 * pos.count<PAWN>() + pos.non_pawn_material();
|
||||
int v = (nnue * (77871 + material) + optimism * (7191 + material)) / 77871;
|
||||
int v = (nnue * i64(77871 + material) + optimism * i64(7191 + material)) / 77871;
|
||||
|
||||
// Damp down the evaluation linearly when shuffling
|
||||
v -= v * pos.rule50_count() / 199;
|
||||
@@ -93,30 +72,32 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
|
||||
// a string (suitable for outputting to stdout) that contains the detailed
|
||||
// descriptions and values of each evaluation term. Useful for debugging.
|
||||
// Trace scores are from white's point of view
|
||||
std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) {
|
||||
std::string Eval::trace(Position& pos, const Eval::NNUE::Network& network) {
|
||||
|
||||
if (pos.checkers())
|
||||
return "Final evaluation: none (in check)";
|
||||
|
||||
auto accumulators = std::make_unique<Eval::NNUE::AccumulatorStack>();
|
||||
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(networks);
|
||||
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(network);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
|
||||
ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n';
|
||||
ss << '\n' << NNUE::trace(pos, network, *caches) << '\n';
|
||||
|
||||
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] = network.evaluate(pos, *accumulators, *caches);
|
||||
Value v = psqt + positional;
|
||||
v = pos.side_to_move() == WHITE ? v : -v;
|
||||
ss << "NNUE evaluation " << v << " (side to move, internal units)\n";
|
||||
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(network, 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, ...]";
|
||||
ss << "\n";
|
||||
|
||||
ss << "Final evaluation ";
|
||||
ss << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)";
|
||||
ss << " [with scaled NNUE, ...]\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
+4
-7
@@ -33,20 +33,17 @@ 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-c288c895ea92.nnue"
|
||||
#define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue"
|
||||
#define EvalFileDefaultName "nn-89cb98a217f7.nnue"
|
||||
|
||||
namespace NNUE {
|
||||
struct Networks;
|
||||
class Network;
|
||||
struct AccumulatorCaches;
|
||||
class AccumulatorStack;
|
||||
}
|
||||
|
||||
std::string trace(Position& pos, const Eval::NNUE::Networks& networks);
|
||||
std::string trace(Position& pos, const Eval::NNUE::Network& network);
|
||||
|
||||
int simple_eval(const Position& pos);
|
||||
bool use_smallnet(const Position& pos);
|
||||
Value evaluate(const NNUE::Networks& networks,
|
||||
Value evaluate(const NNUE::Network& network,
|
||||
const Position& pos,
|
||||
Eval::NNUE::AccumulatorStack& accumulators,
|
||||
Eval::NNUE::AccumulatorCaches& caches,
|
||||
|
||||
+32
-51
@@ -36,7 +36,7 @@
|
||||
namespace Stockfish {
|
||||
|
||||
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 UINT_16_HISTORY_SIZE = std::numeric_limits<u16>::max() + 1;
|
||||
constexpr int CORRHIST_BASE_SIZE = UINT_16_HISTORY_SIZE;
|
||||
constexpr int CORRECTION_HISTORY_LIMIT = 1024;
|
||||
constexpr int LOW_PLY_HISTORY_SIZE = 5;
|
||||
@@ -52,27 +52,20 @@ static_assert((CORRHIST_BASE_SIZE & (CORRHIST_BASE_SIZE - 1)) == 0,
|
||||
// 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, bool Atomic = false>
|
||||
template<typename T, int D, bool Shared = false>
|
||||
struct StatsEntry {
|
||||
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
|
||||
static_assert(std::is_integral_v<T> && std::is_signed_v<T>, "Not a signed integer type");
|
||||
static_assert(D > 0 && D <= std::numeric_limits<T>::max()
|
||||
&& D <= std::numeric_limits<int>::max() / D,
|
||||
"D can lead to overflows");
|
||||
|
||||
private:
|
||||
std::conditional_t<Atomic, std::atomic<T>, T> entry;
|
||||
std::conditional_t<Shared, RelaxedAtomic<T>, T> entry;
|
||||
|
||||
public:
|
||||
void operator=(const T& v) {
|
||||
if constexpr (Atomic)
|
||||
entry.store(v, std::memory_order_relaxed);
|
||||
else
|
||||
entry = v;
|
||||
}
|
||||
void operator=(const T& v) { entry = v; }
|
||||
|
||||
operator T() const {
|
||||
if constexpr (Atomic)
|
||||
return entry.load(std::memory_order_relaxed);
|
||||
else
|
||||
return entry;
|
||||
}
|
||||
operator T() const { return entry; }
|
||||
|
||||
void operator<<(int bonus) {
|
||||
// Make sure that bonus is in range [-D, D]
|
||||
@@ -89,10 +82,10 @@ enum StatsType {
|
||||
Captures
|
||||
};
|
||||
|
||||
template<typename T, int D, std::size_t... Sizes>
|
||||
template<typename T, int D, usize... Sizes>
|
||||
using Stats = MultiArray<StatsEntry<T, D>, Sizes...>;
|
||||
|
||||
template<typename T, int D, std::size_t... Sizes>
|
||||
template<typename T, int D, usize... Sizes>
|
||||
using AtomicStats = MultiArray<StatsEntry<T, D, true>, Sizes...>;
|
||||
|
||||
// DynStats is a dynamically sized array of Stats, used for thread-shared histories
|
||||
@@ -100,31 +93,31 @@ using AtomicStats = MultiArray<StatsEntry<T, D, true>, Sizes...>;
|
||||
// the per-thread allocation count of T.
|
||||
template<typename T, int SizeMultiplier>
|
||||
struct DynStats {
|
||||
explicit DynStats(size_t s) {
|
||||
explicit DynStats(usize 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;
|
||||
void clear_range(int value, usize threadIdx, usize numaTotal) {
|
||||
usize start = u64(threadIdx) * size / numaTotal;
|
||||
assert(start < size);
|
||||
size_t end = threadIdx + 1 == numaTotal ? size : uint64_t(threadIdx + 1) * size / numaTotal;
|
||||
usize end = threadIdx + 1 == numaTotal ? size : u64(threadIdx + 1) * size / numaTotal;
|
||||
|
||||
while (start < end)
|
||||
data[start++].fill(value);
|
||||
}
|
||||
size_t get_size() const { return size; }
|
||||
T& operator[](size_t index) {
|
||||
usize get_size() const { return size; }
|
||||
T& operator[](usize index) {
|
||||
assert(index < size);
|
||||
return data.get()[index];
|
||||
}
|
||||
const T& operator[](size_t index) const {
|
||||
const T& operator[](usize index) const {
|
||||
assert(index < size);
|
||||
return data.get()[index];
|
||||
}
|
||||
|
||||
private:
|
||||
size_t size;
|
||||
usize size;
|
||||
LargePagePtr<T[]> data;
|
||||
};
|
||||
|
||||
@@ -132,17 +125,17 @@ struct DynStats {
|
||||
// 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
|
||||
using ButterflyHistory = Stats<std::int16_t, 7183, COLOR_NB, UINT_16_HISTORY_SIZE>;
|
||||
using ButterflyHistory = Stats<i16, 7183, COLOR_NB, UINT_16_HISTORY_SIZE>;
|
||||
|
||||
// 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, UINT_16_HISTORY_SIZE>;
|
||||
using LowPlyHistory = Stats<i16, 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>;
|
||||
using CapturePieceToHistory = Stats<i16, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;
|
||||
|
||||
// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
|
||||
using PieceToHistory = Stats<std::int16_t, 30000, PIECE_NB, SQUARE_NB>;
|
||||
using PieceToHistory = AtomicStats<i16, 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
|
||||
@@ -150,17 +143,13 @@ using PieceToHistory = Stats<std::int16_t, 30000, PIECE_NB, SQUARE_NB>;
|
||||
using ContinuationHistory = MultiArray<PieceToHistory, PIECE_NB, SQUARE_NB>;
|
||||
|
||||
// PawnHistory is addressed by the pawn structure and a move's [piece][to]
|
||||
using PawnHistory =
|
||||
DynStats<AtomicStats<std::int16_t, 8192, PIECE_NB, SQUARE_NB>, PAWN_HISTORY_BASE_SIZE>;
|
||||
using PawnHistory = DynStats<AtomicStats<i16, 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
|
||||
// used by some search heuristics.
|
||||
// see https://www.chessprogramming.org/Static_Evaluation_Correction_History
|
||||
enum CorrHistType {
|
||||
Pawn, // By color and pawn structure
|
||||
Minor, // By color and positions of minor pieces (Knight, Bishop)
|
||||
NonPawn, // By non-pawn material positions and color
|
||||
PieceTo, // By [piece][to] move
|
||||
Continuation, // Combined history of move pairs
|
||||
};
|
||||
@@ -183,14 +172,11 @@ struct CorrectionBundle {
|
||||
namespace Detail {
|
||||
|
||||
template<CorrHistType>
|
||||
struct CorrHistTypedef {
|
||||
using type =
|
||||
DynStats<Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB>, CORRHIST_BASE_SIZE>;
|
||||
};
|
||||
struct CorrHistTypedef;
|
||||
|
||||
template<>
|
||||
struct CorrHistTypedef<PieceTo> {
|
||||
using type = Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, PIECE_NB, SQUARE_NB>;
|
||||
using type = Stats<i16, CORRECTION_HISTORY_LIMIT, PIECE_NB, SQUARE_NB>;
|
||||
};
|
||||
|
||||
template<>
|
||||
@@ -198,29 +184,23 @@ struct CorrHistTypedef<Continuation> {
|
||||
using type = MultiArray<CorrHistTypedef<PieceTo>::type, PIECE_NB, SQUARE_NB>;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct CorrHistTypedef<NonPawn> {
|
||||
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>,
|
||||
DynStats<MultiArray<CorrectionBundle<i16, 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>;
|
||||
using TTMoveHistory = StatsEntry<i16, 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) :
|
||||
SharedHistories(usize threadCount) :
|
||||
correctionHistory(threadCount),
|
||||
pawnHistory(threadCount) {
|
||||
assert((threadCount & (threadCount - 1)) == 0 && threadCount != 0);
|
||||
@@ -228,7 +208,7 @@ struct SharedHistories {
|
||||
pawnHistSizeMinus1 = pawnHistory.get_size() - 1;
|
||||
}
|
||||
|
||||
size_t get_size() const { return sizeMinus1 + 1; }
|
||||
usize get_size() const { return sizeMinus1 + 1; }
|
||||
|
||||
auto& pawn_entry(const Position& pos) {
|
||||
return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1];
|
||||
@@ -261,11 +241,12 @@ struct SharedHistories {
|
||||
}
|
||||
|
||||
UnifiedCorrectionHistory correctionHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
PawnHistory pawnHistory;
|
||||
|
||||
|
||||
private:
|
||||
size_t sizeMinus1, pawnHistSizeMinus1;
|
||||
usize sizeMinus1, pawnHistSizeMinus1;
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
+21
-3
@@ -18,8 +18,9 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "bitboard.h"
|
||||
#include "attacks.h"
|
||||
#include "misc.h"
|
||||
#include "position.h"
|
||||
#include "tune.h"
|
||||
@@ -27,13 +28,22 @@
|
||||
|
||||
using namespace Stockfish;
|
||||
|
||||
#ifdef UNIVERSAL_BINARY
|
||||
namespace Stockfish {
|
||||
|
||||
int main(int argc, char* argv[]); // silence 'no previous declaration'
|
||||
|
||||
__attribute__((used)) // keep main alive
|
||||
#endif
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
std::cout << engine_info() << std::endl;
|
||||
|
||||
Bitboards::init();
|
||||
Attacks::init();
|
||||
Position::init();
|
||||
|
||||
auto uci = std::make_unique<UCIEngine>(argc, argv);
|
||||
auto cli = CommandLine(argc, argv);
|
||||
auto uci = std::make_unique<UCIEngine>(std::move(cli));
|
||||
|
||||
Tune::init(uci->engine_options());
|
||||
|
||||
@@ -41,3 +51,11 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef UNIVERSAL_BINARY
|
||||
} // namespace Stockfish
|
||||
|
||||
#ifdef UNIVERSAL_NEEDS_MAIN_SHIM
|
||||
int main(int argc, char* argv[]) { return Stockfish::main(argc, argv); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
+71
-16
@@ -19,13 +19,19 @@
|
||||
#include "memory.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream> // std::cerr
|
||||
|
||||
#if __has_include("features.h")
|
||||
#include <features.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && !defined(__ANDROID__)
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
// IWYU pragma: no_include <bits/mman-map-flags-generic.h>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \
|
||||
@@ -45,9 +51,7 @@
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
|
||||
#include <ios> // std::hex, std::dec
|
||||
#include <iostream> // std::cerr
|
||||
#include <ostream> // std::endl
|
||||
#include <ios> // std::hex, std::dec
|
||||
#include <windows.h>
|
||||
|
||||
// The needed Windows API for processor groups could be missed from old Windows
|
||||
@@ -64,7 +68,7 @@ namespace Stockfish {
|
||||
// availability of aligned_alloc(). Memory allocated with std_aligned_alloc()
|
||||
// must be freed with std_aligned_free().
|
||||
|
||||
void* std_aligned_alloc(size_t alignment, size_t size) {
|
||||
void* std_aligned_alloc(usize alignment, usize size) {
|
||||
#if defined(_ISOC11_SOURCE)
|
||||
return aligned_alloc(alignment, size);
|
||||
#elif defined(POSIXALIGNEDALLOC)
|
||||
@@ -98,19 +102,19 @@ void std_aligned_free(void* ptr) {
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) {
|
||||
static void* aligned_large_pages_alloc_windows([[maybe_unused]] usize allocSize) {
|
||||
|
||||
return windows_try_with_large_page_priviliges(
|
||||
[&](size_t largePageSize) {
|
||||
[&](usize largePageSize) {
|
||||
// Round up size to full pages and allocate
|
||||
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
|
||||
allocSize = (allocSize + largePageSize - 1) & ~usize(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) {
|
||||
void* aligned_large_pages_alloc_with_hint(usize allocSize, bool) {
|
||||
|
||||
// Try to allocate large pages
|
||||
void* mem = aligned_large_pages_alloc_windows(allocSize);
|
||||
@@ -124,17 +128,45 @@ void* aligned_large_pages_alloc(size_t allocSize) {
|
||||
|
||||
#else
|
||||
|
||||
void* aligned_large_pages_alloc(size_t allocSize) {
|
||||
#if defined(__linux__) && defined(MAP_HUGE_SHIFT) && defined(__x86_64__)
|
||||
#define HAS_HUGE_PAGES
|
||||
|
||||
static std::map<void*, usize> huge_pages;
|
||||
static std::mutex huge_pages_mtx;
|
||||
|
||||
static void* try_huge_pages_alloc(usize allocSize) {
|
||||
usize size = ((allocSize + HugePageSize - 1) / HugePageSize) * HugePageSize;
|
||||
void* mem = mmap(NULL, size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | (30 << MAP_HUGE_SHIFT), -1, 0);
|
||||
|
||||
if (mem == MAP_FAILED)
|
||||
return nullptr;
|
||||
|
||||
std::lock_guard lg(huge_pages_mtx);
|
||||
huge_pages[mem] = size;
|
||||
return mem;
|
||||
}
|
||||
#endif // defined(__linux__) && defined(MAP_HUGE_SHIFT) && defined(__x86_64__)
|
||||
|
||||
void* aligned_large_pages_alloc_with_hint(usize allocSize, [[maybe_unused]] bool hugePageHint) {
|
||||
#ifdef HAS_HUGE_PAGES
|
||||
if (hugePageHint && allocSize >= HugePageSize)
|
||||
{
|
||||
void* mem = try_huge_pages_alloc(allocSize);
|
||||
if (mem)
|
||||
return mem;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
constexpr size_t alignment = 2 * 1024 * 1024; // 2MB page size assumed
|
||||
constexpr usize alignment = 2 * 1024 * 1024; // 2MB page size assumed
|
||||
#else
|
||||
constexpr size_t alignment = 4096; // small page size assumed
|
||||
constexpr usize alignment = 4096; // small page size assumed
|
||||
#endif
|
||||
|
||||
// Round up to multiples of alignment
|
||||
size_t size = ((allocSize + alignment - 1) / alignment) * alignment;
|
||||
void* mem = std_aligned_alloc(alignment, size);
|
||||
usize size = ((allocSize + alignment - 1) / alignment) * alignment;
|
||||
void* mem = std_aligned_alloc(alignment, size);
|
||||
#if defined(MADV_HUGEPAGE)
|
||||
madvise(mem, size, MADV_HUGEPAGE);
|
||||
#endif
|
||||
@@ -143,12 +175,16 @@ void* aligned_large_pages_alloc(size_t allocSize) {
|
||||
|
||||
#endif
|
||||
|
||||
void* aligned_large_pages_alloc(usize size) {
|
||||
return aligned_large_pages_alloc_with_hint(size, false);
|
||||
}
|
||||
|
||||
bool has_large_pages() {
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
constexpr size_t page_size = 2 * 1024 * 1024; // 2MB page size assumed
|
||||
void* mem = aligned_large_pages_alloc_windows(page_size);
|
||||
constexpr usize page_size = 2 * 1024 * 1024; // 2MB page size assumed
|
||||
void* mem = aligned_large_pages_alloc_windows(page_size);
|
||||
if (mem == nullptr)
|
||||
{
|
||||
return false;
|
||||
@@ -193,7 +229,26 @@ void aligned_large_pages_free(void* mem) {
|
||||
|
||||
#else
|
||||
|
||||
void aligned_large_pages_free(void* mem) { std_aligned_free(mem); }
|
||||
void aligned_large_pages_free(void* mem) {
|
||||
if (!mem)
|
||||
return;
|
||||
|
||||
#ifdef HAS_HUGE_PAGES
|
||||
std::lock_guard lg(huge_pages_mtx);
|
||||
if (auto it = huge_pages.find(mem); it != huge_pages.end())
|
||||
{
|
||||
if (munmap(mem, it->second) != 0)
|
||||
{
|
||||
std::cerr << "munmap failed: " << strerror(errno) << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
huge_pages.erase(it);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
std_aligned_free(mem);
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace Stockfish
|
||||
|
||||
+40
-16
@@ -20,14 +20,16 @@
|
||||
#define MEMORY_H_INCLUDED
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <cstring>
|
||||
|
||||
#include "types.h"
|
||||
#include "misc.h"
|
||||
|
||||
#if defined(_WIN64)
|
||||
|
||||
@@ -61,11 +63,14 @@ using AdjustTokenPrivileges_t =
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
void* std_aligned_alloc(size_t alignment, size_t size);
|
||||
constexpr usize HugePageSize = usize(1) << 30;
|
||||
|
||||
void* std_aligned_alloc(usize alignment, usize size);
|
||||
void std_aligned_free(void* ptr);
|
||||
|
||||
// Memory aligned by page size, min alignment: 4096 bytes
|
||||
void* aligned_large_pages_alloc(size_t size);
|
||||
void* aligned_large_pages_alloc_with_hint(usize size, bool hugePageHint);
|
||||
void* aligned_large_pages_alloc(usize size);
|
||||
void aligned_large_pages_free(void* mem);
|
||||
|
||||
bool has_large_pages();
|
||||
@@ -93,15 +98,15 @@ void memory_deleter_array(T* ptr, FREE_FUNC free_func) {
|
||||
|
||||
|
||||
// Move back on the pointer to where the size is allocated
|
||||
const size_t array_offset = std::max(sizeof(size_t), alignof(T));
|
||||
char* raw_memory = reinterpret_cast<char*>(ptr) - array_offset;
|
||||
const usize array_offset = std::max(sizeof(usize), alignof(T));
|
||||
char* raw_memory = reinterpret_cast<char*>(ptr) - array_offset;
|
||||
|
||||
if constexpr (!std::is_trivially_destructible_v<T>)
|
||||
{
|
||||
const size_t size = *reinterpret_cast<size_t*>(raw_memory);
|
||||
const usize size = *reinterpret_cast<usize*>(raw_memory);
|
||||
|
||||
// Explicitly call the destructor for each element in reverse order
|
||||
for (size_t i = size; i-- > 0;)
|
||||
for (usize i = size; i-- > 0;)
|
||||
ptr[i].~T();
|
||||
}
|
||||
|
||||
@@ -120,19 +125,19 @@ inline std::enable_if_t<!std::is_array_v<T>, T*> memory_allocator(ALLOC_FUNC all
|
||||
// Allocates memory for an array of unknown bound and places it there with placement new
|
||||
template<typename T, typename ALLOC_FUNC>
|
||||
inline std::enable_if_t<std::is_array_v<T>, std::remove_extent_t<T>*>
|
||||
memory_allocator(ALLOC_FUNC alloc_func, size_t num) {
|
||||
memory_allocator(ALLOC_FUNC alloc_func, usize num) {
|
||||
using ElementType = std::remove_extent_t<T>;
|
||||
|
||||
const size_t array_offset = std::max(sizeof(size_t), alignof(ElementType));
|
||||
const usize array_offset = std::max(sizeof(usize), alignof(ElementType));
|
||||
|
||||
// Save the array size in the memory location
|
||||
char* raw_memory =
|
||||
reinterpret_cast<char*>(alloc_func(array_offset + num * sizeof(ElementType)));
|
||||
ASSERT_ALIGNED(raw_memory, alignof(T));
|
||||
|
||||
new (raw_memory) size_t(num);
|
||||
new (raw_memory) usize(num);
|
||||
|
||||
for (size_t i = 0; i < num; ++i)
|
||||
for (usize i = 0; i < num; ++i)
|
||||
new (raw_memory + array_offset + i * sizeof(ElementType)) ElementType();
|
||||
|
||||
// Need to return the pointer at the start of the array so that
|
||||
@@ -175,7 +180,7 @@ std::enable_if_t<!std::is_array_v<T>, LargePagePtr<T>> make_unique_large_page(Ar
|
||||
|
||||
// make_unique_large_page for arrays of unknown bound
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_array_v<T>, LargePagePtr<T>> make_unique_large_page(size_t num) {
|
||||
std::enable_if_t<std::is_array_v<T>, LargePagePtr<T>> make_unique_large_page(usize num) {
|
||||
using ElementType = std::remove_extent_t<T>;
|
||||
|
||||
static_assert(alignof(ElementType) <= 4096,
|
||||
@@ -211,7 +216,7 @@ using AlignedPtr =
|
||||
// make_unique_aligned for single objects
|
||||
template<typename T, typename... Args>
|
||||
std::enable_if_t<!std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(Args&&... args) {
|
||||
const auto func = [](size_t size) { return std_aligned_alloc(alignof(T), size); };
|
||||
const auto func = [](usize size) { return std_aligned_alloc(alignof(T), size); };
|
||||
T* obj = memory_allocator<T>(func, std::forward<Args>(args)...);
|
||||
|
||||
return AlignedPtr<T>(obj);
|
||||
@@ -219,10 +224,10 @@ std::enable_if_t<!std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(Args&&.
|
||||
|
||||
// make_unique_aligned for arrays of unknown bound
|
||||
template<typename T>
|
||||
std::enable_if_t<std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(size_t num) {
|
||||
std::enable_if_t<std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(usize num) {
|
||||
using ElementType = std::remove_extent_t<T>;
|
||||
|
||||
const auto func = [](size_t size) { return std_aligned_alloc(alignof(ElementType), size); };
|
||||
const auto func = [](usize size) { return std_aligned_alloc(alignof(ElementType), size); };
|
||||
ElementType* memory = memory_allocator<T>(func, num);
|
||||
|
||||
return AlignedPtr<T>(memory);
|
||||
@@ -253,7 +258,7 @@ auto windows_try_with_large_page_priviliges([[maybe_unused]] FuncYesT&& fyes, Fu
|
||||
HANDLE hProcessToken{};
|
||||
LUID luid{};
|
||||
|
||||
const size_t largePageSize = GetLargePageMinimum();
|
||||
const usize largePageSize = GetLargePageMinimum();
|
||||
if (!largePageSize)
|
||||
return fno();
|
||||
|
||||
@@ -317,6 +322,25 @@ auto windows_try_with_large_page_priviliges([[maybe_unused]] FuncYesT&& fyes, Fu
|
||||
|
||||
#endif
|
||||
|
||||
template<typename T, typename ByteT>
|
||||
T load_as(const ByteT* buffer) {
|
||||
static_assert(std::is_trivially_copyable<T>::value, "Type must be trivially copyable");
|
||||
static_assert(sizeof(ByteT) == 1);
|
||||
|
||||
if (reinterpret_cast<uintptr_t>(buffer) % alignof(T) != 0)
|
||||
{
|
||||
assert(false);
|
||||
#ifdef __GNUC__
|
||||
__builtin_unreachable();
|
||||
#endif
|
||||
}
|
||||
|
||||
T value;
|
||||
std::memcpy(&value, buffer, sizeof(T));
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif // #ifndef MEMORY_H_INCLUDED
|
||||
|
||||
+155
-75
@@ -22,8 +22,11 @@
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
@@ -33,14 +36,25 @@
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
// Version number or dev.
|
||||
constexpr std::string_view version = "18";
|
||||
constexpr std::string_view version = "dev";
|
||||
|
||||
// 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
|
||||
@@ -83,7 +97,7 @@ class Logger {
|
||||
Tie in, out;
|
||||
|
||||
public:
|
||||
static void start(const std::string& fname) {
|
||||
static void start(const fs::path& fname) {
|
||||
|
||||
static Logger l;
|
||||
|
||||
@@ -144,6 +158,10 @@ std::string engine_version_info() {
|
||||
<< std::setw(2) << std::setfill('0') << day;
|
||||
#endif
|
||||
|
||||
#ifdef GIT_DIFFINDEX
|
||||
ss << "-m";
|
||||
#endif
|
||||
|
||||
ss << "-";
|
||||
|
||||
#ifdef GIT_SHA
|
||||
@@ -263,6 +281,12 @@ std::string compiler_info() {
|
||||
compiler += " NEON_DOTPROD";
|
||||
#elif defined(USE_NEON)
|
||||
compiler += " NEON";
|
||||
#endif
|
||||
#if defined(USE_LASX)
|
||||
compiler += " LASX";
|
||||
#endif
|
||||
#if defined(USE_LSX)
|
||||
compiler += " LSX";
|
||||
#endif
|
||||
compiler += (HasPopCnt ? " POPCNT" : "");
|
||||
|
||||
@@ -288,17 +312,17 @@ constexpr int MaxDebugSlots = 32;
|
||||
|
||||
namespace {
|
||||
|
||||
template<size_t N>
|
||||
template<usize N>
|
||||
struct DebugInfo {
|
||||
std::array<std::atomic<int64_t>, N> data = {0};
|
||||
std::array<std::atomic<i64>, N> data = {0};
|
||||
|
||||
[[nodiscard]] constexpr std::atomic<int64_t>& operator[](size_t index) {
|
||||
[[nodiscard]] constexpr std::atomic<i64>& operator[](usize index) {
|
||||
assert(index < N);
|
||||
return data[index];
|
||||
}
|
||||
|
||||
constexpr DebugInfo& operator=(const DebugInfo& other) {
|
||||
for (size_t i = 0; i < N; i++)
|
||||
for (usize i = 0; i < N; i++)
|
||||
data[i].store(other.data[i].load());
|
||||
return *this;
|
||||
}
|
||||
@@ -306,8 +330,8 @@ struct DebugInfo {
|
||||
|
||||
struct DebugExtremes: public DebugInfo<3> {
|
||||
DebugExtremes() {
|
||||
data[1] = std::numeric_limits<int64_t>::min();
|
||||
data[2] = std::numeric_limits<int64_t>::max();
|
||||
data[1] = std::numeric_limits<i64>::min();
|
||||
data[2] = std::numeric_limits<i64>::max();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -326,32 +350,32 @@ void dbg_hit_on(bool cond, int slot) {
|
||||
++hit.at(slot)[1];
|
||||
}
|
||||
|
||||
void dbg_mean_of(int64_t value, int slot) {
|
||||
void dbg_mean_of(i64 value, int slot) {
|
||||
|
||||
++mean.at(slot)[0];
|
||||
mean.at(slot)[1] += value;
|
||||
}
|
||||
|
||||
void dbg_stdev_of(int64_t value, int slot) {
|
||||
void dbg_stdev_of(i64 value, int slot) {
|
||||
|
||||
++stdev.at(slot)[0];
|
||||
stdev.at(slot)[1] += value;
|
||||
stdev.at(slot)[2] += value * value;
|
||||
}
|
||||
|
||||
void dbg_extremes_of(int64_t value, int slot) {
|
||||
void dbg_extremes_of(i64 value, int slot) {
|
||||
++extremes.at(slot)[0];
|
||||
|
||||
int64_t current_max = extremes.at(slot)[1].load();
|
||||
i64 current_max = extremes.at(slot)[1].load();
|
||||
while (current_max < value && !extremes.at(slot)[1].compare_exchange_weak(current_max, value))
|
||||
{}
|
||||
|
||||
int64_t current_min = extremes.at(slot)[2].load();
|
||||
i64 current_min = extremes.at(slot)[2].load();
|
||||
while (current_min > value && !extremes.at(slot)[2].compare_exchange_weak(current_min, value))
|
||||
{}
|
||||
}
|
||||
|
||||
void dbg_correl_of(int64_t value1, int64_t value2, int slot) {
|
||||
void dbg_correl_of(i64 value1, i64 value2, int slot) {
|
||||
|
||||
++correl.at(slot)[0];
|
||||
correl.at(slot)[1] += value1;
|
||||
@@ -363,9 +387,9 @@ void dbg_correl_of(int64_t value1, int64_t value2, int slot) {
|
||||
|
||||
void dbg_print() {
|
||||
|
||||
int64_t n;
|
||||
auto E = [&n](int64_t x) { return double(x) / n; };
|
||||
auto sqr = [](double x) { return x * x; };
|
||||
i64 n;
|
||||
auto E = [&n](i64 x) { return double(x) / n; };
|
||||
auto sqr = [](double x) { return x * x; };
|
||||
|
||||
for (int i = 0; i < MaxDebugSlots; ++i)
|
||||
if ((n = hit[i][0]))
|
||||
@@ -428,40 +452,112 @@ std::ostream& operator<<(std::ostream& os, SyncCout sc) {
|
||||
void sync_cout_start() { std::cout << IO_LOCK; }
|
||||
void sync_cout_end() { std::cout << IO_UNLOCK; }
|
||||
|
||||
// Trampoline helper to avoid moving Logger to misc.h
|
||||
void start_logger(const std::string& fname) { Logger::start(fname); }
|
||||
// Hash function based on public domain MurmurHash64A, by Austin Appleby.
|
||||
u64 hash_bytes(const char* data, usize size) {
|
||||
const u64 m = 0xc6a4a7935bd1e995ull;
|
||||
const int r = 47;
|
||||
|
||||
u64 h = size * m;
|
||||
|
||||
#ifdef NO_PREFETCH
|
||||
const char* end = data + (size & ~(usize) 7);
|
||||
|
||||
void prefetch(const void*) {}
|
||||
for (const char* p = data; p != end; p += 8)
|
||||
{
|
||||
u64 k;
|
||||
std::memcpy(&k, p, sizeof(k));
|
||||
|
||||
#else
|
||||
k *= m;
|
||||
k ^= k >> r;
|
||||
k *= m;
|
||||
|
||||
void prefetch(const void* addr) {
|
||||
h ^= k;
|
||||
h *= m;
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
_mm_prefetch((char const*) addr, _MM_HINT_T0);
|
||||
#else
|
||||
__builtin_prefetch(addr);
|
||||
#endif
|
||||
if (size & 7)
|
||||
{
|
||||
u64 k = 0;
|
||||
for (int i = (size & 7) - 1; i >= 0; i--)
|
||||
k = (k << 8) | u64(end[i]);
|
||||
|
||||
h ^= k;
|
||||
h *= m;
|
||||
}
|
||||
|
||||
h ^= h >> r;
|
||||
h *= m;
|
||||
h ^= h >> r;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
#endif
|
||||
// Trampoline helper to avoid moving Logger to misc.h
|
||||
void start_logger(const fs::path& fname) { Logger::start(fname); }
|
||||
|
||||
std::string utf8_from_wstring(std::wstring_view s) {
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#define GETCWD _getcwd
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#define GETCWD getcwd
|
||||
#endif
|
||||
if (s.empty())
|
||||
return {};
|
||||
|
||||
size_t str_to_size_t(const std::string& s) {
|
||||
unsigned long long value = std::stoull(s);
|
||||
if (value > std::numeric_limits<size_t>::max())
|
||||
std::exit(EXIT_FAILURE);
|
||||
return static_cast<size_t>(value);
|
||||
int size =
|
||||
WideCharToMultiByte(CP_UTF8, 0, s.data(), int(s.size()), nullptr, 0, nullptr, nullptr);
|
||||
if (size <= 0)
|
||||
return {};
|
||||
|
||||
std::string out(size, '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, s.data(), int(s.size()), out.data(), size, nullptr, nullptr);
|
||||
return out;
|
||||
#else
|
||||
return std::string(s.begin(), s.end());
|
||||
#endif
|
||||
}
|
||||
|
||||
fs::path path_from_utf8(const std::string& path) {
|
||||
#ifdef _WIN32
|
||||
int u8len = static_cast<int>(path.size());
|
||||
int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), u8len, NULL, 0);
|
||||
|
||||
std::wstring wstr(static_cast<usize>(wlen), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, path.c_str(), u8len, wstr.data(), wlen);
|
||||
return {wstr};
|
||||
#else
|
||||
return {path};
|
||||
#endif
|
||||
}
|
||||
|
||||
CommandLine::CommandLine(int _argc, char** _argv) :
|
||||
argc(_argc),
|
||||
argv(_argv) {
|
||||
#ifdef _WIN32
|
||||
// Convert any non-ANSI characters passed on the command line to UTF-8
|
||||
int wargc = 0;
|
||||
if (LPWSTR* wargv = CommandLineToArgvW(GetCommandLineW(), &wargc))
|
||||
{
|
||||
for (int i = 0; i < wargc; ++i)
|
||||
argv_storage.push_back(utf8_from_wstring(wargv[i]));
|
||||
LocalFree(wargv);
|
||||
|
||||
for (std::string& s : argv_storage)
|
||||
argv_utf8.push_back(s.data());
|
||||
argv_utf8.push_back(nullptr);
|
||||
|
||||
argc = wargc;
|
||||
argv = argv_utf8.data();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
std::optional<usize> str_to_size_t(const std::string& s) {
|
||||
if (s.empty() || s[0] == '-')
|
||||
return std::nullopt;
|
||||
errno = 0;
|
||||
char* endptr = nullptr;
|
||||
const unsigned long long value = std::strtoull(s.c_str(), &endptr, 10);
|
||||
if (errno == ERANGE || (*endptr != '\0' && !std::isspace(*endptr))
|
||||
|| value > std::numeric_limits<usize>::max())
|
||||
return std::nullopt;
|
||||
return static_cast<usize>(value);
|
||||
}
|
||||
|
||||
std::optional<std::string> read_file_to_string(const std::string& path) {
|
||||
@@ -479,49 +575,33 @@ bool is_whitespace(std::string_view s) {
|
||||
return std::all_of(s.begin(), s.end(), [](char c) { return std::isspace(c); });
|
||||
}
|
||||
|
||||
std::string CommandLine::get_binary_directory(std::string argv0) {
|
||||
std::string pathSeparator;
|
||||
fs::path CommandLine::get_binary_directory(fs::path argv0) {
|
||||
|
||||
#ifdef _WIN32
|
||||
pathSeparator = "\\";
|
||||
#ifdef _MSC_VER
|
||||
// Under windows argv[0] may not have the extension. Also _get_pgmptr() had
|
||||
// issues in some Windows 10 versions, so check returned values carefully.
|
||||
char* pgmptr = nullptr;
|
||||
if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)
|
||||
argv0 = pgmptr;
|
||||
#endif
|
||||
#else
|
||||
pathSeparator = "/";
|
||||
// Prefer the executable path reported by Windows. Unlike _get_wpgmptr,
|
||||
// this does not depend on whether the CRT used a narrow or wide entry
|
||||
// point. Windows paths cannot exceed 32767 characters, so a fixed
|
||||
// buffer is always sufficient. Falls back to argv0 if the API fails.
|
||||
constexpr DWORD MaxPath = 32768;
|
||||
wchar_t path[MaxPath];
|
||||
|
||||
if (const DWORD length = GetModuleFileNameW(nullptr, path, MaxPath))
|
||||
argv0 = fs::path(path, path + length);
|
||||
#endif
|
||||
|
||||
// Extract the working directory
|
||||
auto workingDirectory = CommandLine::get_working_directory();
|
||||
|
||||
// Extract the binary directory path from argv0
|
||||
auto binaryDirectory = argv0;
|
||||
size_t pos = binaryDirectory.find_last_of("\\/");
|
||||
if (pos == std::string::npos)
|
||||
binaryDirectory = "." + pathSeparator;
|
||||
else
|
||||
binaryDirectory.resize(pos + 1);
|
||||
|
||||
// Pattern replacement: "./" at the start of path is replaced by the working directory
|
||||
if (binaryDirectory.find("." + pathSeparator) == 0)
|
||||
binaryDirectory.replace(0, 1, workingDirectory);
|
||||
|
||||
auto binaryDirectory = argv0.parent_path();
|
||||
if (binaryDirectory.empty())
|
||||
binaryDirectory = fs::path(".");
|
||||
return binaryDirectory;
|
||||
}
|
||||
|
||||
std::string CommandLine::get_working_directory() {
|
||||
std::string workingDirectory = "";
|
||||
char buff[40000];
|
||||
char* cwd = GETCWD(buff, 40000);
|
||||
if (cwd)
|
||||
workingDirectory = cwd;
|
||||
fs::path CommandLine::get_working_directory() { return std::filesystem::current_path(); }
|
||||
|
||||
return workingDirectory;
|
||||
void set_console_utf8() {
|
||||
#ifdef _WIN32
|
||||
SetConsoleCP(CP_UTF8);
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
+285
-154
@@ -21,67 +21,136 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#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 <filesystem>
|
||||
#include <iosfwd>
|
||||
#include <optional>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#if !defined(NO_PREFETCH) && (defined(_MSC_VER) || defined(__INTEL_COMPILER))
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
#define stringify2(x) #x
|
||||
#define stringify(x) stringify2(x)
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
using u64 = std::uint64_t;
|
||||
using u32 = std::uint32_t;
|
||||
using u16 = std::uint16_t;
|
||||
using u8 = std::uint8_t;
|
||||
|
||||
using i64 = std::int64_t;
|
||||
using i32 = std::int32_t;
|
||||
using i16 = std::int16_t;
|
||||
using i8 = std::int8_t;
|
||||
|
||||
using usize = std::size_t;
|
||||
using isize = std::ptrdiff_t;
|
||||
|
||||
#if defined(__GNUC__) && defined(IS_64BIT)
|
||||
__extension__ using u128 = unsigned __int128;
|
||||
__extension__ using i128 = signed __int128;
|
||||
#endif
|
||||
|
||||
std::string engine_version_info();
|
||||
std::string engine_info(bool to_uci = false);
|
||||
std::string compiler_info();
|
||||
|
||||
// Preloads the given address in L1/L2 cache. This is a non-blocking
|
||||
// function that doesn't stall the CPU waiting for data to be loaded from memory,
|
||||
// which can be quite slow.
|
||||
void prefetch(const void* addr);
|
||||
|
||||
void start_logger(const std::string& fname);
|
||||
|
||||
size_t str_to_size_t(const std::string& s);
|
||||
|
||||
#if defined(__linux__)
|
||||
|
||||
struct PipeDeleter {
|
||||
void operator()(FILE* file) const {
|
||||
if (file != nullptr)
|
||||
{
|
||||
pclose(file);
|
||||
}
|
||||
}
|
||||
// Prefetch hint enums for explicit call-site control.
|
||||
enum class PrefetchRw {
|
||||
READ,
|
||||
WRITE
|
||||
};
|
||||
|
||||
// NOTE: PrefetchLoc controls locality / cache level, not whether a prefetch
|
||||
// is issued. In particular, PrefetchLoc::NONE maps to a non-temporal /
|
||||
// lowest-locality prefetch (Intel: _MM_HINT_NTA, GCC/Clang: locality = 0)
|
||||
// and therefore still performs a prefetch. To completely disable
|
||||
// prefetching, define NO_PREFETCH so that prefetch() becomes a no-op.
|
||||
enum class PrefetchLoc {
|
||||
NONE, // Non-temporal / no cache locality (still issues a prefetch)
|
||||
LOW, // Low locality (e.g. T2 / L2)
|
||||
MODERATE, // Moderate locality (e.g. T1 / L1)
|
||||
HIGH // High locality (e.g. T0 / closest cache)
|
||||
};
|
||||
|
||||
// Preloads the given address into cache. This is a non-blocking
|
||||
// function that doesn't stall the CPU waiting for data to be loaded from memory,
|
||||
// which can be quite slow.
|
||||
#ifdef NO_PREFETCH
|
||||
template<PrefetchRw RW = PrefetchRw::READ, PrefetchLoc LOC = PrefetchLoc::HIGH>
|
||||
void prefetch(const void*) {}
|
||||
#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)
|
||||
|
||||
constexpr int get_intel_hint(PrefetchRw rw, PrefetchLoc loc) {
|
||||
if (rw == PrefetchRw::WRITE)
|
||||
{
|
||||
#ifdef _MM_HINT_ET0
|
||||
return _MM_HINT_ET0;
|
||||
#else
|
||||
// Fallback when write-prefetch hint is not available: use T0
|
||||
return _MM_HINT_T0;
|
||||
#endif
|
||||
}
|
||||
switch (loc)
|
||||
{
|
||||
case PrefetchLoc::NONE :
|
||||
return _MM_HINT_NTA;
|
||||
case PrefetchLoc::LOW :
|
||||
return _MM_HINT_T2;
|
||||
case PrefetchLoc::MODERATE :
|
||||
return _MM_HINT_T1;
|
||||
case PrefetchLoc::HIGH :
|
||||
return _MM_HINT_T0;
|
||||
default :
|
||||
return _MM_HINT_T0;
|
||||
}
|
||||
}
|
||||
|
||||
template<PrefetchRw RW = PrefetchRw::READ, PrefetchLoc LOC = PrefetchLoc::HIGH>
|
||||
void prefetch(const void* addr) {
|
||||
_mm_prefetch(static_cast<const char*>(addr), get_intel_hint(RW, LOC));
|
||||
}
|
||||
#else
|
||||
template<PrefetchRw RW = PrefetchRw::READ, PrefetchLoc LOC = PrefetchLoc::HIGH>
|
||||
void prefetch(const void* addr) {
|
||||
__builtin_prefetch(addr, static_cast<int>(RW), static_cast<int>(LOC));
|
||||
}
|
||||
#endif
|
||||
|
||||
void start_logger(const std::filesystem::path& fname);
|
||||
|
||||
std::optional<usize> str_to_size_t(const std::string& s);
|
||||
|
||||
std::string utf8_from_wstring(std::wstring_view s);
|
||||
std::filesystem::path path_from_utf8(const std::string& path);
|
||||
|
||||
// Reads the file as bytes.
|
||||
// Returns std::nullopt if the file does not exist.
|
||||
std::optional<std::string> read_file_to_string(const std::string& path);
|
||||
|
||||
void dbg_hit_on(bool cond, int slot = 0);
|
||||
void dbg_mean_of(int64_t value, int slot = 0);
|
||||
void dbg_stdev_of(int64_t value, int slot = 0);
|
||||
void dbg_extremes_of(int64_t value, int slot = 0);
|
||||
void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0);
|
||||
void dbg_mean_of(i64 value, int slot = 0);
|
||||
void dbg_stdev_of(i64 value, int slot = 0);
|
||||
void dbg_extremes_of(i64 value, int slot = 0);
|
||||
void dbg_correl_of(i64 value1, i64 value2, int slot = 0);
|
||||
void dbg_print();
|
||||
void dbg_clear();
|
||||
|
||||
using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds
|
||||
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
|
||||
static_assert(sizeof(TimePoint) == sizeof(i64), "TimePoint should be 64 bits");
|
||||
inline TimePoint now() {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch())
|
||||
@@ -94,10 +163,10 @@ inline std::vector<std::string_view> split(std::string_view s, std::string_view
|
||||
if (s.empty())
|
||||
return res;
|
||||
|
||||
size_t begin = 0;
|
||||
usize begin = 0;
|
||||
for (;;)
|
||||
{
|
||||
const size_t end = s.find(delimiter, begin);
|
||||
const usize end = s.find(delimiter, begin);
|
||||
if (end == std::string::npos)
|
||||
break;
|
||||
|
||||
@@ -126,25 +195,31 @@ void sync_cout_start();
|
||||
void sync_cout_end();
|
||||
|
||||
// True if and only if the binary is compiled on a little-endian machine
|
||||
static inline const std::uint16_t Le = 1;
|
||||
static inline const bool IsLittleEndian = *reinterpret_cast<const char*>(&Le) == 1;
|
||||
static inline const u16 Le = 1;
|
||||
static inline const bool IsLittleEndian = *reinterpret_cast<const char*>(&Le) == 1;
|
||||
|
||||
|
||||
template<typename T, std::size_t MaxSize>
|
||||
template<typename T, usize MaxSize>
|
||||
class ValueList {
|
||||
|
||||
public:
|
||||
std::size_t size() const { return size_; }
|
||||
int ssize() const { return int(size_); }
|
||||
void push_back(const T& value) {
|
||||
usize size() const { return size_; }
|
||||
int ssize() const { return int(size_); }
|
||||
void push_back(const T& value) {
|
||||
assert(size_ < MaxSize);
|
||||
values_[size_++] = value;
|
||||
}
|
||||
// pushes back value if value < max
|
||||
void push_back_if_lt(const T& value, const T& max) {
|
||||
assert(size_ < MaxSize);
|
||||
values_[size_] = value;
|
||||
size_ += (value < max);
|
||||
}
|
||||
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* make_space(usize count) {
|
||||
T* result = &values_[size_];
|
||||
size_ += count;
|
||||
assert(size_ <= MaxSize);
|
||||
@@ -152,22 +227,22 @@ class ValueList {
|
||||
}
|
||||
|
||||
private:
|
||||
T values_[MaxSize];
|
||||
std::size_t size_ = 0;
|
||||
T values_[MaxSize];
|
||||
usize size_ = 0;
|
||||
};
|
||||
|
||||
|
||||
template<typename T, std::size_t Size, std::size_t... Sizes>
|
||||
template<typename T, usize Size, usize... Sizes>
|
||||
class MultiArray;
|
||||
|
||||
namespace Detail {
|
||||
|
||||
template<typename T, std::size_t Size, std::size_t... Sizes>
|
||||
template<typename T, usize Size, usize... Sizes>
|
||||
struct MultiArrayHelper {
|
||||
using ChildType = MultiArray<T, Sizes...>;
|
||||
};
|
||||
|
||||
template<typename T, std::size_t Size>
|
||||
template<typename T, usize Size>
|
||||
struct MultiArrayHelper<T, Size> {
|
||||
using ChildType = T;
|
||||
};
|
||||
@@ -180,7 +255,7 @@ constexpr bool is_strictly_assignable_v =
|
||||
|
||||
// MultiArray is a generic N-dimensional array.
|
||||
// The template parameters (Size and Sizes) encode the dimensions of the array.
|
||||
template<typename T, std::size_t Size, std::size_t... Sizes>
|
||||
template<typename T, usize Size, usize... Sizes>
|
||||
class MultiArray {
|
||||
using ChildType = typename Detail::MultiArrayHelper<T, Size, Sizes...>::ChildType;
|
||||
using ArrayType = std::array<ChildType, Size>;
|
||||
@@ -199,11 +274,17 @@ class MultiArray {
|
||||
using reverse_iterator = typename ArrayType::reverse_iterator;
|
||||
using const_reverse_iterator = typename ArrayType::const_reverse_iterator;
|
||||
|
||||
constexpr auto& at(size_type index) noexcept { return data_.at(index); }
|
||||
constexpr const auto& at(size_type index) const noexcept { return data_.at(index); }
|
||||
constexpr auto& at(size_type index) { return data_.at(index); }
|
||||
constexpr const auto& at(size_type index) const { return data_.at(index); }
|
||||
|
||||
constexpr auto& operator[](size_type index) noexcept { return data_[index]; }
|
||||
constexpr const auto& operator[](size_type index) const noexcept { return data_[index]; }
|
||||
constexpr auto& operator[](size_type index) noexcept {
|
||||
assert(index < Size);
|
||||
return data_[index];
|
||||
}
|
||||
constexpr const auto& operator[](size_type index) const noexcept {
|
||||
assert(index < Size);
|
||||
return data_[index];
|
||||
}
|
||||
|
||||
constexpr auto& front() noexcept { return data_.front(); }
|
||||
constexpr const auto& front() const noexcept { return data_.front(); }
|
||||
@@ -247,6 +328,102 @@ class MultiArray {
|
||||
constexpr void swap(MultiArray<T, Size, Sizes...>& other) noexcept { data_.swap(other.data_); }
|
||||
};
|
||||
|
||||
// Wrapper around std::atomic<T> which uses relaxed accesses or plain
|
||||
// accesses, depending on the config. Intended use is e.g. wasm where
|
||||
// the overhead of atomic instructions can be significant, and we only
|
||||
// require non-tearing for the updates, while ensuring we use relaxed
|
||||
// accesses otherwise.
|
||||
template<typename T>
|
||||
class RelaxedAtomic {
|
||||
static constexpr bool UseAtomic =
|
||||
#ifdef USE_SLOPPY_ATOMICS
|
||||
!std::atomic<T>::is_always_lock_free || sizeof(T) > sizeof(usize);
|
||||
#else
|
||||
true;
|
||||
#endif
|
||||
|
||||
public:
|
||||
RelaxedAtomic() = default;
|
||||
RelaxedAtomic(T val) :
|
||||
inner(val) {}
|
||||
RelaxedAtomic(const RelaxedAtomic& a) :
|
||||
inner(static_cast<T>(a)) {}
|
||||
|
||||
T operator=(T val) {
|
||||
if constexpr (UseAtomic)
|
||||
inner.store(val, std::memory_order_relaxed);
|
||||
else
|
||||
inner = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator=(const RelaxedAtomic& a) {
|
||||
this->store(static_cast<T>(a), std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
if constexpr (UseAtomic)
|
||||
return inner.load(std::memory_order_relaxed);
|
||||
else
|
||||
return inner;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator+=(T val) {
|
||||
T res = this->load(std::memory_order_relaxed) + val;
|
||||
this->store(res, std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator++() {
|
||||
T res = this->load(std::memory_order_relaxed) + 1;
|
||||
this->store(res, std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator--() {
|
||||
T res = this->load(std::memory_order_relaxed) - 1;
|
||||
this->store(res, std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T operator++(int) {
|
||||
T val = this->load(std::memory_order_relaxed);
|
||||
this->store(val + 1, std::memory_order_relaxed);
|
||||
return val;
|
||||
}
|
||||
|
||||
T operator--(int) {
|
||||
T val = this->load(std::memory_order_relaxed);
|
||||
this->store(val - 1, std::memory_order_relaxed);
|
||||
return val;
|
||||
}
|
||||
|
||||
RelaxedAtomic& operator-=(T val) {
|
||||
T res = this->load(std::memory_order_relaxed) - val;
|
||||
this->store(res, std::memory_order_relaxed);
|
||||
return *this;
|
||||
}
|
||||
|
||||
T load(std::memory_order order) const {
|
||||
assert(order == std::memory_order_relaxed);
|
||||
if constexpr (UseAtomic)
|
||||
return inner.load(order);
|
||||
else
|
||||
return inner;
|
||||
}
|
||||
|
||||
void store(T val, std::memory_order order) {
|
||||
assert(order == std::memory_order_relaxed);
|
||||
if constexpr (UseAtomic)
|
||||
inner.store(val, order);
|
||||
else
|
||||
inner = val;
|
||||
}
|
||||
|
||||
private:
|
||||
std::conditional_t<UseAtomic, std::atomic<T>, T> inner;
|
||||
};
|
||||
|
||||
// xorshift64star Pseudo-Random Number Generator
|
||||
// This class is based on original code written and dedicated
|
||||
@@ -265,16 +442,16 @@ class MultiArray {
|
||||
|
||||
class PRNG {
|
||||
|
||||
uint64_t s;
|
||||
u64 s;
|
||||
|
||||
uint64_t rand64() {
|
||||
u64 rand64() {
|
||||
|
||||
s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
|
||||
return s * 2685821657736338717LL;
|
||||
}
|
||||
|
||||
public:
|
||||
PRNG(uint64_t seed) :
|
||||
PRNG(u64 seed) :
|
||||
s(seed) {
|
||||
assert(seed);
|
||||
}
|
||||
@@ -292,41 +469,38 @@ class PRNG {
|
||||
}
|
||||
};
|
||||
|
||||
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
||||
#if defined(__GNUC__) && defined(IS_64BIT)
|
||||
__extension__ using uint128 = unsigned __int128;
|
||||
return (uint128(a) * uint128(b)) >> 64;
|
||||
inline usize mul_hi64(u64 a, usize b) {
|
||||
#if defined(__GNUC__) && defined(IS_64BIT) && !defined(__wasm__)
|
||||
return (u128(a) * u128(b)) >> 64;
|
||||
#else
|
||||
uint64_t aL = uint32_t(a), aH = a >> 32;
|
||||
uint64_t bL = uint32_t(b), bH = b >> 32;
|
||||
uint64_t c1 = (aL * bL) >> 32;
|
||||
uint64_t c2 = aH * bL + c1;
|
||||
uint64_t c3 = aL * bH + uint32_t(c2);
|
||||
u64 aL = u32(a), aH = a >> 32;
|
||||
u64 bL = u32(b), bH = u64(b) >> 32;
|
||||
u64 c1 = (aL * bL) >> 32;
|
||||
u64 c2 = aH * bL + c1;
|
||||
u64 c3 = aL * bH + u32(c2);
|
||||
return aH * bH + (c2 >> 32) + (c3 >> 32);
|
||||
#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 T1, typename T2>
|
||||
inline constexpr T2 interpolate(T1 x, T1 x0, T1 x1, T2 y0, T2 y1) {
|
||||
assert(x0 != x1);
|
||||
return T2(y0 + (y1 - y0) * (x - x0) / (x1 - x0));
|
||||
}
|
||||
|
||||
u64 hash_bytes(const char*, usize);
|
||||
|
||||
template<typename T>
|
||||
inline std::size_t get_raw_data_hash(const T& value) {
|
||||
inline usize 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)));
|
||||
return static_cast<usize>(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;
|
||||
inline void hash_combine(usize& seed, const T& v) {
|
||||
usize x;
|
||||
// For primitive types we avoid using the default hasher, which may be
|
||||
// nondeterministic across program invocations
|
||||
if constexpr (std::is_integral<T>())
|
||||
@@ -336,90 +510,28 @@ inline void hash_combine(std::size_t& seed, const 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_;
|
||||
};
|
||||
inline u64 hash_string(const std::string& sv) { return hash_bytes(sv.data(), sv.size()); }
|
||||
|
||||
struct CommandLine {
|
||||
public:
|
||||
CommandLine(int _argc, char** _argv) :
|
||||
argc(_argc),
|
||||
argv(_argv) {}
|
||||
CommandLine(int _argc, char** _argv);
|
||||
|
||||
static std::string get_binary_directory(std::string argv0);
|
||||
static std::string get_working_directory();
|
||||
CommandLine(const CommandLine&) = delete;
|
||||
CommandLine& operator=(const CommandLine&) = delete;
|
||||
CommandLine(CommandLine&&) = default;
|
||||
CommandLine& operator=(CommandLine&&) = default;
|
||||
|
||||
static std::filesystem::path get_binary_directory(std::filesystem::path argv0);
|
||||
static std::filesystem::path get_working_directory();
|
||||
|
||||
int argc;
|
||||
char** argv;
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
std::vector<std::string> argv_storage;
|
||||
std::vector<char*> argv_utf8;
|
||||
#endif
|
||||
};
|
||||
|
||||
namespace Utility {
|
||||
@@ -435,6 +547,10 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __has_builtin
|
||||
#define __has_builtin(x) 0
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__)
|
||||
#define sf_always_inline __attribute__((always_inline))
|
||||
#elif defined(_MSC_VER)
|
||||
@@ -444,7 +560,9 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
|
||||
#define sf_always_inline
|
||||
#endif
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#if defined(__clang__)
|
||||
#define sf_assume(cond) __builtin_assume(cond)
|
||||
#elif defined(__GNUC__)
|
||||
#if __GNUC__ >= 13
|
||||
#define sf_assume(cond) __attribute__((assume(cond)))
|
||||
#else
|
||||
@@ -455,18 +573,31 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
|
||||
__builtin_unreachable(); \
|
||||
} while (0)
|
||||
#endif
|
||||
#elif defined(_MSC_VER)
|
||||
#define sf_assume(cond) __assume(cond)
|
||||
#else
|
||||
// do nothing for other compilers
|
||||
#define sf_assume(cond)
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define sf_unreachable() __builtin_unreachable()
|
||||
#elif defined(_MSC_VER)
|
||||
#define sf_unreachable() __assume(0)
|
||||
#else
|
||||
#define sf_unreachable()
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define RESTRICT __restrict__
|
||||
#elif defined(_MSC_VER)
|
||||
#define RESTRICT __restrict
|
||||
#else
|
||||
#define RESTRICT
|
||||
#endif
|
||||
|
||||
void set_console_utf8();
|
||||
|
||||
} // 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
|
||||
|
||||
+20
-42
@@ -21,6 +21,7 @@
|
||||
#include <cassert>
|
||||
#include <initializer_list>
|
||||
|
||||
#include "attacks.h"
|
||||
#include "bitboard.h"
|
||||
#include "position.h"
|
||||
|
||||
@@ -36,53 +37,30 @@ 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;
|
||||
}();
|
||||
assert(popcount(to_bb) <= 8); // <= 8 pawns per side
|
||||
|
||||
auto table = reinterpret_cast<const __m512i*>(SPLAT_TABLE.data());
|
||||
const __m128i toSquares =
|
||||
_mm_cvtepi8_epi16(_mm512_castsi512_si128(_mm512_maskz_compress_epi8(to_bb, AllSquares)));
|
||||
const __m128i fromSquares = _mm_subs_epi16(toSquares, _mm_set1_epi16(offset));
|
||||
const __m128i moves = _mm_or_si128(_mm_slli_epi16(fromSquares, Move::FromSqShift),
|
||||
_mm_slli_epi16(toSquares, Move::ToSqShift));
|
||||
|
||||
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;
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(moveList), moves);
|
||||
return moveList + popcount(to_bb);
|
||||
}
|
||||
|
||||
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;
|
||||
}();
|
||||
assert(popcount(to_bb) <= 32); // Q can attack up to 27 squares
|
||||
|
||||
__m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw());
|
||||
const __m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw());
|
||||
const __m512i toSquares =
|
||||
_mm512_cvtepi8_epi16(_mm512_castsi512_si256(_mm512_maskz_compress_epi8(to_bb, AllSquares)));
|
||||
const __m512i moves = _mm512_or_si512(fromVec, _mm512_slli_epi16(toSquares, Move::ToSqShift));
|
||||
|
||||
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;
|
||||
_mm512_storeu_si512(moveList, moves);
|
||||
return moveList + popcount(to_bb);
|
||||
}
|
||||
|
||||
#else
|
||||
@@ -193,7 +171,7 @@ Move* generate_pawn_moves(const Position& pos, Move* moveList, Bitboard target)
|
||||
if (Type == EVASIONS && (target & (pos.ep_square() + Up)))
|
||||
return moveList;
|
||||
|
||||
b1 = pawnsNotOn7 & attacks_bb<PAWN>(pos.ep_square(), Them);
|
||||
b1 = pawnsNotOn7 & Attacks::attacks_bb<PAWN>(pos.ep_square(), Them);
|
||||
|
||||
assert(b1);
|
||||
|
||||
@@ -216,7 +194,7 @@ Move* generate_moves(const Position& pos, Move* moveList, Bitboard target) {
|
||||
while (bb)
|
||||
{
|
||||
Square from = pop_lsb(bb);
|
||||
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
|
||||
Bitboard b = Attacks::attacks_bb<Pt>(from, pos.pieces()) & target;
|
||||
|
||||
moveList = splat_moves(moveList, from, b);
|
||||
}
|
||||
@@ -236,7 +214,7 @@ Move* generate_all(const Position& pos, Move* moveList) {
|
||||
// Skip generating non-king moves when in double check
|
||||
if (Type != EVASIONS || !more_than_one(pos.checkers()))
|
||||
{
|
||||
target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers()))
|
||||
target = Type == EVASIONS ? Attacks::between_bb(ksq, lsb(pos.checkers()))
|
||||
: Type == NON_EVASIONS ? ~pos.pieces(Us)
|
||||
: Type == CAPTURES ? pos.pieces(~Us)
|
||||
: ~pos.pieces(); // QUIETS
|
||||
@@ -248,7 +226,7 @@ Move* generate_all(const Position& pos, Move* moveList) {
|
||||
moveList = generate_moves<Us, QUEEN>(pos, moveList, target);
|
||||
}
|
||||
|
||||
Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
|
||||
Bitboard b = Attacks::attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
|
||||
|
||||
moveList = splat_moves(moveList, ksq, b);
|
||||
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@
|
||||
#define MOVEGEN_H_INCLUDED
|
||||
|
||||
#include <algorithm> // IWYU pragma: keep
|
||||
#include <cstddef>
|
||||
|
||||
#include "misc.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace Stockfish {
|
||||
@@ -61,7 +61,7 @@ struct MoveList {
|
||||
last(generate<T>(pos, moveList)) {}
|
||||
const Move* begin() const { return moveList; }
|
||||
const Move* end() const { return last; }
|
||||
size_t size() const { return last - moveList; }
|
||||
usize size() const { return last - moveList; }
|
||||
bool contains(Move move) const { return std::find(begin(), end(), move) != end(); }
|
||||
|
||||
private:
|
||||
|
||||
+76
-6
@@ -56,12 +56,82 @@ enum Stages {
|
||||
QCAPTURE
|
||||
};
|
||||
|
||||
#ifdef USE_AVX512
|
||||
// Load the Move, and the ExtMove value, into all lanes of 512-bit registers
|
||||
static void splat_extmove(const ExtMove& m, __m512i& move, __m512i& value) {
|
||||
move = _mm512_set1_epi32(m.raw());
|
||||
value = _mm512_set1_epi32(m.value);
|
||||
}
|
||||
|
||||
// Sorts up to 16 moves.
|
||||
struct MoveSorter {
|
||||
static constexpr int MAX_ELEMENTS = 16;
|
||||
__m512i sortedValues, sortedMoves;
|
||||
|
||||
explicit MoveSorter(const ExtMove& first) {
|
||||
splat_extmove(first, sortedMoves, sortedValues);
|
||||
|
||||
// Set the uninitialized move values to INT_MIN, so that they sort less than any other move
|
||||
sortedValues = _mm512_mask_set1_epi32(sortedValues, ~1, std::numeric_limits<int>::min());
|
||||
}
|
||||
|
||||
void insert(const ExtMove& m) {
|
||||
__m512i move, value;
|
||||
splat_extmove(m, move, value);
|
||||
|
||||
// Mask of all elements except the insertion point
|
||||
assert(m.value != std::numeric_limits<int>::min());
|
||||
const u16 expand = _kadd_mask16(_mm512_cmplt_epi32_mask(sortedValues, value), -1);
|
||||
|
||||
sortedValues = _mm512_mask_expand_epi32(value, expand, sortedValues);
|
||||
sortedMoves = _mm512_mask_expand_epi32(move, expand, sortedMoves);
|
||||
}
|
||||
|
||||
void write_sorted(ExtMove* moves, isize count) const {
|
||||
static_assert(sizeof(ExtMove) == 8);
|
||||
assert(count <= MAX_ELEMENTS);
|
||||
|
||||
// Because values and moves are stored separately, we need to reassemble the ExtMoves
|
||||
auto write = [&](int offset, const __m512i indices) {
|
||||
const __m512i extMoves = _mm512_permutex2var_epi32(sortedMoves, indices, sortedValues);
|
||||
const isize storeCount = count - offset;
|
||||
|
||||
if (storeCount > 0)
|
||||
_mm512_mask_storeu_epi64(moves + offset, (1 << storeCount) - 1, extMoves);
|
||||
};
|
||||
|
||||
write(0, _mm512_setr_epi32(0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23));
|
||||
write(8, _mm512_setr_epi32(8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31));
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
// 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) {
|
||||
ExtMove *sortedEnd = begin, *p = begin + 1;
|
||||
|
||||
for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p)
|
||||
#ifdef USE_AVX512
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
MoveSorter sorter(*begin);
|
||||
for (; p < end; ++p)
|
||||
{
|
||||
if (p->value >= limit)
|
||||
{
|
||||
if (sortedEnd - begin + 1 >= MoveSorter::MAX_ELEMENTS) // sorter full
|
||||
break;
|
||||
|
||||
sorter.insert(*p);
|
||||
*p = *++sortedEnd;
|
||||
}
|
||||
}
|
||||
sorter.write_sorted(begin, sortedEnd - begin + 1);
|
||||
// Use scalar implementation for any remaining elements
|
||||
#endif
|
||||
|
||||
for (; p < end; ++p)
|
||||
if (p->value >= limit)
|
||||
{
|
||||
ExtMove tmp = *p, *q;
|
||||
@@ -120,9 +190,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo
|
||||
|
||||
// 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.
|
||||
// with a good history. Quiet moves are ordered using the history tables.
|
||||
template<GenType Type>
|
||||
ExtMove* MovePicker::score(MoveList<Type>& ml) {
|
||||
ExtMove* MovePicker::score(const MoveList<Type>& ml) {
|
||||
|
||||
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
|
||||
|
||||
@@ -136,7 +206,7 @@ ExtMove* MovePicker::score(MoveList<Type>& ml) {
|
||||
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];
|
||||
threatByLesser[KING] = 0;
|
||||
}
|
||||
|
||||
ExtMove* it = cur;
|
||||
@@ -167,11 +237,11 @@ ExtMove* MovePicker::score(MoveList<Type>& ml) {
|
||||
m.value += (*continuationHistory[5])[pc][to];
|
||||
|
||||
// bonus for checks
|
||||
m.value += (bool(pos.check_squares(pt) & to) && pos.see_ge(m, -75)) * 16384;
|
||||
m.value += ((pos.check_squares(pt) & to) && pos.see_ge(m, -75)) * 16384;
|
||||
|
||||
// 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);
|
||||
int v = 20 * (bool(threatByLesser[pt] & from) - bool(threatByLesser[pt] & to));
|
||||
m.value += PieceValue[pt] * v;
|
||||
|
||||
|
||||
|
||||
+1
-3
@@ -55,9 +55,7 @@ class MovePicker {
|
||||
template<typename Pred>
|
||||
Move select(Pred);
|
||||
template<GenType T>
|
||||
ExtMove* score(MoveList<T>&);
|
||||
ExtMove* begin() { return cur; }
|
||||
ExtMove* end() { return endCur; }
|
||||
ExtMove* score(const MoveList<T>&);
|
||||
|
||||
const Position& pos;
|
||||
const ButterflyHistory* mainHistory;
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <initializer_list>
|
||||
#include <utility>
|
||||
|
||||
#include "../../attacks.h"
|
||||
#include "../../bitboard.h"
|
||||
#include "../../misc.h"
|
||||
#include "../../position.h"
|
||||
@@ -46,11 +47,11 @@ 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{};
|
||||
std::array<std::array<u8, SQUARE_NB>, SQUARE_NB> out{};
|
||||
|
||||
for (Square from = SQ_A1; from <= SQ_H8; ++from)
|
||||
{
|
||||
Bitboard attacks = PseudoAttacks[PT][from];
|
||||
Bitboard attacks = Attacks::PseudoAttacks[PT][from];
|
||||
|
||||
for (Square to = SQ_A1; to <= SQ_H8; ++to)
|
||||
{
|
||||
@@ -65,13 +66,13 @@ 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{};
|
||||
std::array<std::array<u8, 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];
|
||||
Bitboard attacks = Attacks::PseudoAttacks[C][from];
|
||||
|
||||
for (Square to = SQ_A1; to <= SQ_H8; ++to)
|
||||
{
|
||||
@@ -89,7 +90,7 @@ constexpr auto index_lut2_array() {
|
||||
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{};
|
||||
std::array<std::array<std::array<u8, 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>();
|
||||
@@ -128,14 +129,14 @@ constexpr auto init_threat_offsets() {
|
||||
|
||||
if (type_of(piece) != PAWN)
|
||||
{
|
||||
Bitboard attacks = PseudoAttacks[type_of(piece)][from];
|
||||
Bitboard attacks = 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));
|
||||
Bitboard attacks = (pieceIdx < 8) ? Attacks::PseudoAttacks[WHITE][from]
|
||||
: Attacks::PseudoAttacks[BLACK][from];
|
||||
cumulativePieceOffset += constexpr_popcount(attacks);
|
||||
}
|
||||
}
|
||||
@@ -153,7 +154,7 @@ constexpr auto helper_offsets = init_threat_offsets().first;
|
||||
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{};
|
||||
std::array<std::array<std::array<u32, 2>, PIECE_NB>, PIECE_NB> indices{};
|
||||
|
||||
for (Piece attacker : AllPieces)
|
||||
{
|
||||
@@ -190,13 +191,13 @@ 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;
|
||||
const i8 orientation = OrientTBL[ksq] ^ (56 * perspective);
|
||||
unsigned from_oriented = u8(from) ^ orientation;
|
||||
unsigned to_oriented = u8(to) ^ orientation;
|
||||
|
||||
std::int8_t swap = 8 * perspective;
|
||||
unsigned attacker_oriented = attacker ^ swap;
|
||||
unsigned attacked_oriented = attacked ^ swap;
|
||||
i8 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]
|
||||
@@ -206,65 +207,57 @@ inline sf_always_inline IndexType FullThreats::make_index(
|
||||
// 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();
|
||||
const Square ksq = pos.square<KING>(perspective);
|
||||
const Bitboard occupied = pos.pieces();
|
||||
const Bitboard pawnTargets = pos.pieces(KNIGHT, ROOK);
|
||||
const Bitboard minorSliderTargets = pos.pieces(PAWN, KNIGHT, BISHOP, ROOK);
|
||||
const Bitboard queenTargets = pos.pieces(PAWN, KNIGHT, BISHOP, ROOK, QUEEN);
|
||||
|
||||
for (Color color : {WHITE, BLACK})
|
||||
{
|
||||
for (PieceType pt = PAWN; pt <= KING; ++pt)
|
||||
const Color c = Color(perspective ^ color);
|
||||
|
||||
{
|
||||
Color c = Color(perspective ^ color);
|
||||
Piece attacker = make_piece(c, pt);
|
||||
Bitboard bb = pos.pieces(c, pt);
|
||||
const Piece attacker = make_piece(c, PAWN);
|
||||
const Bitboard cPawns = pos.pieces(c, PAWN);
|
||||
auto process_pawn_attacks = [&](Bitboard attacks, Direction attkDir) {
|
||||
while (attacks)
|
||||
{
|
||||
Square to = pop_lsb(attacks);
|
||||
Square from = to - attkDir;
|
||||
Piece attacked = pos.piece_on(to);
|
||||
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
|
||||
active.push_back_if_lt(index, Dimensions);
|
||||
}
|
||||
};
|
||||
|
||||
if (pt == PAWN)
|
||||
if (c == WHITE)
|
||||
{
|
||||
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);
|
||||
}
|
||||
process_pawn_attacks(shift<NORTH_EAST>(cPawns) & pawnTargets, NORTH_EAST);
|
||||
process_pawn_attacks(shift<NORTH_WEST>(cPawns) & pawnTargets, NORTH_WEST);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (bb)
|
||||
process_pawn_attacks(shift<SOUTH_WEST>(cPawns) & pawnTargets, SOUTH_WEST);
|
||||
process_pawn_attacks(shift<SOUTH_EAST>(cPawns) & pawnTargets, SOUTH_EAST);
|
||||
}
|
||||
}
|
||||
|
||||
for (PieceType pt = KNIGHT; pt < KING; ++pt)
|
||||
{
|
||||
Piece attacker = make_piece(c, pt);
|
||||
Bitboard bb = pos.pieces(c, pt);
|
||||
Bitboard targets = pt == KNIGHT || pt == QUEEN ? queenTargets : minorSliderTargets;
|
||||
while (bb)
|
||||
{
|
||||
Square from = pop_lsb(bb);
|
||||
Bitboard attacks = Attacks::attacks_bb(pt, from, occupied) & targets;
|
||||
while (attacks)
|
||||
{
|
||||
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);
|
||||
}
|
||||
Square to = pop_lsb(attacks);
|
||||
Piece attacked = pos.piece_on(to);
|
||||
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
|
||||
active.push_back_if_lt(index, Dimensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,13 +266,13 @@ void FullThreats::append_active_indices(Color perspective, const Position& pos,
|
||||
|
||||
// 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) {
|
||||
void FullThreats::append_changed_indices(Color perspective,
|
||||
Square ksq,
|
||||
const DiffType& diff,
|
||||
IndexList& removed,
|
||||
IndexList& added,
|
||||
const ThreatWeightType* prefetchBase,
|
||||
IndexType prefetchStride) {
|
||||
|
||||
for (const auto& dirty : diff.list)
|
||||
{
|
||||
@@ -289,47 +282,14 @@ void FullThreats::append_changed_indices(Color perspective,
|
||||
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);
|
||||
if (prefetchBase)
|
||||
prefetch<PrefetchRw::READ, PrefetchLoc::LOW>(reinterpret_cast<const void*>(
|
||||
reinterpret_cast<uintptr_t>(prefetchBase) + index * prefetchStride));
|
||||
insert.push_back_if_lt(index, Dimensions);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -13,12 +13,11 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//Definition of input features Simplified_Threats of NNUE evaluation function
|
||||
//Definition of input features Full_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"
|
||||
@@ -30,23 +29,22 @@ 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};
|
||||
// Pawn diagonal threats only target knights and rooks (pawn-pawn relationships
|
||||
// are handled by the PP_3Wide feature set), so pawns have 4 valid targets.
|
||||
static constexpr int numValidTargets[PIECE_NB] = {0, 4, 10, 8, 8, 10, 0, 0,
|
||||
0, 4, 10, 8, 8, 10, 0, 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;
|
||||
static constexpr u32 HashValue = 0x2e6b9d04u;
|
||||
|
||||
// Number of feature dimensions
|
||||
static constexpr IndexType Dimensions = 79856;
|
||||
static constexpr IndexType Dimensions = 59808;
|
||||
|
||||
// clang-format off
|
||||
// Orient a square according to perspective (rotates by 180 for black)
|
||||
static constexpr std::int8_t OrientTBL[SQUARE_NB] = {
|
||||
static constexpr i8 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,
|
||||
@@ -58,26 +56,18 @@ class FullThreats {
|
||||
};
|
||||
|
||||
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}
|
||||
{-1, 0, -1, 1, -1, -1},
|
||||
{ 0, 1, 2, 3, 4, -1},
|
||||
{ 0, 1, 2, 3, -1, -1},
|
||||
{ 0, 1, 2, 3, -1, -1},
|
||||
{ 0, 1, 2, 3, 4, -1},
|
||||
{-1, -1, -1, -1, -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;
|
||||
using IndexList = ValueList<u16, 256>;
|
||||
using DiffType = DirtyThreats;
|
||||
|
||||
static IndexType
|
||||
make_index(Color perspective, Piece attkr, Square from, Square to, Piece attkd, Square ksq);
|
||||
@@ -86,17 +76,13 @@ class FullThreats {
|
||||
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);
|
||||
static void append_changed_indices(Color perspective,
|
||||
Square ksq,
|
||||
const DiffType& diff,
|
||||
IndexList& removed,
|
||||
IndexList& added,
|
||||
const ThreatWeightType* prefetchBase = nullptr,
|
||||
IndexType prefetchStride = 0);
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE::Features
|
||||
|
||||
@@ -20,13 +20,67 @@
|
||||
|
||||
#include "half_ka_v2_hm.h"
|
||||
|
||||
#include "../../bitboard.h"
|
||||
#include "../../position.h"
|
||||
#include "../../types.h"
|
||||
#include "../nnue_common.h"
|
||||
|
||||
#if defined(USE_AVX512ICL)
|
||||
#include "../../bitboard.h"
|
||||
#endif
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Features {
|
||||
|
||||
#if defined(USE_AVX512ICL)
|
||||
void HalfKAv2_hm::write_indices(const std::array<Piece, SQUARE_NB>& oldPieces,
|
||||
const std::array<Piece, SQUARE_NB>& newPieces,
|
||||
Bitboard removedBB,
|
||||
Bitboard addedBB,
|
||||
Color perspective,
|
||||
Square ksq,
|
||||
IndexList& removed,
|
||||
IndexList& added) {
|
||||
|
||||
auto* write_removed = removed.make_space(popcount(removedBB));
|
||||
auto* write_added = added.make_space(popcount(addedBB));
|
||||
|
||||
const __m512i vecOldPieces = _mm512_loadu_si512(oldPieces.data());
|
||||
const __m512i vecNewPieces = _mm512_loadu_si512(newPieces.data());
|
||||
|
||||
// PieceSquareIndex and KingBuckets are multiples of 64, while s and orient
|
||||
// use only the low six bits. Therefore no carry crosses bit 6, and
|
||||
// (s ^ orient) + psi[pc] + bucket == s ^ (psi[pc] + bucket + orient),
|
||||
// allowing the orientation to be folded into the per-piece lookup offset.
|
||||
const u16 flip = 56 * perspective;
|
||||
const u16 orient = u16(OrientTBL[ksq]) ^ flip;
|
||||
const __m512i psi =
|
||||
_mm512_castsi256_si512(_mm256_loadu_si256((const __m256i*) PieceSquareIndex[perspective]));
|
||||
const __m512i psi_plus_offset =
|
||||
_mm512_add_epi16(psi, _mm512_set1_epi16(u16(KingBuckets[int(ksq) ^ flip] + orient)));
|
||||
|
||||
__m512i removed_squares = _mm512_maskz_compress_epi8(removedBB, AllSquares);
|
||||
__m512i added_squares = _mm512_maskz_compress_epi8(addedBB, AllSquares);
|
||||
__m512i removed_pieces = _mm512_maskz_compress_epi8(removedBB, vecOldPieces);
|
||||
__m512i added_pieces = _mm512_maskz_compress_epi8(addedBB, vecNewPieces);
|
||||
|
||||
removed_squares = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(removed_squares));
|
||||
added_squares = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(added_squares));
|
||||
removed_pieces = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(removed_pieces));
|
||||
added_pieces = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(added_pieces));
|
||||
|
||||
const __m512i removed_indices =
|
||||
_mm512_xor_si512(removed_squares, _mm512_permutexvar_epi16(removed_pieces, psi_plus_offset));
|
||||
const __m512i added_indices =
|
||||
_mm512_xor_si512(added_squares, _mm512_permutexvar_epi16(added_pieces, psi_plus_offset));
|
||||
|
||||
_mm512_storeu_si512(write_removed,
|
||||
_mm512_cvtepu16_epi32(_mm512_castsi512_si256(removed_indices)));
|
||||
_mm512_storeu_si512(write_removed + 16,
|
||||
_mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(removed_indices, 1)));
|
||||
_mm512_storeu_si512(write_added, _mm512_cvtepu16_epi32(_mm512_castsi512_si256(added_indices)));
|
||||
_mm512_storeu_si512(write_added + 16,
|
||||
_mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(added_indices, 1)));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Index of a feature for a given king position and another piece on some square
|
||||
|
||||
IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) {
|
||||
@@ -35,18 +89,6 @@ IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square
|
||||
+ KingBuckets[int(ksq) ^ flip];
|
||||
}
|
||||
|
||||
// Get a list of indices for active features
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// Get a list of indices for recently changed features
|
||||
|
||||
void HalfKAv2_hm::append_changed_indices(
|
||||
|
||||
@@ -16,21 +16,15 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//Definition of input features HalfKP of NNUE evaluation function
|
||||
//Definition of input features HalfKAv2_hm of NNUE evaluation function
|
||||
|
||||
#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
||||
#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../misc.h"
|
||||
#include "../../types.h"
|
||||
#include "../nnue_common.h"
|
||||
|
||||
namespace Stockfish {
|
||||
class Position;
|
||||
}
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Features {
|
||||
|
||||
// Feature HalfKAv2_hm: Combination of the position of own king and the
|
||||
@@ -54,7 +48,7 @@ class HalfKAv2_hm {
|
||||
PS_NB = 11 * SQUARE_NB
|
||||
};
|
||||
|
||||
static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
|
||||
alignas(64) static constexpr u16 PieceSquareIndex[COLOR_NB][PIECE_NB] = {
|
||||
// Convention: W - us, B - them
|
||||
// Viewed from other side, W and B are reversed
|
||||
{PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,
|
||||
@@ -63,11 +57,8 @@ class HalfKAv2_hm {
|
||||
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}};
|
||||
|
||||
public:
|
||||
// Feature name
|
||||
static constexpr const char* Name = "HalfKAv2_hm(Friend)";
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t HashValue = 0x7f234cb8u;
|
||||
static constexpr u32 HashValue = 0x7f234cb8u;
|
||||
|
||||
// Number of feature dimensions
|
||||
static constexpr IndexType Dimensions =
|
||||
@@ -106,14 +97,22 @@ class HalfKAv2_hm {
|
||||
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
|
||||
using DiffType = DirtyPiece;
|
||||
|
||||
#if defined(USE_AVX512ICL)
|
||||
// Compute all changed feature indices and write them to the given lists
|
||||
static void write_indices(const std::array<Piece, SQUARE_NB>& oldPieces,
|
||||
const std::array<Piece, SQUARE_NB>& newPieces,
|
||||
Bitboard removedBB,
|
||||
Bitboard addedBB,
|
||||
Color perspective,
|
||||
Square ksq,
|
||||
IndexList& removed,
|
||||
IndexList& added);
|
||||
#endif
|
||||
|
||||
// Index of a feature for a given king position and another piece on some square
|
||||
|
||||
static IndexType make_index(Color perspective, Square s, Piece pc, 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);
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include "pp_3wide.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
#include "full_threats.h"
|
||||
#include "../../bitboard.h"
|
||||
#include "../../misc.h"
|
||||
#include "../../position.h"
|
||||
#include "../../types.h"
|
||||
#include "../nnue_common.h"
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Features {
|
||||
|
||||
constexpr IndexType make_pawn_id(Color color, Square square) {
|
||||
assert(square >= SQ_A2 && square <= SQ_H7);
|
||||
return 48 * int(color) + square - SQ_A2;
|
||||
}
|
||||
|
||||
#ifdef USE_AVX512ICL
|
||||
static inline __m256i pp_idx_epi16(__m256i a, __m256i b) {
|
||||
const __m256i hi = _mm256_max_epu16(a, b);
|
||||
const __m256i lo = _mm256_min_epu16(a, b);
|
||||
const __m256i prod = _mm256_mullo_epi16(hi, _mm256_sub_epi16(hi, _mm256_set1_epi16(1)));
|
||||
return _mm256_add_epi16(_mm256_add_epi16(_mm256_srli_epi16(prod, 1), lo),
|
||||
_mm256_set1_epi16(i16(PP_3Wide::IndexBase)));
|
||||
}
|
||||
#endif
|
||||
|
||||
inline sf_always_inline IndexType PP_3Wide::make_index(
|
||||
Color perspective, Color color, Square from, Square to, Color pairedColor, Square ksq) {
|
||||
const i8 orientation = FullThreats::OrientTBL[ksq] ^ (56 * perspective);
|
||||
unsigned from_oriented = u8(from) ^ orientation;
|
||||
unsigned to_oriented = u8(to) ^ orientation;
|
||||
|
||||
Color color_oriented = Color(color ^ perspective);
|
||||
Color pairedColor_oriented = Color(pairedColor ^ perspective);
|
||||
|
||||
assert(from_oriented >= SQ_A2 && from_oriented <= SQ_H7);
|
||||
assert(to_oriented >= SQ_A2 && to_oriented <= SQ_H7);
|
||||
|
||||
const IndexType idA = make_pawn_id(color_oriented, Square(from_oriented));
|
||||
const IndexType idB = make_pawn_id(pairedColor_oriented, Square(to_oriented));
|
||||
const IndexType hi = std::max(idA, idB);
|
||||
const IndexType lo = std::min(idA, idB);
|
||||
|
||||
return hi * (hi - 1) / 2 + lo + IndexBase;
|
||||
}
|
||||
|
||||
void PP_3Wide::append_active_indices(Color perspective, const Position& pos, IndexList& active) {
|
||||
const Square ksq = pos.square<KING>(perspective);
|
||||
const Bitboard white = pos.pieces(WHITE, PAWN);
|
||||
const Bitboard black = pos.pieces(BLACK, PAWN);
|
||||
|
||||
Bitboard bb = white;
|
||||
while (bb)
|
||||
{
|
||||
Square from = pop_lsb(bb);
|
||||
const Bitboard band = pawn_pair_bb(from);
|
||||
for (Bitboard ww = band & bb; ww;)
|
||||
active.push_back(make_index(perspective, WHITE, from, pop_lsb(ww), WHITE, ksq));
|
||||
for (Bitboard wb = band & black; wb;)
|
||||
active.push_back(make_index(perspective, WHITE, from, pop_lsb(wb), BLACK, ksq));
|
||||
}
|
||||
|
||||
bb = black;
|
||||
while (bb)
|
||||
{
|
||||
Square from = pop_lsb(bb);
|
||||
const Bitboard band = pawn_pair_bb(from);
|
||||
for (Bitboard bbk = band & bb; bbk;)
|
||||
active.push_back(make_index(perspective, BLACK, from, pop_lsb(bbk), BLACK, ksq));
|
||||
}
|
||||
}
|
||||
|
||||
void PP_3Wide::append_changed_indices(Color perspective,
|
||||
Square ksq,
|
||||
const DiffType& diff,
|
||||
IndexList& removed,
|
||||
IndexList& added,
|
||||
[[maybe_unused]] const ThreatWeightType* prefetchBase,
|
||||
[[maybe_unused]] IndexType prefetchStride) {
|
||||
|
||||
const Bitboard whiteBefore = diff.before[WHITE];
|
||||
const Bitboard blackBefore = diff.before[BLACK];
|
||||
const Bitboard whiteAfter = diff.after[WHITE];
|
||||
const Bitboard blackAfter = diff.after[BLACK];
|
||||
|
||||
if (whiteBefore == whiteAfter && blackBefore == blackAfter)
|
||||
return;
|
||||
|
||||
#ifdef USE_AVX512ICL
|
||||
const u8 orientation = u8(FullThreats::OrientTBL[ksq]) ^ u8(56 * perspective);
|
||||
const __m512i iota = AllSquares;
|
||||
const __m512i adjusted =
|
||||
_mm512_sub_epi8(_mm512_xor_si512(iota, _mm512_set1_epi8(orientation)), _mm512_set1_epi8(8));
|
||||
|
||||
auto generate = [&](Bitboard updatedW, Bitboard updatedB, Bitboard pawnsW, Bitboard pawnsB,
|
||||
IndexList& out) {
|
||||
const Bitboard friendly = perspective == WHITE ? pawnsW : pawnsB;
|
||||
const Bitboard enemy = perspective == WHITE ? pawnsB : pawnsW;
|
||||
const __m512i ids = _mm512_mask_blend_epi8(
|
||||
friendly, _mm512_add_epi8(adjusted, _mm512_set1_epi8(48)), adjusted);
|
||||
|
||||
const Bitboard unchanged = (pawnsW | pawnsB) & ~(updatedW | updatedB);
|
||||
for (Bitboard u = updatedW | updatedB; u;)
|
||||
{
|
||||
const Square a = pop_lsb(u);
|
||||
const Bitboard partners = pawn_pair_bb(a) & (unchanged | u);
|
||||
const int n = popcount(partners);
|
||||
if (!n)
|
||||
continue;
|
||||
|
||||
const u16 colorOff = (enemy & a) ? 48 : 0;
|
||||
const u16 aId = u16(((u8(a) ^ orientation) - 8) + colorOff);
|
||||
const __m256i pids = _mm256_cvtepu8_epi16(
|
||||
_mm512_castsi512_si128(_mm512_maskz_compress_epi8(partners, ids)));
|
||||
const __m256i feats = pp_idx_epi16(_mm256_set1_epi16(aId), pids);
|
||||
|
||||
u16* w = out.make_space(n);
|
||||
_mm256_storeu_epi16(w, feats);
|
||||
}
|
||||
};
|
||||
#else
|
||||
auto generate = [&](Bitboard updatedW, Bitboard updatedB, Bitboard pawnsW, Bitboard pawnsB,
|
||||
IndexList& out) {
|
||||
auto push = [&](IndexType index) {
|
||||
if (prefetchBase)
|
||||
prefetch<PrefetchRw::READ, PrefetchLoc::LOW>(reinterpret_cast<const void*>(
|
||||
reinterpret_cast<uintptr_t>(prefetchBase) + index * prefetchStride));
|
||||
out.push_back(index);
|
||||
};
|
||||
const Bitboard unchanged = (pawnsW | pawnsB) & ~(updatedW | updatedB);
|
||||
for (Bitboard u = updatedW | updatedB; u;)
|
||||
{
|
||||
const Square a = pop_lsb(u);
|
||||
const Bitboard mask = pawn_pair_bb(a) & (unchanged | u);
|
||||
const Color aCol = (pawnsB & a) ? BLACK : WHITE;
|
||||
for (Bitboard pb = pawnsB & mask; pb;)
|
||||
push(make_index(perspective, aCol, a, pop_lsb(pb), BLACK, ksq));
|
||||
for (Bitboard pw = pawnsW & mask; pw;)
|
||||
push(make_index(perspective, aCol, a, pop_lsb(pw), WHITE, ksq));
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
generate(whiteAfter & ~whiteBefore, blackAfter & ~blackBefore, whiteAfter, blackAfter, added);
|
||||
generate(whiteBefore & ~whiteAfter, blackBefore & ~blackAfter, whiteBefore, blackBefore,
|
||||
removed);
|
||||
}
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE::Features
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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_FEATURES_PP_3WIDE_INCLUDED
|
||||
#define NNUE_FEATURES_PP_3WIDE_INCLUDED
|
||||
|
||||
#include "../../misc.h"
|
||||
#include "../../types.h"
|
||||
#include "../nnue_common.h"
|
||||
|
||||
namespace Stockfish {
|
||||
class Position;
|
||||
}
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Features {
|
||||
|
||||
class PP_3Wide {
|
||||
public:
|
||||
static constexpr u32 HashValue = 0x86f2b1ddu;
|
||||
|
||||
static constexpr IndexType PawnIds = COLOR_NB * 48;
|
||||
static constexpr IndexType Dimensions = PawnIds * (PawnIds - 1) / 2;
|
||||
|
||||
// Pawn pair feature indices are concatenated to threats, so this must equal ThreatFeatureSet::Dimensions;
|
||||
// see nnue_feature_transformer.h
|
||||
static constexpr IndexType IndexBase = 59808;
|
||||
using IndexList = ValueList<u16, 256>;
|
||||
using DiffType = DirtyPawnPairs;
|
||||
|
||||
static IndexType make_index(
|
||||
Color perspective, Color color, Square from, Square to, Color pairedColor, Square ksq);
|
||||
|
||||
static void append_active_indices(Color perspective, const Position& pos, IndexList& active);
|
||||
|
||||
static void append_changed_indices(Color perspective,
|
||||
Square ksq,
|
||||
const DiffType& diff,
|
||||
IndexList& removed,
|
||||
IndexList& added,
|
||||
const ThreatWeightType* prefetchBase = nullptr,
|
||||
IndexType prefetchStride = 0);
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE::Features
|
||||
|
||||
#endif // #ifndef NNUE_FEATURES_PP_3WIDE_INCLUDED
|
||||
@@ -24,6 +24,7 @@
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#include "../../memory.h"
|
||||
#include "../nnue_common.h"
|
||||
#include "../simd.h"
|
||||
|
||||
@@ -39,7 +40,7 @@
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Layers {
|
||||
|
||||
#if defined(USE_SSSE3) || defined(USE_NEON_DOTPROD)
|
||||
#if defined(USE_SSSE3) || defined(USE_NEON_DOTPROD) || defined(USE_LSX) || defined(USE_LASX)
|
||||
#define ENABLE_SEQ_OPT
|
||||
#endif
|
||||
|
||||
@@ -48,10 +49,8 @@ namespace Stockfish::Eval::NNUE::Layers {
|
||||
#ifndef ENABLE_SEQ_OPT
|
||||
|
||||
template<IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
|
||||
static void affine_transform_non_ssse3(std::int32_t* output,
|
||||
const std::int8_t* weights,
|
||||
const std::int32_t* biases,
|
||||
const std::uint8_t* input) {
|
||||
static void
|
||||
affine_transform_non_ssse3(i32* output, const i8* weights, const i32* biases, const u8* input) {
|
||||
#if defined(USE_SSE2) || defined(USE_NEON)
|
||||
#if defined(USE_SSE2)
|
||||
// At least a multiple of 16, with SSE2.
|
||||
@@ -95,7 +94,7 @@ static void affine_transform_non_ssse3(std::int32_t* output,
|
||||
#elif defined(USE_NEON)
|
||||
|
||||
int32x4_t sum = {biases[i]};
|
||||
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
|
||||
const auto row = reinterpret_cast<const SIMD::vec_i8x8_t*>(&weights[offset]);
|
||||
for (IndexType j = 0; j < NumChunks; ++j)
|
||||
{
|
||||
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
|
||||
@@ -106,15 +105,35 @@ static void affine_transform_non_ssse3(std::int32_t* output,
|
||||
|
||||
#endif
|
||||
}
|
||||
#elif defined(USE_RVV)
|
||||
for (IndexType i = 0; i < OutputDimensions; ++i)
|
||||
{
|
||||
const i8* row = &weights[i * PaddedInputDimensions];
|
||||
vint32m1_t vsum = __riscv_vmv_v_x_i32m1(0, __riscv_vsetvlmax_e32m1());
|
||||
|
||||
for (usize j = 0; j < InputDimensions;)
|
||||
{
|
||||
usize vl = __riscv_vsetvl_e8m4(InputDimensions - j);
|
||||
|
||||
vint8m4_t w = __riscv_vle8_v_i8m4(&row[j], vl);
|
||||
vuint8m4_t x = __riscv_vle8_v_u8m4(&input[j], vl);
|
||||
vint16m8_t prod = __riscv_vwmulsu_vv_i16m8(w, x, vl);
|
||||
|
||||
vsum = __riscv_vwredsum_vs_i16m8_i32m1(prod, vsum, vl);
|
||||
j += vl;
|
||||
}
|
||||
|
||||
output[i] = biases[i] + __riscv_vmv_x_s_i32m1_i32(vsum);
|
||||
}
|
||||
#else
|
||||
std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions);
|
||||
std::memcpy(output, biases, sizeof(i32) * OutputDimensions);
|
||||
|
||||
// Traverse weights in transpose order to take advantage of input sparsity
|
||||
for (IndexType i = 0; i < InputDimensions; ++i)
|
||||
if (input[i])
|
||||
{
|
||||
const std::int8_t* w = &weights[i];
|
||||
const int in = input[i];
|
||||
const i8* w = &weights[i];
|
||||
const int in = input[i];
|
||||
for (IndexType j = 0; j < OutputDimensions; ++j)
|
||||
output[j] += w[j * PaddedInputDimensions] * in;
|
||||
}
|
||||
@@ -123,12 +142,12 @@ static void affine_transform_non_ssse3(std::int32_t* output,
|
||||
|
||||
#endif // !ENABLE_SEQ_OPT
|
||||
|
||||
template<IndexType InDims, IndexType OutDims>
|
||||
template<IndexType InDims, IndexType OutDims, bool ScrambledInput = false>
|
||||
class AffineTransform {
|
||||
public:
|
||||
// Input/output type
|
||||
using InputType = std::uint8_t;
|
||||
using OutputType = std::int32_t;
|
||||
using InputType = u8;
|
||||
using OutputType = i32;
|
||||
|
||||
// Number of input/output dimensions
|
||||
static constexpr IndexType InputDimensions = InDims;
|
||||
@@ -142,8 +161,8 @@ class AffineTransform {
|
||||
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
||||
std::uint32_t hashValue = 0xCC03DAE4u;
|
||||
static constexpr u32 get_hash_value(u32 prevHash) {
|
||||
u32 hashValue = 0xCC03DAE4u;
|
||||
hashValue += OutputDimensions;
|
||||
hashValue ^= prevHash >> 1;
|
||||
hashValue ^= prevHash << 31;
|
||||
@@ -151,8 +170,21 @@ class AffineTransform {
|
||||
}
|
||||
|
||||
static constexpr IndexType get_weight_index_scrambled(IndexType i) {
|
||||
return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4
|
||||
+ i / PaddedInputDimensions * 4 + i % 4;
|
||||
IndexType inputIndex = i % PaddedInputDimensions;
|
||||
|
||||
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
|
||||
if constexpr (ScrambledInput)
|
||||
{
|
||||
// AVX2 packs operate independently on 128-bit lanes. Keep their interleaved output
|
||||
// order and rearrange the following layer's weights instead of issuing VPERMD.
|
||||
const IndexType block = inputIndex / 32;
|
||||
const IndexType chunk = (inputIndex % 32) / 4;
|
||||
inputIndex = block * 32 + ((chunk % 2) * 4 + chunk / 2) * 4 + inputIndex % 4;
|
||||
}
|
||||
#endif
|
||||
|
||||
return inputIndex / 4 * OutputDimensions * 4 + i / PaddedInputDimensions * 4
|
||||
+ inputIndex % 4;
|
||||
}
|
||||
|
||||
static constexpr IndexType get_weight_index(IndexType i) {
|
||||
@@ -182,8 +214,8 @@ class AffineTransform {
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
usize get_content_hash() const {
|
||||
usize 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));
|
||||
@@ -200,10 +232,12 @@ class AffineTransform {
|
||||
#if defined(USE_AVX512)
|
||||
using vec_t = __m512i;
|
||||
#define vec_set_32 _mm512_set1_epi32
|
||||
#define vec_add_32 _mm512_add_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_32 _mm256_add_epi32
|
||||
#define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32
|
||||
#elif defined(USE_SSSE3)
|
||||
using vec_t = __m128i;
|
||||
@@ -215,6 +249,16 @@ class AffineTransform {
|
||||
#define vec_add_dpbusd_32(acc, a, b) \
|
||||
SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \
|
||||
vreinterpretq_s8_s32(b))
|
||||
#elif defined(USE_LASX)
|
||||
using vec_t = __m256i;
|
||||
#define vec_set_32 __lasx_xvreplgr2vr_w
|
||||
#define vec_add_32 __lasx_xvadd_w
|
||||
#define vec_add_dpbusd_32 SIMD::lasx_m256_add_dpbusd_epi32
|
||||
#elif defined(USE_LSX)
|
||||
using vec_t = __m128i;
|
||||
#define vec_set_32 __lsx_vreplgr2vr_w
|
||||
#define vec_add_32 __lsx_vadd_w
|
||||
#define vec_add_dpbusd_32 SIMD::lsx_m128_add_dpbusd_epi32
|
||||
#endif
|
||||
|
||||
static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);
|
||||
@@ -222,26 +266,58 @@ class AffineTransform {
|
||||
static_assert(OutputDimensions % OutputSimdWidth == 0);
|
||||
|
||||
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;
|
||||
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
|
||||
constexpr IndexType NumAccums = OutputDimensions / OutputSimdWidth;
|
||||
|
||||
#if defined(USE_VNNI) || defined(USE_NEON_DOTPROD)
|
||||
constexpr IndexType NumRegs = 2 * NumAccums;
|
||||
#else
|
||||
constexpr IndexType NumRegs = NumAccums;
|
||||
#endif
|
||||
|
||||
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
|
||||
const vec_t* biasvec = reinterpret_cast<const vec_t*>(biases);
|
||||
vec_t acc[NumRegs];
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
acc[k] = biasvec[k];
|
||||
for (IndexType k = NumAccums; k < NumRegs; ++k)
|
||||
acc[k] = vec_set_32(0);
|
||||
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
IndexType i = 0;
|
||||
#if defined(USE_VNNI) || defined(USE_NEON_DOTPROD)
|
||||
for (; i < NumChunks; i += 2)
|
||||
{
|
||||
const vec_t in0 = vec_set_32(input32[i]);
|
||||
const vec_t in0 = vec_set_32(load_as<i32>(input + i * sizeof(i32)));
|
||||
const vec_t in1 = vec_set_32(load_as<i32>(input + (i + 1) * sizeof(i32)));
|
||||
const auto col0 =
|
||||
reinterpret_cast<const vec_t*>(&weights[i * OutputDimensions * 4]);
|
||||
const auto col1 =
|
||||
reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
#if defined(USE_NEON_DOTPROD)
|
||||
acc[k] = vaddq_s32(acc[k], acc[k + NumAccums]);
|
||||
#else
|
||||
acc[k] = vec_add_32(acc[k], acc[k + NumAccums]);
|
||||
#endif
|
||||
#endif
|
||||
for (; i < NumChunks; ++i)
|
||||
{
|
||||
const vec_t in0 = vec_set_32(load_as<i32>(input + i * sizeof(i32)));
|
||||
const auto col0 =
|
||||
reinterpret_cast<const vec_t*>(&weights[i * OutputDimensions * 4]);
|
||||
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
vec_add_dpbusd_32(acc[k], in0, col0[k]);
|
||||
}
|
||||
|
||||
vec_t* outptr = reinterpret_cast<vec_t*>(output);
|
||||
for (IndexType k = 0; k < NumRegs; ++k)
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
outptr[k] = acc[k];
|
||||
|
||||
#undef vec_set_32
|
||||
@@ -268,6 +344,16 @@ class AffineTransform {
|
||||
SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \
|
||||
vreinterpretq_s8_s32(b))
|
||||
#define vec_hadd SIMD::neon_m128_hadd
|
||||
#elif defined(USE_LASX)
|
||||
using vec_t = __m256i;
|
||||
#define vec_setzero() __lasx_xvldi(0)
|
||||
#define vec_add_dpbusd_32 SIMD::lasx_m256_add_dpbusd_epi32
|
||||
#define vec_hadd SIMD::lasx_m256_hadd
|
||||
#elif defined(USE_LSX)
|
||||
using vec_t = __m128i;
|
||||
#define vec_setzero() __lsx_vldi(0)
|
||||
#define vec_add_dpbusd_32 SIMD::lsx_m128_add_dpbusd_epi32
|
||||
#define vec_hadd SIMD::lsx_m128_hadd
|
||||
#endif
|
||||
|
||||
const auto inputVector = reinterpret_cast<const vec_t*>(input);
|
||||
@@ -300,7 +386,7 @@ class AffineTransform {
|
||||
|
||||
private:
|
||||
using BiasType = OutputType;
|
||||
using WeightType = std::int8_t;
|
||||
using WeightType = i8;
|
||||
|
||||
alignas(CacheLineSize) BiasType biases[OutputDimensions];
|
||||
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
|
||||
|
||||
@@ -24,11 +24,14 @@
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#include "../../bitboard.h"
|
||||
#include "../../memory.h"
|
||||
#include "../simd.h"
|
||||
#include "../nnue_common.h"
|
||||
#include "../nnz_helper.h"
|
||||
|
||||
/*
|
||||
This file contains the definition for a fully connected layer (aka affine transform) with block sparse input.
|
||||
@@ -36,147 +39,13 @@
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Layers {
|
||||
|
||||
#if (USE_SSSE3 | (USE_NEON >= 8))
|
||||
static constexpr int lsb_index64[64] = {
|
||||
0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42,
|
||||
21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43,
|
||||
31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63};
|
||||
|
||||
constexpr int constexpr_lsb(uint64_t bb) {
|
||||
assert(bb != 0);
|
||||
constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL;
|
||||
return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58];
|
||||
}
|
||||
|
||||
alignas(CacheLineSize) static constexpr struct OffsetIndices {
|
||||
|
||||
std::uint16_t offset_indices[256][8];
|
||||
|
||||
constexpr OffsetIndices() :
|
||||
offset_indices() {
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
std::uint64_t j = i, k = 0;
|
||||
while (j)
|
||||
{
|
||||
offset_indices[i][k++] = constexpr_lsb(j);
|
||||
j &= j - 1;
|
||||
}
|
||||
while (k < 8)
|
||||
offset_indices[i][k++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
} 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* RESTRICT input,
|
||||
std::uint16_t* RESTRICT out,
|
||||
IndexType& count_out) {
|
||||
|
||||
#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);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
// bitmask of nonzero values in this chunk
|
||||
unsigned nnz = 0;
|
||||
for (IndexType j = 0; j < InputsPerChunk; ++j)
|
||||
{
|
||||
const vec_uint_t inputChunk = inputVector[i * InputsPerChunk + j];
|
||||
nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth);
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Sparse input implementation
|
||||
template<IndexType InDims, IndexType OutDims>
|
||||
class AffineTransformSparseInput {
|
||||
public:
|
||||
// Input/output type
|
||||
using InputType = std::uint8_t;
|
||||
using OutputType = std::int32_t;
|
||||
using InputType = u8;
|
||||
using OutputType = i32;
|
||||
|
||||
// Number of input/output dimensions
|
||||
static constexpr IndexType InputDimensions = InDims;
|
||||
@@ -190,7 +59,8 @@ class AffineTransformSparseInput {
|
||||
static constexpr IndexType PaddedOutputDimensions =
|
||||
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
|
||||
|
||||
#if (USE_SSSE3 | (USE_NEON >= 8))
|
||||
#if (defined(USE_SSSE3) || defined(USE_LSX) || defined(USE_LASX) || (USE_NEON >= 8) \
|
||||
|| defined(USE_RVV))
|
||||
static constexpr IndexType ChunkSize = 4;
|
||||
#else
|
||||
static constexpr IndexType ChunkSize = 1;
|
||||
@@ -199,8 +69,8 @@ class AffineTransformSparseInput {
|
||||
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
||||
std::uint32_t hashValue = 0xCC03DAE4u;
|
||||
static constexpr u32 get_hash_value(u32 prevHash) {
|
||||
u32 hashValue = 0xCC03DAE4u;
|
||||
hashValue += OutputDimensions;
|
||||
hashValue ^= prevHash >> 1;
|
||||
hashValue ^= prevHash << 31;
|
||||
@@ -213,7 +83,8 @@ class AffineTransformSparseInput {
|
||||
}
|
||||
|
||||
static constexpr IndexType get_weight_index(IndexType i) {
|
||||
#if (USE_SSSE3 | (USE_NEON >= 8))
|
||||
#if (defined(USE_SSSE3) || defined(USE_LSX) || defined(USE_LASX) || (USE_NEON >= 8) \
|
||||
|| defined(USE_RVV))
|
||||
return get_weight_index_scrambled(i);
|
||||
#else
|
||||
return i;
|
||||
@@ -239,8 +110,8 @@ class AffineTransformSparseInput {
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
usize get_content_hash() const {
|
||||
usize 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));
|
||||
@@ -248,9 +119,11 @@ class AffineTransformSparseInput {
|
||||
}
|
||||
|
||||
// Forward propagation
|
||||
void propagate(const InputType* input, OutputType* output) const {
|
||||
void propagate(const InputType* input,
|
||||
OutputType* output,
|
||||
[[maybe_unused]] const NNZInfo<InDims>& nnzInfo) const {
|
||||
|
||||
#if (USE_SSSE3 | (USE_NEON >= 8))
|
||||
#if (defined(USE_SSSE3) || defined(USE_LSX) || defined(USE_LASX) || (USE_NEON >= 8))
|
||||
#if defined(USE_AVX512)
|
||||
using invec_t = __m512i;
|
||||
using outvec_t = __m512i;
|
||||
@@ -278,49 +151,64 @@ class AffineTransformSparseInput {
|
||||
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
|
||||
#elif defined(USE_LASX)
|
||||
using invec_t = __m256i;
|
||||
using outvec_t = __m256i;
|
||||
#define vec_add_32 __lasx_xvadd_w
|
||||
#define vec_set_32 __lasx_xvreplgr2vr_w
|
||||
#define vec_add_dpbusd_32 SIMD::lasx_m256_add_dpbusd_epi32
|
||||
#elif defined(USE_LSX)
|
||||
using invec_t = __m128i;
|
||||
using outvec_t = __m128i;
|
||||
#define vec_add_32 __lsx_vadd_w
|
||||
#define vec_set_32 __lsx_vreplgr2vr_w
|
||||
#define vec_add_dpbusd_32 SIMD::lsx_m128_add_dpbusd_epi32
|
||||
#endif
|
||||
constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType);
|
||||
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;
|
||||
constexpr IndexType NumAccums = OutputDimensions / OutputSimdWidth;
|
||||
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
|
||||
// into separate dependency chains and merge at the end
|
||||
constexpr IndexType NumRegs =
|
||||
#if defined(USE_VNNI)
|
||||
#if (defined(USE_VNNI) && defined(USE_AVX512)) || defined(USE_NEON_DOTPROD)
|
||||
3 * NumAccums;
|
||||
#elif defined(USE_AVXVNNI)
|
||||
2 * NumAccums;
|
||||
#else
|
||||
NumAccums;
|
||||
#endif
|
||||
std::uint16_t nnz[NumChunks];
|
||||
IndexType count;
|
||||
|
||||
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
|
||||
|
||||
// Find indices of nonzero 32-bit blocks
|
||||
find_nnz<NumChunks>(input32, nnz, count);
|
||||
|
||||
const outvec_t* biasvec = reinterpret_cast<const outvec_t*>(biases);
|
||||
outvec_t acc[NumRegs];
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
acc[k] = biasvec[k];
|
||||
|
||||
const auto* start = nnz;
|
||||
const auto* end = nnz + count;
|
||||
#if defined(USE_AVXVNNI)
|
||||
for (IndexType k = NumAccums; k < NumRegs; ++k)
|
||||
acc[k] = vec_set_32(0);
|
||||
#elif defined(USE_NEON_DOTPROD)
|
||||
for (IndexType k = NumAccums; k < NumRegs; ++k)
|
||||
acc[k] = vdupq_n_s32(0);
|
||||
#endif
|
||||
|
||||
// convince GCC to not do weird pointer arithmetic in the following loops
|
||||
const i8* weights_cp = weights;
|
||||
|
||||
#if defined(USE_AVX512)
|
||||
const auto* start = nnzInfo.nnz;
|
||||
const auto* end = nnzInfo.nnz + nnzInfo.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();
|
||||
|
||||
#if defined(USE_VNNI)
|
||||
while (start < end - 2)
|
||||
{
|
||||
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 =
|
||||
const isize i0 = *start++;
|
||||
const isize i1 = *start++;
|
||||
const isize i2 = *start++;
|
||||
const invec_t in0 = vec_set_32(load_as<i32>(input + i0 * sizeof(i32)));
|
||||
const invec_t in1 = vec_set_32(load_as<i32>(input + i1 * sizeof(i32)));
|
||||
const invec_t in2 = vec_set_32(load_as<i32>(input + i2 * sizeof(i32)));
|
||||
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]);
|
||||
@@ -333,19 +221,147 @@ class AffineTransformSparseInput {
|
||||
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
|
||||
#endif
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
const std::ptrdiff_t i = *start++;
|
||||
const invec_t in = vec_set_32(input32[i]);
|
||||
const auto col =
|
||||
const isize i = *start++;
|
||||
const invec_t in = vec_set_32(load_as<i32>(input + i * sizeof(i32)));
|
||||
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]);
|
||||
}
|
||||
#else
|
||||
static_assert(InputDimensions % 256 == 0);
|
||||
|
||||
for (IndexType k = 0; k < InputDimensions / 256; ++k)
|
||||
{
|
||||
u64 bits = load_as<u64>(nnzInfo.bitset + k * 8);
|
||||
isize base = k * 64;
|
||||
|
||||
auto* base_addr = input + base * sizeof(i32);
|
||||
auto* weights_base = &weights_cp[base * OutputDimensions * ChunkSize];
|
||||
|
||||
#if defined(USE_NEON_DOTPROD) && defined(__GNUC__) && !defined(__clang__)
|
||||
// GCC 15 pessimizes the following code on ARM64 by eliding the intermediate
|
||||
// computation of key pointers (base_addr, weights_base, col, input_addr), leading
|
||||
// to a lot of redundant indexing arithmetic in the while (bits) loop. The
|
||||
// optimization barriers force these pointers to be calculated and used.
|
||||
#if __GNUC__ >= 15
|
||||
#define FIX_GCC15_MISOPTIMIZATION
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef FIX_GCC15_MISOPTIMIZATION
|
||||
asm("" : "+r"(base_addr), "+r"(weights_base)); // opt barrier
|
||||
#endif
|
||||
|
||||
#if defined(USE_AVXVNNI)
|
||||
while (bits)
|
||||
{
|
||||
const isize i0 = pop_lsb(bits);
|
||||
const invec_t in0 = vec_set_32(load_as<i32>(base_addr + i0 * sizeof(i32)));
|
||||
const auto col0 = reinterpret_cast<const invec_t*>(
|
||||
&weights_base[i0 * OutputDimensions * ChunkSize]);
|
||||
|
||||
if (!bits)
|
||||
{
|
||||
for (IndexType l = 0; l < NumAccums; ++l)
|
||||
vec_add_dpbusd_32(acc[l], in0, col0[l]);
|
||||
break;
|
||||
}
|
||||
|
||||
const isize i1 = pop_lsb(bits);
|
||||
const invec_t in1 = vec_set_32(load_as<i32>(base_addr + i1 * sizeof(i32)));
|
||||
const auto col1 = reinterpret_cast<const invec_t*>(
|
||||
&weights_base[i1 * OutputDimensions * ChunkSize]);
|
||||
|
||||
for (IndexType l = 0; l < NumAccums; ++l)
|
||||
{
|
||||
vec_add_dpbusd_32(acc[l], in0, col0[l]);
|
||||
vec_add_dpbusd_32(acc[l + NumAccums], in1, col1[l]);
|
||||
}
|
||||
}
|
||||
#elif defined(USE_NEON_DOTPROD)
|
||||
while (bits)
|
||||
{
|
||||
const isize i0 = pop_lsb(bits);
|
||||
if (!bits)
|
||||
{
|
||||
const invec_t in0 = vec_set_32(load_as<i32>(base_addr + i0 * sizeof(i32)));
|
||||
const auto col0 = reinterpret_cast<const invec_t*>(
|
||||
&weights_base[i0 * OutputDimensions * ChunkSize]);
|
||||
for (IndexType l = 0; l < NumAccums; ++l)
|
||||
vec_add_dpbusd_32(acc[l], in0, col0[l]);
|
||||
break;
|
||||
}
|
||||
|
||||
const isize i1 = pop_lsb(bits);
|
||||
if (!bits)
|
||||
{
|
||||
const invec_t in0 = vec_set_32(load_as<i32>(base_addr + i0 * sizeof(i32)));
|
||||
const invec_t in1 = vec_set_32(load_as<i32>(base_addr + i1 * sizeof(i32)));
|
||||
const auto col0 = reinterpret_cast<const invec_t*>(
|
||||
&weights_base[i0 * OutputDimensions * ChunkSize]);
|
||||
const auto col1 = reinterpret_cast<const invec_t*>(
|
||||
&weights_base[i1 * OutputDimensions * ChunkSize]);
|
||||
for (IndexType l = 0; l < NumAccums; ++l)
|
||||
{
|
||||
vec_add_dpbusd_32(acc[l], in0, col0[l]);
|
||||
vec_add_dpbusd_32(acc[l + NumAccums], in1, col1[l]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const isize i2 = pop_lsb(bits);
|
||||
const invec_t in0 = vec_set_32(load_as<i32>(base_addr + i0 * sizeof(i32)));
|
||||
const invec_t in1 = vec_set_32(load_as<i32>(base_addr + i1 * sizeof(i32)));
|
||||
const invec_t in2 = vec_set_32(load_as<i32>(base_addr + i2 * sizeof(i32)));
|
||||
const auto col0 = reinterpret_cast<const invec_t*>(
|
||||
&weights_base[i0 * OutputDimensions * ChunkSize]);
|
||||
const auto col1 = reinterpret_cast<const invec_t*>(
|
||||
&weights_base[i1 * OutputDimensions * ChunkSize]);
|
||||
const auto col2 = reinterpret_cast<const invec_t*>(
|
||||
&weights_base[i2 * OutputDimensions * ChunkSize]);
|
||||
for (IndexType l = 0; l < NumAccums; ++l)
|
||||
{
|
||||
vec_add_dpbusd_32(acc[l], in0, col0[l]);
|
||||
vec_add_dpbusd_32(acc[l + NumAccums], in1, col1[l]);
|
||||
vec_add_dpbusd_32(acc[l + 2 * NumAccums], in2, col2[l]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
while (bits)
|
||||
{
|
||||
isize i = pop_lsb(bits);
|
||||
const auto* input_addr = base_addr + i * sizeof(i32);
|
||||
auto col =
|
||||
reinterpret_cast<const invec_t*>(&weights_base[i * OutputDimensions * ChunkSize]);
|
||||
|
||||
#ifdef FIX_GCC15_MISOPTIMIZATION
|
||||
asm("" : "+r"(col), "+r"(input_addr));
|
||||
#undef FIX_GCC15_MISOPTIMIZATION
|
||||
#endif
|
||||
|
||||
const invec_t in = vec_set_32(load_as<i32>(input_addr));
|
||||
for (IndexType l = 0; l < NumAccums; ++l)
|
||||
vec_add_dpbusd_32(acc[l], in, col[l]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(USE_AVXVNNI)
|
||||
for (IndexType l = 0; l < NumAccums; ++l)
|
||||
acc[l] = vec_add_32(acc[l], acc[l + NumAccums]);
|
||||
#elif defined(USE_NEON_DOTPROD)
|
||||
for (IndexType l = 0; l < NumAccums; ++l)
|
||||
acc[l] = vaddq_s32(vaddq_s32(acc[l], acc[l + NumAccums]), acc[l + 2 * NumAccums]);
|
||||
#endif
|
||||
#endif
|
||||
outvec_t* outptr = reinterpret_cast<outvec_t*>(output);
|
||||
for (IndexType k = 0; k < NumAccums; ++k)
|
||||
outptr[k] = acc[k];
|
||||
@@ -355,6 +371,51 @@ class AffineTransformSparseInput {
|
||||
#ifdef vec_add_32
|
||||
#undef vec_add_32
|
||||
#endif
|
||||
#elif defined(USE_RVV)
|
||||
static_assert(InputDimensions % 256 == 0);
|
||||
|
||||
const i8* weights_cp = weights;
|
||||
|
||||
#define RVV_SPARSE_PROPAGATE(LMUL) \
|
||||
do \
|
||||
{ \
|
||||
const usize blk = __riscv_vsetvlmax_e32m##LMUL(); \
|
||||
for (IndexType ob = 0; ob < OutputDimensions; ob += blk) \
|
||||
{ \
|
||||
const usize vl = __riscv_vsetvl_e32m##LMUL(OutputDimensions - ob); \
|
||||
vint32m##LMUL##_t acc = __riscv_vle32_v_i32m##LMUL(biases + ob, vl); \
|
||||
for (IndexType k = 0; k < InputDimensions / 256; ++k) \
|
||||
{ \
|
||||
u64 bits = load_as<u64>(nnzInfo.bitset + k * 8); \
|
||||
isize base = k * 64; \
|
||||
auto* base_addr = input + base * sizeof(i32); \
|
||||
auto* weights_base = &weights_cp[base * OutputDimensions * ChunkSize]; \
|
||||
while (bits) \
|
||||
{ \
|
||||
isize i = pop_lsb(bits); \
|
||||
vuint8m##LMUL##_t a = __riscv_vreinterpret_v_u32m##LMUL##_u8m##LMUL( \
|
||||
__riscv_vmv_v_x_u32m##LMUL(load_as<u32>(base_addr + i * sizeof(i32)), \
|
||||
vl)); \
|
||||
vint8m##LMUL##_t b = __riscv_vle8_v_i8m##LMUL( \
|
||||
&weights_base[i * OutputDimensions * ChunkSize + ob * ChunkSize], \
|
||||
vl * ChunkSize); \
|
||||
acc = \
|
||||
__riscv_vadd_vv_i32m##LMUL(acc, SIMD::rvv_dpbusd_m##LMUL(a, b, vl), vl); \
|
||||
} \
|
||||
} \
|
||||
__riscv_vse32_v_i32m##LMUL(output + ob, acc, vl); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
// Select LMUL
|
||||
if (__riscv_vsetvlmax_e32m1() >= OutputDimensions)
|
||||
RVV_SPARSE_PROPAGATE(1);
|
||||
else if (__riscv_vsetvlmax_e32m2() >= OutputDimensions)
|
||||
RVV_SPARSE_PROPAGATE(2);
|
||||
else
|
||||
RVV_SPARSE_PROPAGATE(4);
|
||||
|
||||
#undef RVV_SPARSE_PROPAGATE
|
||||
#else
|
||||
// Use dense implementation for the other architectures.
|
||||
affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
|
||||
@@ -364,7 +425,7 @@ class AffineTransformSparseInput {
|
||||
|
||||
private:
|
||||
using BiasType = OutputType;
|
||||
using WeightType = std::int8_t;
|
||||
using WeightType = i8;
|
||||
|
||||
alignas(CacheLineSize) BiasType biases[OutputDimensions];
|
||||
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
namespace Stockfish::Eval::NNUE::Layers {
|
||||
|
||||
// Clipped ReLU
|
||||
template<IndexType InDims>
|
||||
template<IndexType InDims, int WeightScaleBitsLocal = WeightScaleBits>
|
||||
class ClippedReLU {
|
||||
public:
|
||||
// Input/output type
|
||||
using InputType = std::int32_t;
|
||||
using OutputType = std::uint8_t;
|
||||
using InputType = i32;
|
||||
using OutputType = u8;
|
||||
|
||||
// Number of input/output dimensions
|
||||
static constexpr IndexType InputDimensions = InDims;
|
||||
@@ -46,9 +46,11 @@ class ClippedReLU {
|
||||
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
||||
std::uint32_t hashValue = 0x538D24C7u;
|
||||
static constexpr u32 get_hash_value(u32 prevHash) {
|
||||
u32 hashValue = 0x538D24C7u;
|
||||
hashValue += prevHash;
|
||||
// TODO: consider including WeightScaleBitsLocal in the hash value.
|
||||
// For now omitted on purpose because not written by trainer (yet)
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@@ -58,8 +60,8 @@ 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;
|
||||
usize get_content_hash() const {
|
||||
usize h = 0;
|
||||
hash_combine(h, get_hash_value(0));
|
||||
return h;
|
||||
}
|
||||
@@ -67,49 +69,9 @@ class ClippedReLU {
|
||||
// Forward propagation
|
||||
void propagate(const InputType* input, OutputType* output) const {
|
||||
|
||||
#if defined(USE_AVX2)
|
||||
if constexpr (InputDimensions % SimdWidth == 0)
|
||||
{
|
||||
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||
const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
|
||||
const auto in = reinterpret_cast<const __m256i*>(input);
|
||||
const auto out = reinterpret_cast<__m256i*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m256i words0 =
|
||||
_mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 0]),
|
||||
_mm256_load_si256(&in[i * 4 + 1])),
|
||||
WeightScaleBits);
|
||||
const __m256i words1 =
|
||||
_mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 2]),
|
||||
_mm256_load_si256(&in[i * 4 + 3])),
|
||||
WeightScaleBits);
|
||||
_mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(
|
||||
_mm256_packs_epi16(words0, words1), Offsets));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||
const auto out = reinterpret_cast<__m128i*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m128i words0 = _mm_srli_epi16(
|
||||
_mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
|
||||
WeightScaleBits);
|
||||
const __m128i words1 = _mm_srli_epi16(
|
||||
_mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
|
||||
WeightScaleBits);
|
||||
_mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));
|
||||
}
|
||||
}
|
||||
constexpr IndexType Start = InputDimensions % SimdWidth == 0
|
||||
? InputDimensions / SimdWidth * SimdWidth
|
||||
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
|
||||
|
||||
#elif defined(USE_SSE2)
|
||||
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||
#if defined(USE_SSE2)
|
||||
constexpr IndexType NumChunks = InputDimensions / 16;
|
||||
|
||||
#ifndef USE_SSE41
|
||||
const __m128i k0x80s = _mm_set1_epi8(-128);
|
||||
@@ -122,45 +84,95 @@ class ClippedReLU {
|
||||
#if defined(USE_SSE41)
|
||||
const __m128i words0 = _mm_srli_epi16(
|
||||
_mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
|
||||
WeightScaleBits);
|
||||
WeightScaleBitsLocal);
|
||||
const __m128i words1 = _mm_srli_epi16(
|
||||
_mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
|
||||
WeightScaleBits);
|
||||
WeightScaleBitsLocal);
|
||||
_mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));
|
||||
#else
|
||||
const __m128i words0 = _mm_srai_epi16(
|
||||
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
|
||||
WeightScaleBits);
|
||||
WeightScaleBitsLocal);
|
||||
const __m128i words1 = _mm_srai_epi16(
|
||||
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
|
||||
WeightScaleBits);
|
||||
WeightScaleBitsLocal);
|
||||
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
||||
_mm_store_si128(&out[i], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s));
|
||||
#endif
|
||||
}
|
||||
constexpr IndexType Start = NumChunks * SimdWidth;
|
||||
constexpr IndexType Start = NumChunks * 16;
|
||||
|
||||
#elif defined(USE_NEON)
|
||||
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
||||
const int8x8_t Zero = {0};
|
||||
const auto in = reinterpret_cast<const int32x4_t*>(input);
|
||||
const auto out = reinterpret_cast<int8x8_t*>(output);
|
||||
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
||||
const SIMD::vec_i8x8_t Zero = {0};
|
||||
const auto in = reinterpret_cast<const SIMD::vec_i32x4_t*>(input);
|
||||
const auto out = reinterpret_cast<SIMD::vec_i8x8_t*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
int16x8_t shifted;
|
||||
const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
|
||||
pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits);
|
||||
pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits);
|
||||
pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBitsLocal);
|
||||
pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBitsLocal);
|
||||
out[i] = vmax_s8(vqmovn_s16(shifted), Zero);
|
||||
}
|
||||
constexpr IndexType Start = NumChunks * (SimdWidth / 2);
|
||||
|
||||
#elif defined(USE_LASX)
|
||||
constexpr IndexType NumChunks = InputDimensions / 32;
|
||||
const auto in = reinterpret_cast<const __m256i*>(input);
|
||||
const auto out = reinterpret_cast<__m256i*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m256i packed0 = SIMD::lasx_packus_32(in[i * 4 + 0], in[i * 4 + 1]);
|
||||
const __m256i packed1 = SIMD::lasx_packus_32(in[i * 4 + 2], in[i * 4 + 3]);
|
||||
const __m256i words0 = __lasx_xvsrli_h(packed0, WeightScaleBitsLocal);
|
||||
const __m256i words1 = __lasx_xvsrli_h(packed1, WeightScaleBitsLocal);
|
||||
const __m256i packed = __lasx_xvssrani_b_h(words1, words0, 0);
|
||||
const __m256i swaped = __lasx_xvpermi_d(packed, 0xD8);
|
||||
__lasx_xvst(__lasx_xvshuf4i_w(swaped, 0xD8), out + i, 0);
|
||||
}
|
||||
constexpr IndexType Start = NumChunks * 32;
|
||||
|
||||
#elif defined(USE_LSX)
|
||||
constexpr IndexType NumChunks = InputDimensions / 16;
|
||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||
const auto out = reinterpret_cast<__m128i*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m128i packed0 = SIMD::lsx_packus_32(in[i * 4 + 0], in[i * 4 + 1]);
|
||||
const __m128i packed1 = SIMD::lsx_packus_32(in[i * 4 + 2], in[i * 4 + 3]);
|
||||
const __m128i words0 = __lsx_vsrli_h(packed0, WeightScaleBitsLocal);
|
||||
const __m128i words1 = __lsx_vsrli_h(packed1, WeightScaleBitsLocal);
|
||||
out[i] = __lsx_vssrani_b_h(words1, words0, 0);
|
||||
}
|
||||
constexpr IndexType Start = NumChunks * 16;
|
||||
|
||||
#elif defined(USE_RVV)
|
||||
|
||||
for (usize j = 0; j < InputDimensions;)
|
||||
{
|
||||
usize vl = __riscv_vsetvl_e32m4(InputDimensions - j);
|
||||
|
||||
vint32m4_t in = __riscv_vle32_v_i32m4(&input[j], vl);
|
||||
in = __riscv_vmax_vx_i32m4(in, 0, vl);
|
||||
|
||||
vint16m2_t words =
|
||||
__riscv_vnclip_wx_i16m2(in, WeightScaleBitsLocal, __RISCV_VXRM_RDN, vl);
|
||||
vint8m1_t narrowed = __riscv_vnclip_wx_i8m1(words, 0, __RISCV_VXRM_RDN, vl);
|
||||
|
||||
__riscv_vse8_v_u8m1(&output[j], __riscv_vreinterpret_v_i8m1_u8m1(narrowed), vl);
|
||||
j += vl;
|
||||
}
|
||||
constexpr IndexType Start = InputDimensions;
|
||||
|
||||
#else
|
||||
constexpr IndexType Start = 0;
|
||||
#endif
|
||||
|
||||
for (IndexType i = Start; i < InputDimensions; ++i)
|
||||
{
|
||||
output[i] = static_cast<OutputType>(std::clamp(input[i] >> WeightScaleBits, 0, 127));
|
||||
output[i] =
|
||||
static_cast<OutputType>(std::clamp(input[i] >> WeightScaleBitsLocal, 0, 127));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,16 +26,17 @@
|
||||
#include <iosfwd>
|
||||
|
||||
#include "../nnue_common.h"
|
||||
#include "../simd.h"
|
||||
|
||||
namespace Stockfish::Eval::NNUE::Layers {
|
||||
|
||||
// Clipped ReLU
|
||||
template<IndexType InDims>
|
||||
template<IndexType InDims, int WeightScaleBitsLocal = WeightScaleBits>
|
||||
class SqrClippedReLU {
|
||||
public:
|
||||
// Input/output type
|
||||
using InputType = std::int32_t;
|
||||
using OutputType = std::uint8_t;
|
||||
using InputType = i32;
|
||||
using OutputType = u8;
|
||||
|
||||
// Number of input/output dimensions
|
||||
static constexpr IndexType InputDimensions = InDims;
|
||||
@@ -46,9 +47,11 @@ class SqrClippedReLU {
|
||||
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
||||
std::uint32_t hashValue = 0x538D24C7u;
|
||||
static constexpr u32 get_hash_value(u32 prevHash) {
|
||||
u32 hashValue = 0x538D24C7u;
|
||||
hashValue += prevHash;
|
||||
// TODO: consider including WeightScaleBitsLocal in the hash value.
|
||||
// For now omitted on purpose because not written by trainer (yet)
|
||||
return hashValue;
|
||||
}
|
||||
|
||||
@@ -58,21 +61,82 @@ 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;
|
||||
usize get_content_hash() const {
|
||||
usize h = 0;
|
||||
hash_combine(h, get_hash_value(0));
|
||||
return h;
|
||||
}
|
||||
|
||||
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
|
||||
// Produce the squared and linear clipped activations together, sharing the input loads and
|
||||
// the initial signed 32-to-16-bit saturating packs.
|
||||
void propagate_pair(const InputType* input, OutputType* squared, OutputType* clipped) const {
|
||||
static_assert(WeightScaleBitsLocal >= 5 && WeightScaleBitsLocal <= 8,
|
||||
"SqrClippedReLU only support WeightScaleBitsLocal between 5 and 8");
|
||||
static_assert(InputDimensions % 32 == 0);
|
||||
|
||||
constexpr IndexType NumChunks = InputDimensions / 32;
|
||||
constexpr int SimdShiftAmount = WeightScaleBitsLocal * 2 + 7 - 16;
|
||||
|
||||
const auto in = reinterpret_cast<const __m256i*>(input);
|
||||
auto sqrOut = reinterpret_cast<__m256i*>(squared);
|
||||
auto clipOut = reinterpret_cast<__m256i*>(clipped);
|
||||
|
||||
const __m256i zero = _mm256_setzero_si256();
|
||||
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m256i words0 = _mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 0]),
|
||||
_mm256_load_si256(&in[i * 4 + 1]));
|
||||
const __m256i words1 = _mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 2]),
|
||||
_mm256_load_si256(&in[i * 4 + 3]));
|
||||
|
||||
const __m256i sqr0 =
|
||||
_mm256_srli_epi16(_mm256_mulhi_epi16(words0, words0), SimdShiftAmount);
|
||||
const __m256i sqr1 =
|
||||
_mm256_srli_epi16(_mm256_mulhi_epi16(words1, words1), SimdShiftAmount);
|
||||
const __m256i sqrPacked = _mm256_packs_epi16(sqr0, sqr1);
|
||||
_mm256_store_si256(&sqrOut[i], sqrPacked);
|
||||
|
||||
const __m256i clip0 =
|
||||
_mm256_srli_epi16(_mm256_max_epi16(words0, zero), WeightScaleBitsLocal);
|
||||
const __m256i clip1 =
|
||||
_mm256_srli_epi16(_mm256_max_epi16(words1, zero), WeightScaleBitsLocal);
|
||||
const __m256i clipPacked = _mm256_packs_epi16(clip0, clip1);
|
||||
_mm256_store_si256(&clipOut[i], clipPacked);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Forward propagation
|
||||
void propagate(const InputType* input, OutputType* output) const {
|
||||
static_assert(WeightScaleBitsLocal >= 5 && WeightScaleBitsLocal <= 8,
|
||||
"SqrClippedReLU only support WeightScaleBitsLocal between 5 and 8");
|
||||
// After squaring we need to shift by WeightScaleBitsLocal * 2 + 7
|
||||
// MulHi strips the lower 16 bits (i.e. shift by 16) so we need to shift out the remaining.
|
||||
[[maybe_unused]] constexpr int SimdShiftAmount = WeightScaleBitsLocal * 2 + 7 - 16;
|
||||
|
||||
#if defined(USE_SSE2)
|
||||
#if defined(USE_AVX512)
|
||||
static_assert(InputDimensions % 32 == 0);
|
||||
constexpr IndexType NumChunks = InputDimensions / 32;
|
||||
const auto in = reinterpret_cast<const __m512i*>(input);
|
||||
const auto out = reinterpret_cast<__m256i*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m256i words0 = _mm512_cvtsepi32_epi16(_mm512_load_si512(&in[i * 2 + 0]));
|
||||
const __m256i words1 = _mm512_cvtsepi32_epi16(_mm512_load_si512(&in[i * 2 + 1]));
|
||||
__m512i words = _mm512_inserti64x4(_mm512_castsi256_si512(words0), words1, 1);
|
||||
|
||||
words = _mm512_srli_epi16(_mm512_mulhi_epi16(words, words), SimdShiftAmount);
|
||||
|
||||
_mm256_store_si256(&out[i], _mm512_cvtsepi16_epi8(words));
|
||||
}
|
||||
constexpr IndexType Start = NumChunks * 32;
|
||||
|
||||
#elif defined(USE_SSE2)
|
||||
constexpr IndexType NumChunks = InputDimensions / 16;
|
||||
|
||||
static_assert(WeightScaleBits == 6);
|
||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||
const auto out = reinterpret_cast<__m128i*>(output);
|
||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||
const auto out = reinterpret_cast<__m128i*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
__m128i words0 =
|
||||
@@ -80,16 +144,79 @@ class SqrClippedReLU {
|
||||
__m128i words1 =
|
||||
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3]));
|
||||
|
||||
// We shift by WeightScaleBits * 2 = 12 and divide by 128
|
||||
// which is an additional shift-right of 7, meaning 19 in total.
|
||||
// MulHi strips the lower 16 bits so we need to shift out 3 more to match.
|
||||
words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3);
|
||||
words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3);
|
||||
words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), SimdShiftAmount);
|
||||
words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), SimdShiftAmount);
|
||||
|
||||
_mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));
|
||||
}
|
||||
constexpr IndexType Start = NumChunks * 16;
|
||||
|
||||
#elif defined(USE_LASX)
|
||||
constexpr IndexType NumChunks = InputDimensions / 32;
|
||||
const auto in = reinterpret_cast<const __m256i*>(input);
|
||||
const auto out = reinterpret_cast<__m256i*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m256i words0 = __lasx_xvssrani_h_w(in[i * 4 + 1], in[i * 4 + 0], 0);
|
||||
const __m256i words1 = __lasx_xvssrani_h_w(in[i * 4 + 3], in[i * 4 + 2], 0);
|
||||
const __m256i sqr0 = __lasx_xvmuh_h(words0, words0);
|
||||
const __m256i sqr1 = __lasx_xvmuh_h(words1, words1);
|
||||
__m256i packed;
|
||||
packed = __lasx_xvssrlni_b_h(sqr1, sqr0, SimdShiftAmount);
|
||||
const __m256i permed = __lasx_xvpermi_d(packed, 0xD8);
|
||||
__lasx_xvst(__lasx_xvshuf4i_w(permed, 0xD8), out + i, 0);
|
||||
}
|
||||
constexpr IndexType Start = NumChunks * 32;
|
||||
|
||||
#elif defined(USE_LSX)
|
||||
constexpr IndexType NumChunks = InputDimensions / 16;
|
||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||
const auto out = reinterpret_cast<__m128i*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const __m128i words0 = __lsx_vssrani_h_w(in[i * 4 + 1], in[i * 4 + 0], 0);
|
||||
const __m128i words1 = __lsx_vssrani_h_w(in[i * 4 + 3], in[i * 4 + 2], 0);
|
||||
const __m128i sqr0 = __lsx_vmuh_h(words0, words0);
|
||||
const __m128i sqr1 = __lsx_vmuh_h(words1, words1);
|
||||
out[i] = __lsx_vssrlni_b_h(sqr1, sqr0, SimdShiftAmount);
|
||||
}
|
||||
constexpr IndexType Start = NumChunks * 16;
|
||||
|
||||
#elif defined(USE_NEON)
|
||||
constexpr IndexType NumChunks = InputDimensions / 16;
|
||||
const auto in = reinterpret_cast<const int32x4_t*>(input);
|
||||
const auto out = reinterpret_cast<int8x16_t*>(output);
|
||||
for (IndexType i = 0; i < NumChunks; ++i)
|
||||
{
|
||||
const int16x8_t words0 =
|
||||
vcombine_s16(vqmovn_s32(in[i * 4 + 0]), vqmovn_s32(in[i * 4 + 1]));
|
||||
const int16x8_t words1 =
|
||||
vcombine_s16(vqmovn_s32(in[i * 4 + 2]), vqmovn_s32(in[i * 4 + 3]));
|
||||
// Neon needs to shift by one more since the used simd instruction does
|
||||
// `Saturating Doubling Multiply High` (doubling before shift by 16).
|
||||
const int16x8_t r0 = vshrq_n_s16(vqdmulhq_s16(words0, words0), SimdShiftAmount + 1);
|
||||
const int16x8_t r1 = vshrq_n_s16(vqdmulhq_s16(words1, words1), SimdShiftAmount + 1);
|
||||
|
||||
out[i] = vcombine_s8(vqmovn_s16(r0), vqmovn_s16(r1));
|
||||
}
|
||||
constexpr IndexType Start = NumChunks * 16;
|
||||
|
||||
#elif defined(USE_RVV)
|
||||
|
||||
for (usize j = 0; j < InputDimensions;)
|
||||
{
|
||||
usize vl = __riscv_vsetvl_e32m4(InputDimensions - j);
|
||||
vint32m4_t in = __riscv_vle32_v_i32m4(&input[j], vl);
|
||||
|
||||
vint16m2_t words = __riscv_vnclip_wx_i16m2(in, 0, __RISCV_VXRM_RDN, vl);
|
||||
vint16m2_t sqr = __riscv_vmulh_vv_i16m2(words, words, vl);
|
||||
vint8m1_t narrowed = __riscv_vnclip_wx_i8m1(sqr, SimdShiftAmount, __RISCV_VXRM_RDN, vl);
|
||||
|
||||
__riscv_vse8_v_u8m1(&output[j], __riscv_vreinterpret_v_i8m1_u8m1(narrowed), vl);
|
||||
j += vl;
|
||||
}
|
||||
constexpr IndexType Start = InputDimensions;
|
||||
|
||||
#else
|
||||
constexpr IndexType Start = 0;
|
||||
#endif
|
||||
@@ -99,7 +226,8 @@ class SqrClippedReLU {
|
||||
output[i] = static_cast<OutputType>(
|
||||
// Really should be /127 but we need to make it fast so we right-shift
|
||||
// by an extra 7 bits instead. Needs to be accounted for in the trainer.
|
||||
std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7)));
|
||||
std::min(127ll,
|
||||
((long long) (input[i]) * input[i]) >> (2 * WeightScaleBitsLocal + 7)));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
+105
-168
@@ -24,6 +24,7 @@
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
#define INCBIN_SILENCE_BITCODE_WARNING
|
||||
#include "../incbin/incbin.h"
|
||||
@@ -35,6 +36,7 @@
|
||||
#include "nnue_architecture.h"
|
||||
#include "nnue_common.h"
|
||||
#include "nnue_misc.h"
|
||||
#include "nnz_helper.h"
|
||||
|
||||
// Macro to embed the default efficiently updatable neural network (NNUE) file
|
||||
// data in the engine binary (using incbin.h, by Dale Weiler).
|
||||
@@ -43,46 +45,24 @@
|
||||
// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end
|
||||
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
|
||||
// Note that this does not work in Microsoft Visual Studio.
|
||||
#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
|
||||
INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig);
|
||||
INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall);
|
||||
#if !defined(UNIVERSAL_BINARY) && !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
|
||||
INCBIN(EmbeddedNNUE, EvalFileDefaultName);
|
||||
#elif defined(UNIVERSAL_BINARY_MACOS_X86_SLICE)
|
||||
// Determined at runtime, see universal/nnue_embed.cpp
|
||||
extern const unsigned char* const gEmbeddedNNUEData;
|
||||
extern const unsigned int gEmbeddedNNUESize;
|
||||
#elif defined(UNIVERSAL_BINARY)
|
||||
extern const unsigned char gEmbeddedNNUEData[];
|
||||
extern const unsigned int gEmbeddedNNUESize;
|
||||
#else
|
||||
const unsigned char gEmbeddedNNUEBigData[1] = {0x0};
|
||||
const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1];
|
||||
const unsigned int gEmbeddedNNUEBigSize = 1;
|
||||
const unsigned char gEmbeddedNNUESmallData[1] = {0x0};
|
||||
const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1];
|
||||
const unsigned int gEmbeddedNNUESmallSize = 1;
|
||||
const unsigned char gEmbeddedNNUEData[1] = {0x0};
|
||||
const unsigned int gEmbeddedNNUESize = 1;
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
struct EmbeddedNNUE {
|
||||
EmbeddedNNUE(const unsigned char* embeddedData,
|
||||
const unsigned char* embeddedEnd,
|
||||
const unsigned int embeddedSize) :
|
||||
data(embeddedData),
|
||||
end(embeddedEnd),
|
||||
size(embeddedSize) {}
|
||||
const unsigned char* data;
|
||||
const unsigned char* end;
|
||||
const unsigned int size;
|
||||
};
|
||||
|
||||
using namespace Stockfish::Eval::NNUE;
|
||||
|
||||
EmbeddedNNUE get_embedded(EmbeddedNNUEType type) {
|
||||
if (type == EmbeddedNNUEType::BIG)
|
||||
return EmbeddedNNUE(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize);
|
||||
else
|
||||
return EmbeddedNNUE(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Detail {
|
||||
|
||||
@@ -90,8 +70,8 @@ namespace Detail {
|
||||
template<typename T>
|
||||
bool read_parameters(std::istream& stream, T& reference) {
|
||||
|
||||
std::uint32_t header;
|
||||
header = read_little_endian<std::uint32_t>(stream);
|
||||
u32 header;
|
||||
header = read_little_endian<u32>(stream);
|
||||
if (!stream || header != T::get_hash_value())
|
||||
return false;
|
||||
return reference.read_parameters(stream);
|
||||
@@ -101,107 +81,96 @@ bool read_parameters(std::istream& stream, T& reference) {
|
||||
template<typename T>
|
||||
bool write_parameters(std::ostream& stream, const T& reference) {
|
||||
|
||||
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
|
||||
write_little_endian<u32>(stream, T::get_hash_value());
|
||||
return reference.write_parameters(stream);
|
||||
}
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
void Network<Arch, Transformer>::load(const std::string& rootDirectory, std::string evalfilePath) {
|
||||
void Network::load(const fs::path& rootDirectory, fs::path evalfilePath, EvalFile& evalFile) {
|
||||
#if defined(DEFAULT_NNUE_DIRECTORY)
|
||||
std::vector<std::string> dirs = {"<internal>", "", rootDirectory,
|
||||
stringify(DEFAULT_NNUE_DIRECTORY)};
|
||||
std::vector<fs::path> dirs = {fs::path{}, rootDirectory,
|
||||
fs::path(stringify(DEFAULT_NNUE_DIRECTORY))};
|
||||
#else
|
||||
std::vector<std::string> dirs = {"<internal>", "", rootDirectory};
|
||||
std::vector<fs::path> dirs = {fs::path{}, rootDirectory};
|
||||
#endif
|
||||
|
||||
if (evalfilePath.empty())
|
||||
evalfilePath = evalFile.defaultName;
|
||||
|
||||
if (evalFile.current != evalfilePath && evalfilePath == evalFile.defaultName)
|
||||
load_internal(evalFile);
|
||||
|
||||
for (const auto& directory : dirs)
|
||||
{
|
||||
if (std::string(evalFile.current) != evalfilePath)
|
||||
{
|
||||
if (directory != "<internal>")
|
||||
{
|
||||
load_user_net(directory, evalfilePath);
|
||||
}
|
||||
|
||||
if (directory == "<internal>" && evalfilePath == std::string(evalFile.defaultName))
|
||||
{
|
||||
load_internal();
|
||||
}
|
||||
}
|
||||
if (evalFile.current != evalfilePath)
|
||||
load_external(directory, evalfilePath, evalFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
bool Network<Arch, Transformer>::save(const std::optional<std::string>& filename) const {
|
||||
std::string actualFilename;
|
||||
std::string msg;
|
||||
|
||||
if (filename.has_value())
|
||||
actualFilename = filename.value();
|
||||
else
|
||||
bool Network::save(const EvalFile& evalFile, const std::optional<fs::path>& filename) const {
|
||||
if (!evalFile.current.has_value())
|
||||
{
|
||||
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";
|
||||
|
||||
sync_cout << msg << sync_endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
actualFilename = evalFile.defaultName;
|
||||
sync_cout << "Failed to export a net. No network file is currently loaded. "
|
||||
"Please load a network file first."
|
||||
<< sync_endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!filename.has_value() && evalFile.current != evalFile.defaultName)
|
||||
{
|
||||
sync_cout << "Failed to export a net. A non-embedded net can only be "
|
||||
"saved if the filename is specified"
|
||||
<< sync_endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::path actualFilename = filename.value_or(evalFile.defaultName);
|
||||
std::ofstream stream(actualFilename, std::ios_base::binary);
|
||||
bool saved = save(stream, evalFile.current, evalFile.netDescription);
|
||||
|
||||
msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net";
|
||||
bool saved = save(stream, evalFile.netDescription);
|
||||
|
||||
sync_cout << (saved ? "Network saved successfully to " + actualFilename.string()
|
||||
: "Failed to export a net")
|
||||
<< sync_endl;
|
||||
|
||||
sync_cout << msg << sync_endl;
|
||||
return saved;
|
||||
}
|
||||
|
||||
NetworkOutput Network::evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches& cache) const {
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
NetworkOutput
|
||||
Network<Arch, Transformer>::evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches::Cache<FTDimensions>& cache) const {
|
||||
constexpr u64 alignment = CacheLineSize;
|
||||
|
||||
constexpr uint64_t alignment = CacheLineSize;
|
||||
|
||||
alignas(alignment)
|
||||
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
|
||||
alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
|
||||
|
||||
ASSERT_ALIGNED(transformedFeatures, alignment);
|
||||
|
||||
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
||||
const auto psqt =
|
||||
featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
|
||||
const auto positional = network[bucket].propagate(transformedFeatures);
|
||||
NNZInfo<L1> nnzInfo;
|
||||
|
||||
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
||||
const auto psqt = featureTransformer.transform(pos, accumulatorStack, cache,
|
||||
transformedFeatures, bucket, nnzInfo);
|
||||
const auto positional = network[bucket].propagate(transformedFeatures, nnzInfo);
|
||||
return {static_cast<Value>(psqt / OutputScale), static_cast<Value>(positional / OutputScale)};
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
void Network<Arch, Transformer>::verify(std::string evalfilePath,
|
||||
const std::function<void(std::string_view)>& f) const {
|
||||
void Network::verify(const std::function<void(std::string_view)>& f,
|
||||
const EvalFile& evalFile,
|
||||
fs::path evalfilePath) const {
|
||||
if (evalfilePath.empty())
|
||||
evalfilePath = evalFile.defaultName;
|
||||
|
||||
if (std::string(evalFile.current) != evalfilePath)
|
||||
if (evalFile.current != evalfilePath)
|
||||
{
|
||||
if (f)
|
||||
{
|
||||
std::string msg1 =
|
||||
"Network evaluation parameters compatible with the engine must be available.";
|
||||
std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully.";
|
||||
std::string msg2 =
|
||||
"The network file " + evalfilePath.string() + " was not loaded successfully.";
|
||||
std::string msg3 = "The UCI option EvalFile might need to specify the full path, "
|
||||
"including the directory name, to the network file.";
|
||||
std::string msg4 = "The default net can be downloaded from: "
|
||||
@@ -220,9 +189,10 @@ void Network<Arch, Transformer>::verify(std::string
|
||||
|
||||
if (f)
|
||||
{
|
||||
size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks;
|
||||
f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024))
|
||||
+ "MiB, (" + std::to_string(featureTransformer.TotalInputDimensions) + ", "
|
||||
usize size = sizeof(featureTransformer) + sizeof(NetworkArchitecture) * LayerStacks;
|
||||
f("NNUE evaluation using " + evalfilePath.string() + " ("
|
||||
+ std::to_string(size / (1024 * 1024)) + "MiB, ("
|
||||
+ std::to_string(featureTransformer.InputDimensions) + ", "
|
||||
+ std::to_string(network[0].TransformedFeatureDimensions) + ", "
|
||||
+ std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS)
|
||||
+ ", 1))");
|
||||
@@ -230,16 +200,13 @@ void Network<Arch, Transformer>::verify(std::string
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
NnueEvalTrace
|
||||
Network<Arch, Transformer>::trace_evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches::Cache<FTDimensions>& cache) const {
|
||||
NnueEvalTrace Network::trace_evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches& cache) const {
|
||||
|
||||
constexpr uint64_t alignment = CacheLineSize;
|
||||
constexpr u64 alignment = CacheLineSize;
|
||||
|
||||
alignas(alignment)
|
||||
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
|
||||
alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
|
||||
|
||||
ASSERT_ALIGNED(transformedFeatures, alignment);
|
||||
|
||||
@@ -247,9 +214,10 @@ Network<Arch, Transformer>::trace_evaluate(const Position&
|
||||
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
||||
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)
|
||||
{
|
||||
const auto materialist =
|
||||
featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
|
||||
const auto positional = network[bucket].propagate(transformedFeatures);
|
||||
NNZInfo<L1> nnzInfo;
|
||||
const auto materialist = featureTransformer.transform(pos, accumulatorStack, cache,
|
||||
transformedFeatures, bucket, nnzInfo);
|
||||
const auto positional = network[bucket].propagate(transformedFeatures, nnzInfo);
|
||||
|
||||
t.psqt[bucket] = static_cast<Value>(materialist / OutputScale);
|
||||
t.positional[bucket] = static_cast<Value>(positional / OutputScale);
|
||||
@@ -259,10 +227,8 @@ Network<Arch, Transformer>::trace_evaluate(const Position&
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
void Network<Arch, Transformer>::load_user_net(const std::string& dir,
|
||||
const std::string& evalfilePath) {
|
||||
std::ifstream stream(dir + evalfilePath, std::ios::binary);
|
||||
void Network::load_external(const fs::path& dir, const fs::path& evalfilePath, EvalFile& evalFile) {
|
||||
std::ifstream stream(dir / evalfilePath, std::ios::binary);
|
||||
auto description = load(stream);
|
||||
|
||||
if (description.has_value())
|
||||
@@ -273,21 +239,23 @@ void Network<Arch, Transformer>::load_user_net(const std::string& dir,
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
void Network<Arch, Transformer>::load_internal() {
|
||||
void Network::load_internal(EvalFile& evalFile) {
|
||||
// C++ way to prepare a buffer for a memory stream
|
||||
class MemoryBuffer: public std::basic_streambuf<char> {
|
||||
public:
|
||||
MemoryBuffer(char* p, size_t n) {
|
||||
MemoryBuffer(char* p, usize n) {
|
||||
setg(p, p, p + n);
|
||||
setp(p, p + n);
|
||||
}
|
||||
};
|
||||
|
||||
const auto embedded = get_embedded(embeddedType);
|
||||
#ifdef UNIVERSAL_BINARY_MACOS_X86_SLICE
|
||||
if (gEmbeddedNNUEData == nullptr) // failed embedded load
|
||||
return;
|
||||
#endif
|
||||
|
||||
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(embedded.data)),
|
||||
size_t(embedded.size));
|
||||
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
|
||||
usize(gEmbeddedNNUESize));
|
||||
|
||||
std::istream stream(&buffer);
|
||||
auto description = load(stream);
|
||||
@@ -300,25 +268,15 @@ void Network<Arch, Transformer>::load_internal() {
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
void Network<Arch, Transformer>::initialize() {
|
||||
initialized = true;
|
||||
}
|
||||
void Network::initialize() { initialized = true; }
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
bool Network<Arch, Transformer>::save(std::ostream& stream,
|
||||
const std::string& name,
|
||||
const std::string& netDescription) const {
|
||||
if (name.empty() || name == "None")
|
||||
return false;
|
||||
|
||||
bool Network::save(std::ostream& stream, const std::string& netDescription) const {
|
||||
return write_parameters(stream, netDescription);
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
std::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream) {
|
||||
std::optional<std::string> Network::load(std::istream& stream) {
|
||||
initialize();
|
||||
std::string description;
|
||||
|
||||
@@ -326,30 +284,24 @@ 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 {
|
||||
usize Network::get_content_hash() const {
|
||||
if (!initialized)
|
||||
return 0;
|
||||
|
||||
std::size_t h = 0;
|
||||
usize 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,
|
||||
std::uint32_t* hashValue,
|
||||
std::string* desc) const {
|
||||
std::uint32_t version, size;
|
||||
bool Network::read_header(std::istream& stream, u32* hashValue, std::string* desc) const {
|
||||
u32 version, size;
|
||||
|
||||
version = read_little_endian<std::uint32_t>(stream);
|
||||
*hashValue = read_little_endian<std::uint32_t>(stream);
|
||||
size = read_little_endian<std::uint32_t>(stream);
|
||||
version = read_little_endian<u32>(stream);
|
||||
*hashValue = read_little_endian<u32>(stream);
|
||||
size = read_little_endian<u32>(stream);
|
||||
if (!stream || version != Version)
|
||||
return false;
|
||||
desc->resize(size);
|
||||
@@ -359,29 +311,24 @@ bool Network<Arch, Transformer>::read_header(std::istream& stream,
|
||||
|
||||
|
||||
// Write network header
|
||||
template<typename Arch, typename Transformer>
|
||||
bool Network<Arch, Transformer>::write_header(std::ostream& stream,
|
||||
std::uint32_t hashValue,
|
||||
const std::string& desc) const {
|
||||
write_little_endian<std::uint32_t>(stream, Version);
|
||||
write_little_endian<std::uint32_t>(stream, hashValue);
|
||||
write_little_endian<std::uint32_t>(stream, std::uint32_t(desc.size()));
|
||||
bool Network::write_header(std::ostream& stream, u32 hashValue, const std::string& desc) const {
|
||||
write_little_endian<u32>(stream, Version);
|
||||
write_little_endian<u32>(stream, hashValue);
|
||||
write_little_endian<u32>(stream, u32(desc.size()));
|
||||
stream.write(&desc[0], desc.size());
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
bool Network<Arch, Transformer>::read_parameters(std::istream& stream,
|
||||
std::string& netDescription) {
|
||||
std::uint32_t hashValue;
|
||||
bool Network::read_parameters(std::istream& stream, std::string& netDescription) {
|
||||
u32 hashValue;
|
||||
if (!read_header(stream, &hashValue, &netDescription))
|
||||
return false;
|
||||
if (hashValue != Network::hash)
|
||||
return false;
|
||||
if (!Detail::read_parameters(stream, featureTransformer))
|
||||
return false;
|
||||
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||
for (usize i = 0; i < LayerStacks; ++i)
|
||||
{
|
||||
if (!Detail::read_parameters(stream, network[i]))
|
||||
return false;
|
||||
@@ -390,14 +337,12 @@ bool Network<Arch, Transformer>::read_parameters(std::istream& stream,
|
||||
}
|
||||
|
||||
|
||||
template<typename Arch, typename Transformer>
|
||||
bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
|
||||
const std::string& netDescription) const {
|
||||
bool Network::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))
|
||||
return false;
|
||||
for (std::size_t i = 0; i < LayerStacks; ++i)
|
||||
for (usize i = 0; i < LayerStacks; ++i)
|
||||
{
|
||||
if (!Detail::write_parameters(stream, network[i]))
|
||||
return false;
|
||||
@@ -405,12 +350,4 @@ bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
|
||||
return bool(stream);
|
||||
}
|
||||
|
||||
// Explicit template instantiations
|
||||
|
||||
template class Network<NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>,
|
||||
FeatureTransformer<TransformedFeatureDimensionsBig>>;
|
||||
|
||||
template class Network<NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>,
|
||||
FeatureTransformer<TransformedFeatureDimensionsSmall>>;
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
+35
-75
@@ -19,8 +19,6 @@
|
||||
#ifndef NETWORK_H_INCLUDED
|
||||
#define NETWORK_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
@@ -28,12 +26,11 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <filesystem>
|
||||
|
||||
#include "../misc.h"
|
||||
#include "../types.h"
|
||||
#include "nnue_accumulator.h"
|
||||
#include "../misc.h"
|
||||
#include "nnue_architecture.h"
|
||||
#include "nnue_common.h"
|
||||
#include "nnue_feature_transformer.h"
|
||||
#include "nnue_misc.h"
|
||||
|
||||
@@ -43,24 +40,17 @@ class Position;
|
||||
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
enum class EmbeddedNNUEType {
|
||||
BIG,
|
||||
SMALL,
|
||||
};
|
||||
class AccumulatorStack;
|
||||
struct AccumulatorCaches;
|
||||
|
||||
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;
|
||||
|
||||
public:
|
||||
Network(EvalFile file, EmbeddedNNUEType type) :
|
||||
evalFile(file),
|
||||
embeddedType(type) {}
|
||||
Network() = default;
|
||||
|
||||
Network(const Network& other) = default;
|
||||
Network(Network&& other) = default;
|
||||
@@ -68,94 +58,64 @@ class Network {
|
||||
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;
|
||||
void load(const std::filesystem::path& rootDirectory,
|
||||
std::filesystem::path evalfilePath,
|
||||
EvalFile& evalFile);
|
||||
bool save(const EvalFile& evalFile, const std::optional<std::filesystem::path>& filename) const;
|
||||
|
||||
std::size_t get_content_hash() const;
|
||||
usize get_content_hash() const;
|
||||
|
||||
NetworkOutput evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches::Cache<FTDimensions>& cache) const;
|
||||
NetworkOutput evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches& 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;
|
||||
void verify(const std::function<void(std::string_view)>& f,
|
||||
const EvalFile& evalFile,
|
||||
std::filesystem::path evalfilePath) const;
|
||||
|
||||
NnueEvalTrace trace_evaluate(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches& cache) const;
|
||||
|
||||
void load_external(const std::filesystem::path&, const std::filesystem::path&, EvalFile&);
|
||||
void load_internal(EvalFile&);
|
||||
|
||||
private:
|
||||
void load_user_net(const std::string&, const std::string&);
|
||||
void load_internal();
|
||||
|
||||
void initialize();
|
||||
|
||||
bool save(std::ostream&, const std::string&, const std::string&) const;
|
||||
bool save(std::ostream&, const std::string&) const;
|
||||
std::optional<std::string> load(std::istream&);
|
||||
|
||||
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_header(std::istream&, u32*, std::string*) const;
|
||||
bool write_header(std::ostream&, u32, const std::string&) const;
|
||||
|
||||
bool read_parameters(std::istream&, std::string&);
|
||||
bool write_parameters(std::ostream&, const std::string&) const;
|
||||
|
||||
// Input feature converter
|
||||
Transformer featureTransformer;
|
||||
FeatureTransformer featureTransformer;
|
||||
|
||||
// Evaluation function
|
||||
Arch network[LayerStacks];
|
||||
|
||||
EvalFile evalFile;
|
||||
EmbeddedNNUEType embeddedType;
|
||||
NetworkArchitecture network[LayerStacks];
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
// Hash value of evaluation function structure
|
||||
static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value();
|
||||
static constexpr u32 hash =
|
||||
FeatureTransformer::get_hash_value() ^ NetworkArchitecture::get_hash_value();
|
||||
|
||||
template<IndexType Size>
|
||||
friend struct AccumulatorCaches::Cache;
|
||||
};
|
||||
|
||||
// Definitions of the network types
|
||||
using SmallFeatureTransformer = FeatureTransformer<TransformedFeatureDimensionsSmall>;
|
||||
using SmallNetworkArchitecture =
|
||||
NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>;
|
||||
|
||||
using BigFeatureTransformer = FeatureTransformer<TransformedFeatureDimensionsBig>;
|
||||
using BigNetworkArchitecture = NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>;
|
||||
|
||||
using NetworkBig = Network<BigNetworkArchitecture, BigFeatureTransformer>;
|
||||
using NetworkSmall = Network<SmallNetworkArchitecture, SmallFeatureTransformer>;
|
||||
|
||||
|
||||
struct Networks {
|
||||
Networks(EvalFile bigFile, EvalFile smallFile) :
|
||||
big(bigFile, EmbeddedNNUEType::BIG),
|
||||
small(smallFile, EmbeddedNNUEType::SMALL) {}
|
||||
|
||||
NetworkBig big;
|
||||
NetworkSmall small;
|
||||
friend struct AccumulatorCaches;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Stockfish
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
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 {
|
||||
template<>
|
||||
struct std::hash<Stockfish::Eval::NNUE::Network> {
|
||||
Stockfish::usize operator()(const Stockfish::Eval::NNUE::Network& 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
|
||||
|
||||
+509
-647
File diff suppressed because it is too large
Load Diff
+55
-125
@@ -23,11 +23,10 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "../types.h"
|
||||
#include "../misc.h"
|
||||
#include "nnue_architecture.h"
|
||||
#include "nnue_common.h"
|
||||
|
||||
@@ -37,18 +36,16 @@ class Position;
|
||||
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
template<IndexType Size>
|
||||
struct alignas(CacheLineSize) Accumulator;
|
||||
|
||||
template<IndexType TransformedFeatureDimensions>
|
||||
class FeatureTransformer;
|
||||
|
||||
// Class that holds the result of affine transformation of input features
|
||||
template<IndexType Size>
|
||||
// Class that holds the result of affine transformation of input features,
|
||||
// combined HalfKA + Threats
|
||||
struct alignas(CacheLineSize) Accumulator {
|
||||
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 = {};
|
||||
std::array<std::array<i16, L1>, COLOR_NB> accumulation;
|
||||
std::array<std::array<i32, PSQTBuckets>, COLOR_NB> psqtAccumulation;
|
||||
std::array<bool, COLOR_NB> computed = {};
|
||||
};
|
||||
|
||||
|
||||
@@ -59,146 +56,79 @@ struct alignas(CacheLineSize) Accumulator {
|
||||
// This idea, was first described by Luecx (author of Koivisto) and
|
||||
// is commonly referred to as "Finny Tables".
|
||||
struct AccumulatorCaches {
|
||||
|
||||
template<typename Networks>
|
||||
AccumulatorCaches(const Networks& networks) {
|
||||
clear(networks);
|
||||
template<typename Network>
|
||||
AccumulatorCaches(const Network& network) {
|
||||
clear(network);
|
||||
}
|
||||
|
||||
template<IndexType Size>
|
||||
struct alignas(CacheLineSize) Cache {
|
||||
struct alignas(CacheLineSize) Entry {
|
||||
std::array<BiasType, L1> accumulation;
|
||||
std::array<PSQTWeightType, PSQTBuckets> psqtAccumulation;
|
||||
std::array<Piece, SQUARE_NB> pieces;
|
||||
Bitboard pieceBB;
|
||||
|
||||
struct alignas(CacheLineSize) Entry {
|
||||
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 std::array<BiasType, Size>& biases) {
|
||||
accumulation = biases;
|
||||
std::memset(reinterpret_cast<std::byte*>(this) + offsetof(Entry, psqtAccumulation),
|
||||
0, sizeof(Entry) - offsetof(Entry, psqtAccumulation));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Network>
|
||||
void clear(const Network& network) {
|
||||
for (auto& entries1D : entries)
|
||||
for (auto& entry : entries1D)
|
||||
entry.clear(network.featureTransformer.biases);
|
||||
// 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 std::array<BiasType, L1>& biases) {
|
||||
accumulation = biases;
|
||||
std::memset(reinterpret_cast<std::byte*>(this) + offsetof(Entry, psqtAccumulation), 0,
|
||||
sizeof(Entry) - offsetof(Entry, psqtAccumulation));
|
||||
}
|
||||
|
||||
std::array<Entry, COLOR_NB>& operator[](Square sq) { return entries[sq]; }
|
||||
|
||||
std::array<std::array<Entry, COLOR_NB>, SQUARE_NB> entries;
|
||||
};
|
||||
|
||||
template<typename Networks>
|
||||
void clear(const Networks& networks) {
|
||||
big.clear(networks.big);
|
||||
small.clear(networks.small);
|
||||
template<typename Network>
|
||||
void clear(const Network& network) {
|
||||
for (auto& entries1D : entries)
|
||||
for (auto& entry : entries1D)
|
||||
entry.clear(network.featureTransformer.biases);
|
||||
}
|
||||
|
||||
Cache<TransformedFeatureDimensionsBig> big;
|
||||
Cache<TransformedFeatureDimensionsSmall> small;
|
||||
std::array<Entry, COLOR_NB>& operator[](Square sq) { return entries[sq]; }
|
||||
|
||||
std::array<std::array<Entry, COLOR_NB>, SQUARE_NB> entries;
|
||||
};
|
||||
|
||||
|
||||
template<typename FeatureSet>
|
||||
struct AccumulatorState {
|
||||
Accumulator<TransformedFeatureDimensionsBig> accumulatorBig;
|
||||
Accumulator<TransformedFeatureDimensionsSmall> accumulatorSmall;
|
||||
typename FeatureSet::DiffType diff;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
struct AccumulatorState: public Accumulator, Dirties {};
|
||||
|
||||
class AccumulatorStack {
|
||||
public:
|
||||
static constexpr std::size_t MaxSize = MAX_PLY + 1;
|
||||
static constexpr usize MaxSize = MAX_PLY + 1;
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] const AccumulatorState<T>& latest() const noexcept;
|
||||
[[nodiscard]] const AccumulatorState& latest() const noexcept;
|
||||
|
||||
void reset() noexcept;
|
||||
std::pair<DirtyPiece&, DirtyThreats&> push() noexcept;
|
||||
void pop() noexcept;
|
||||
void reset() noexcept;
|
||||
Dirties& push() noexcept;
|
||||
void pop() noexcept;
|
||||
|
||||
template<IndexType Dimensions>
|
||||
void evaluate(const Position& pos,
|
||||
const FeatureTransformer<Dimensions>& featureTransformer,
|
||||
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
|
||||
void evaluate(const Position& pos,
|
||||
const FeatureTransformer& featureTransformer,
|
||||
// Silence spurious warning on GCC 10
|
||||
[[maybe_unused]] AccumulatorCaches& cache) noexcept;
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
[[nodiscard]] AccumulatorState<T>& mut_latest() noexcept;
|
||||
[[nodiscard]] AccumulatorState& mut_latest() noexcept;
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] const std::array<AccumulatorState<T>, MaxSize>& accumulators() const noexcept;
|
||||
void evaluate_side(Color perspective,
|
||||
const Position& pos,
|
||||
const FeatureTransformer& featureTransformer,
|
||||
// Silence spurious warning on GCC 10
|
||||
[[maybe_unused]] AccumulatorCaches& cache) noexcept;
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::array<AccumulatorState<T>, MaxSize>& mut_accumulators() noexcept;
|
||||
[[nodiscard]] usize find_last_usable_accumulator(Color perspective) const noexcept;
|
||||
|
||||
template<typename FeatureSet, IndexType Dimensions>
|
||||
void evaluate_side(Color perspective,
|
||||
const Position& pos,
|
||||
const FeatureTransformer<Dimensions>& featureTransformer,
|
||||
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
|
||||
void forward_update_incremental(Color perspective,
|
||||
const Position& pos,
|
||||
const FeatureTransformer& featureTransformer,
|
||||
const usize begin) noexcept;
|
||||
|
||||
template<typename FeatureSet, IndexType Dimensions>
|
||||
[[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept;
|
||||
void backward_update_incremental(Color perspective,
|
||||
const Position& pos,
|
||||
const FeatureTransformer& featureTransformer,
|
||||
const usize end) noexcept;
|
||||
|
||||
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;
|
||||
std::array<AccumulatorState, MaxSize> accumulators;
|
||||
usize size = 1;
|
||||
};
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
@@ -22,31 +22,29 @@
|
||||
#define NNUE_ARCHITECTURE_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iosfwd>
|
||||
|
||||
#include "features/half_ka_v2_hm.h"
|
||||
#include "features/full_threats.h"
|
||||
#include "features/pp_3wide.h"
|
||||
#include "layers/affine_transform.h"
|
||||
#include "layers/affine_transform_sparse_input.h"
|
||||
#include "layers/clipped_relu.h"
|
||||
#include "layers/sqr_clipped_relu.h"
|
||||
#include "nnue_common.h"
|
||||
#include "nnz_helper.h"
|
||||
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
// Input features used in evaluation function
|
||||
using ThreatFeatureSet = Features::FullThreats;
|
||||
using PairFeatureSet = Features::PP_3Wide;
|
||||
using PSQFeatureSet = Features::HalfKAv2_hm;
|
||||
|
||||
// Number of input feature dimensions after conversion
|
||||
constexpr IndexType TransformedFeatureDimensionsBig = 1024;
|
||||
constexpr int L2Big = 15;
|
||||
constexpr int L3Big = 32;
|
||||
|
||||
constexpr IndexType TransformedFeatureDimensionsSmall = 128;
|
||||
constexpr int L2Small = 15;
|
||||
constexpr int L3Small = 32;
|
||||
constexpr IndexType L1 = 1024;
|
||||
constexpr int L2 = 32;
|
||||
constexpr int L3 = 32;
|
||||
|
||||
constexpr IndexType PSQTBuckets = 8;
|
||||
constexpr IndexType LayerStacks = 8;
|
||||
@@ -57,26 +55,33 @@ constexpr IndexType LayerStacks = 8;
|
||||
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;
|
||||
static constexpr int FC_0_OUTPUTS = L2;
|
||||
static constexpr int FC_1_OUTPUTS = L3;
|
||||
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
|
||||
static constexpr bool UsePairedActivations = true;
|
||||
#else
|
||||
static constexpr bool UsePairedActivations = false;
|
||||
#endif
|
||||
|
||||
Layers::AffineTransformSparseInput<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
|
||||
Layers::SqrClippedReLU<FC_0_OUTPUTS + 1> ac_sqr_0;
|
||||
Layers::ClippedReLU<FC_0_OUTPUTS + 1> ac_0;
|
||||
Layers::AffineTransform<FC_0_OUTPUTS * 2, FC_1_OUTPUTS> fc_1;
|
||||
Layers::ClippedReLU<FC_1_OUTPUTS> ac_1;
|
||||
Layers::AffineTransform<FC_1_OUTPUTS, 1> fc_2;
|
||||
Layers::AffineTransformSparseInput<TransformedFeatureDimensions, FC_0_OUTPUTS> fc_0;
|
||||
Layers::SqrClippedReLU<FC_0_OUTPUTS, WeightScaleBits + 1> ac_sqr_0;
|
||||
Layers::ClippedReLU<FC_0_OUTPUTS, WeightScaleBits + 1> ac_0;
|
||||
Layers::AffineTransform<FC_0_OUTPUTS * 2, FC_1_OUTPUTS, UsePairedActivations> fc_1;
|
||||
Layers::SqrClippedReLU<FC_1_OUTPUTS, WeightScaleBits> ac_sqr_1;
|
||||
Layers::ClippedReLU<FC_1_OUTPUTS, WeightScaleBits> ac_1;
|
||||
Layers::AffineTransform<FC_0_OUTPUTS * 2 + FC_1_OUTPUTS * 2, 1, UsePairedActivations> fc_2;
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t get_hash_value() {
|
||||
static constexpr u32 get_hash_value() {
|
||||
// input slice hash
|
||||
std::uint32_t hashValue = 0xEC42E90Du;
|
||||
u32 hashValue = 0xEC42E90Du;
|
||||
hashValue ^= TransformedFeatureDimensions * 2;
|
||||
|
||||
hashValue = decltype(fc_0)::get_hash_value(hashValue);
|
||||
// TODO: consider including hash value of ac_sqr_0 in the overall hash value.
|
||||
// For now omitted on purpose because hash value is not written by trainer (yet)
|
||||
hashValue = decltype(ac_0)::get_hash_value(hashValue);
|
||||
hashValue = decltype(fc_1)::get_hash_value(hashValue);
|
||||
hashValue = decltype(ac_1)::get_hash_value(hashValue);
|
||||
@@ -99,52 +104,61 @@ struct NetworkArchitecture {
|
||||
&& fc_2.write_parameters(stream);
|
||||
}
|
||||
|
||||
std::int32_t propagate(const TransformedFeatureType* transformedFeatures) const {
|
||||
i32 propagate(const TransformedFeatureType* transformedFeatures,
|
||||
const NNZInfo<L1>& nnzInfo) const {
|
||||
struct alignas(CacheLineSize) Buffer {
|
||||
alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out;
|
||||
alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType
|
||||
ac_sqr_0_out[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2, 32)];
|
||||
alignas(CacheLineSize) typename decltype(ac_0)::OutputBuffer ac_0_out;
|
||||
concat_buffer[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2 + FC_1_OUTPUTS * 2, 32)];
|
||||
alignas(CacheLineSize) typename decltype(fc_1)::OutputBuffer fc_1_out;
|
||||
alignas(CacheLineSize) typename decltype(ac_1)::OutputBuffer ac_1_out;
|
||||
alignas(CacheLineSize) typename decltype(fc_2)::OutputBuffer fc_2_out;
|
||||
|
||||
Buffer() { std::memset(this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
#if defined(__clang__) && (__APPLE__)
|
||||
// workaround for a bug reported with xcode 12
|
||||
static thread_local auto tlsBuffer = std::make_unique<Buffer>();
|
||||
// Access TLS only once, cache result.
|
||||
Buffer& buffer = *tlsBuffer;
|
||||
Buffer buffer;
|
||||
|
||||
fc_0.propagate(transformedFeatures, buffer.fc_0_out, nnzInfo);
|
||||
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
|
||||
ac_sqr_0.propagate_pair(buffer.fc_0_out, buffer.concat_buffer,
|
||||
buffer.concat_buffer + FC_0_OUTPUTS);
|
||||
#else
|
||||
alignas(CacheLineSize) static thread_local Buffer buffer;
|
||||
ac_sqr_0.propagate(buffer.fc_0_out, buffer.concat_buffer);
|
||||
ac_0.propagate(buffer.fc_0_out, buffer.concat_buffer + FC_0_OUTPUTS);
|
||||
#endif
|
||||
|
||||
fc_0.propagate(transformedFeatures, buffer.fc_0_out);
|
||||
ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out);
|
||||
ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out);
|
||||
std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out,
|
||||
FC_0_OUTPUTS * sizeof(typename decltype(ac_0)::OutputType));
|
||||
fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out);
|
||||
ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out);
|
||||
fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out);
|
||||
fc_1.propagate(buffer.concat_buffer, buffer.fc_1_out);
|
||||
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
|
||||
ac_sqr_1.propagate_pair(buffer.fc_1_out, buffer.concat_buffer + FC_0_OUTPUTS * 2,
|
||||
buffer.concat_buffer + FC_0_OUTPUTS * 2 + FC_1_OUTPUTS);
|
||||
#else
|
||||
ac_sqr_1.propagate(buffer.fc_1_out, buffer.concat_buffer + FC_0_OUTPUTS * 2);
|
||||
ac_1.propagate(buffer.fc_1_out, buffer.concat_buffer + FC_0_OUTPUTS * 2 + FC_1_OUTPUTS);
|
||||
#endif
|
||||
|
||||
// buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in
|
||||
fc_2.propagate(buffer.concat_buffer, buffer.fc_2_out);
|
||||
|
||||
static_assert(FC_0_OUTPUTS >= 2);
|
||||
i32 fwdOut = buffer.fc_2_out[0];
|
||||
i32 skip_0 = buffer.fc_0_out[FC_0_OUTPUTS - 2] - buffer.fc_0_out[FC_0_OUTPUTS - 1];
|
||||
fwdOut += skip_0;
|
||||
|
||||
// fwdOut is such that 1.0 is equal to HiddenOneVal*(1<<WeightScaleBits)*2 in
|
||||
// quantized form, but we want 1.0 to be equal to 600*OutputScale
|
||||
std::int32_t fwdOut =
|
||||
(buffer.fc_0_out[FC_0_OUTPUTS]) * (600 * OutputScale) / (127 * (1 << WeightScaleBits));
|
||||
std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
|
||||
// to make overflow impossible we cast to int64_t
|
||||
constexpr i64 multiplier = 600 * OutputScale;
|
||||
constexpr i64 denominator =
|
||||
static_cast<i64>(HiddenOneVal) * static_cast<i64>(1U << WeightScaleBits) * 2;
|
||||
|
||||
i32 outputValue = static_cast<i32>((static_cast<i64>(fwdOut) * multiplier) / denominator);
|
||||
return outputValue;
|
||||
}
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
usize get_content_hash() const {
|
||||
usize 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_sqr_1.get_content_hash()); TODO
|
||||
hash_combine(h, ac_1.get_content_hash());
|
||||
hash_combine(h, fc_2.get_content_hash());
|
||||
hash_combine(h, get_hash_value());
|
||||
@@ -154,10 +168,10 @@ struct NetworkArchitecture {
|
||||
|
||||
} // 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 {
|
||||
template<>
|
||||
struct std::hash<Stockfish::Eval::NNUE::NetworkArchitecture> {
|
||||
Stockfish::usize
|
||||
operator()(const Stockfish::Eval::NNUE::NetworkArchitecture& arch) const noexcept {
|
||||
return arch.get_content_hash();
|
||||
}
|
||||
};
|
||||
|
||||
+83
-49
@@ -42,46 +42,61 @@
|
||||
#elif defined(USE_SSE2)
|
||||
#include <emmintrin.h>
|
||||
|
||||
#elif defined(USE_LASX)
|
||||
#include <lasxintrin.h>
|
||||
#include <lsxintrin.h>
|
||||
|
||||
#elif defined(USE_LSX)
|
||||
#include <lsxintrin.h>
|
||||
|
||||
#elif defined(USE_NEON)
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
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;
|
||||
using BiasType = i16;
|
||||
using ThreatWeightType = i8;
|
||||
using WeightType = i16;
|
||||
using PSQTWeightType = i32;
|
||||
using IndexType = u32;
|
||||
|
||||
// Version of the evaluation file
|
||||
constexpr std::uint32_t Version = 0x7AF32F20u;
|
||||
constexpr u32 Version = 0x6A448AFAu;
|
||||
|
||||
// Constant used in evaluation value calculation
|
||||
constexpr int OutputScale = 16;
|
||||
constexpr int WeightScaleBits = 6;
|
||||
constexpr int FtMaxVal = 255;
|
||||
constexpr int HiddenOneVal = 128;
|
||||
|
||||
// Size of cache line (in bytes)
|
||||
constexpr std::size_t CacheLineSize = 64;
|
||||
constexpr usize CacheLineSize = 64;
|
||||
|
||||
constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128";
|
||||
constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;
|
||||
constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128";
|
||||
constexpr const usize Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;
|
||||
|
||||
// SIMD width (in bytes)
|
||||
#if defined(USE_AVX2)
|
||||
constexpr std::size_t SimdWidth = 32;
|
||||
constexpr usize SimdWidth = 32;
|
||||
|
||||
#elif defined(USE_LASX)
|
||||
constexpr usize SimdWidth = 32;
|
||||
|
||||
#elif defined(USE_SSE2)
|
||||
constexpr std::size_t SimdWidth = 16;
|
||||
constexpr usize SimdWidth = 16;
|
||||
|
||||
#elif defined(USE_NEON)
|
||||
constexpr std::size_t SimdWidth = 16;
|
||||
constexpr usize SimdWidth = 16;
|
||||
|
||||
#elif defined(USE_LSX)
|
||||
constexpr usize SimdWidth = 16;
|
||||
#endif
|
||||
|
||||
constexpr std::size_t MaxSimdWidth = 32;
|
||||
constexpr usize MaxSimdWidth = 32;
|
||||
|
||||
// Type of input feature after conversion
|
||||
using TransformedFeatureType = std::uint8_t;
|
||||
using TransformedFeatureType = u8;
|
||||
|
||||
// Round n up to be a multiple of base
|
||||
template<typename IntType>
|
||||
@@ -101,11 +116,11 @@ inline IntType read_little_endian(std::istream& stream) {
|
||||
stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
|
||||
else
|
||||
{
|
||||
std::uint8_t u[sizeof(IntType)];
|
||||
u8 u[sizeof(IntType)];
|
||||
std::make_unsigned_t<IntType> v = 0;
|
||||
|
||||
stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
|
||||
for (std::size_t i = 0; i < sizeof(IntType); ++i)
|
||||
for (usize i = 0; i < sizeof(IntType); ++i)
|
||||
v = (v << 8) | u[sizeof(IntType) - i - 1];
|
||||
|
||||
std::memcpy(&result, &v, sizeof(IntType));
|
||||
@@ -126,20 +141,20 @@ inline void write_little_endian(std::ostream& stream, IntType value) {
|
||||
stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
|
||||
else
|
||||
{
|
||||
std::uint8_t u[sizeof(IntType)];
|
||||
u8 u[sizeof(IntType)];
|
||||
std::make_unsigned_t<IntType> v = value;
|
||||
|
||||
std::size_t i = 0;
|
||||
usize i = 0;
|
||||
// if constexpr to silence the warning about shift by 8
|
||||
if constexpr (sizeof(IntType) > 1)
|
||||
{
|
||||
for (; i + 1 < sizeof(IntType); ++i)
|
||||
{
|
||||
u[i] = std::uint8_t(v);
|
||||
u[i] = u8(v);
|
||||
v >>= 8;
|
||||
}
|
||||
}
|
||||
u[i] = std::uint8_t(v);
|
||||
u[i] = u8(v);
|
||||
|
||||
stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
|
||||
}
|
||||
@@ -149,11 +164,11 @@ inline void write_little_endian(std::ostream& stream, IntType value) {
|
||||
// Read integers in bulk from a little-endian stream.
|
||||
// This reads N integers from stream s and puts them in array out.
|
||||
template<typename IntType>
|
||||
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
|
||||
inline void read_little_endian(std::istream& stream, IntType* out, usize count) {
|
||||
if (IsLittleEndian)
|
||||
stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
|
||||
else
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
for (usize i = 0; i < count; ++i)
|
||||
out[i] = read_little_endian<IntType>(stream);
|
||||
}
|
||||
|
||||
@@ -161,39 +176,36 @@ inline void read_little_endian(std::istream& stream, IntType* out, std::size_t c
|
||||
// Write integers in bulk to a little-endian stream.
|
||||
// This takes N integers from array values and writes them on stream s.
|
||||
template<typename IntType>
|
||||
inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
|
||||
inline void write_little_endian(std::ostream& stream, const IntType* values, usize count) {
|
||||
if (IsLittleEndian)
|
||||
stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
|
||||
else
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
for (usize i = 0; i < count; ++i)
|
||||
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 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) {
|
||||
template<typename BufType, typename IntType>
|
||||
inline void read_leb_128_detail(
|
||||
std::istream& stream, IntType* out, usize Count, u32& bytes_left, BufType& buf, u32& 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;
|
||||
usize 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()));
|
||||
std::min(usize(bytes_left), buf.size()));
|
||||
buf_pos = 0;
|
||||
}
|
||||
|
||||
std::uint8_t byte = buf[buf_pos++];
|
||||
u8 byte = buf[buf_pos++];
|
||||
--bytes_left;
|
||||
result |= (byte & 0x7f) << (shift % 32);
|
||||
shift += 7;
|
||||
@@ -214,11 +226,28 @@ inline void read_leb_128(std::istream& stream, Arrays&... outs) {
|
||||
stream.read(leb128MagicString, Leb128MagicStringSize);
|
||||
assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);
|
||||
|
||||
auto bytes_left = read_little_endian<std::uint32_t>(stream);
|
||||
std::array<std::uint8_t, 8192> buf;
|
||||
std::uint32_t buf_pos = buf.size();
|
||||
auto bytes_left = read_little_endian<u32>(stream);
|
||||
std::array<u8, 8192> buf;
|
||||
u32 buf_pos = u32(buf.size());
|
||||
|
||||
(read_leb_128_detail(stream, outs, bytes_left, buf, buf_pos), ...);
|
||||
(read_leb_128_detail(stream, outs.data(), outs.size(), bytes_left, buf, buf_pos), ...);
|
||||
|
||||
assert(bytes_left == 0);
|
||||
}
|
||||
|
||||
|
||||
template<typename IntType>
|
||||
inline void read_leb_128(std::istream& stream, IntType* out, usize expected) {
|
||||
// Check the presence of our LEB128 magic string
|
||||
char leb128MagicString[Leb128MagicStringSize];
|
||||
stream.read(leb128MagicString, Leb128MagicStringSize);
|
||||
assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);
|
||||
|
||||
auto bytes_left = read_little_endian<u32>(stream);
|
||||
std::array<u8, 8192> buf;
|
||||
u32 buf_pos = u32(buf.size());
|
||||
|
||||
read_leb_128_detail(stream, out, expected, bytes_left, buf, buf_pos);
|
||||
|
||||
assert(bytes_left == 0);
|
||||
}
|
||||
@@ -228,19 +257,19 @@ inline void read_leb_128(std::istream& stream, Arrays&... outs) {
|
||||
// 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, std::size_t Count>
|
||||
inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>& values) {
|
||||
template<typename IntType>
|
||||
inline void write_leb_128(std::ostream& stream, const IntType* values, usize Count) {
|
||||
|
||||
// Write our LEB128 magic string
|
||||
stream.write(Leb128MagicString, Leb128MagicStringSize);
|
||||
|
||||
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)
|
||||
u32 byte_count = 0;
|
||||
for (usize i = 0; i < Count; ++i)
|
||||
{
|
||||
IntType value = values[i];
|
||||
std::uint8_t byte;
|
||||
IntType value = values[i];
|
||||
u8 byte;
|
||||
do
|
||||
{
|
||||
byte = value & 0x7f;
|
||||
@@ -251,9 +280,9 @@ inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>
|
||||
|
||||
write_little_endian(stream, byte_count);
|
||||
|
||||
const std::uint32_t BUF_SIZE = 4096;
|
||||
std::uint8_t buf[BUF_SIZE];
|
||||
std::uint32_t buf_pos = 0;
|
||||
const u32 BUF_SIZE = 4096;
|
||||
u8 buf[BUF_SIZE];
|
||||
u32 buf_pos = 0;
|
||||
|
||||
auto flush = [&]() {
|
||||
if (buf_pos > 0)
|
||||
@@ -263,18 +292,18 @@ inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>
|
||||
}
|
||||
};
|
||||
|
||||
auto write = [&](std::uint8_t b) {
|
||||
auto write = [&](u8 b) {
|
||||
buf[buf_pos++] = b;
|
||||
if (buf_pos == BUF_SIZE)
|
||||
flush();
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < Count; ++i)
|
||||
for (usize i = 0; i < Count; ++i)
|
||||
{
|
||||
IntType value = values[i];
|
||||
while (true)
|
||||
{
|
||||
std::uint8_t byte = value & 0x7f;
|
||||
u8 byte = value & 0x7f;
|
||||
value >>= 7;
|
||||
if ((byte & 0x40) == 0 ? value == 0 : value == -1)
|
||||
{
|
||||
@@ -288,6 +317,11 @@ inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>
|
||||
flush();
|
||||
}
|
||||
|
||||
template<typename IntType, usize Count>
|
||||
inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>& values) {
|
||||
write_leb_128(stream, values.data(), Count);
|
||||
}
|
||||
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
#endif // #ifndef NNUE_COMMON_H_INCLUDED
|
||||
|
||||
+194
-173
@@ -37,35 +37,34 @@
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
// Returns the inverse of a permutation
|
||||
template<std::size_t Len>
|
||||
constexpr std::array<std::size_t, Len>
|
||||
invert_permutation(const std::array<std::size_t, Len>& order) {
|
||||
std::array<std::size_t, Len> inverse{};
|
||||
for (std::size_t i = 0; i < order.size(); i++)
|
||||
template<usize Len>
|
||||
constexpr std::array<usize, Len> invert_permutation(const std::array<usize, Len>& order) {
|
||||
std::array<usize, Len> inverse{};
|
||||
for (usize i = 0; i < order.size(); i++)
|
||||
inverse[order[i]] = i;
|
||||
return inverse;
|
||||
}
|
||||
|
||||
// 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(std::array<T, N>& data, const std::array<std::size_t, OrderSize>& order) {
|
||||
constexpr std::size_t TotalSize = N * sizeof(T);
|
||||
template<usize BlockSize, typename T, usize N, usize OrderSize>
|
||||
void permute(std::array<T, N>& data, const std::array<usize, OrderSize>& order) {
|
||||
constexpr usize TotalSize = N * sizeof(T);
|
||||
|
||||
static_assert(TotalSize % (BlockSize * OrderSize) == 0,
|
||||
"ChunkSize * OrderSize must perfectly divide TotalSize");
|
||||
|
||||
constexpr std::size_t ProcessChunkSize = BlockSize * OrderSize;
|
||||
constexpr usize ProcessChunkSize = BlockSize * OrderSize;
|
||||
|
||||
std::array<std::byte, ProcessChunkSize> buffer{};
|
||||
|
||||
std::byte* const bytes = reinterpret_cast<std::byte*>(data.data());
|
||||
|
||||
for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize)
|
||||
for (usize i = 0; i < TotalSize; i += ProcessChunkSize)
|
||||
{
|
||||
std::byte* const values = &bytes[i];
|
||||
|
||||
for (std::size_t j = 0; j < OrderSize; j++)
|
||||
for (usize j = 0; j < OrderSize; j++)
|
||||
{
|
||||
auto* const buffer_chunk = &buffer[j * BlockSize];
|
||||
auto* const value_chunk = &values[order[j] * BlockSize];
|
||||
@@ -78,38 +77,35 @@ void permute(std::array<T, N>& data, const std::array<std::size_t, OrderSize>& o
|
||||
}
|
||||
|
||||
// Input feature converter
|
||||
template<IndexType TransformedFeatureDimensions>
|
||||
class FeatureTransformer {
|
||||
static constexpr bool UseThreats =
|
||||
(TransformedFeatureDimensions == TransformedFeatureDimensionsBig);
|
||||
// Number of output dimensions for one side
|
||||
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
|
||||
static constexpr IndexType HalfDimensions = L1;
|
||||
|
||||
public:
|
||||
// Output type
|
||||
using OutputType = TransformedFeatureType;
|
||||
|
||||
// Number of input/output dimensions
|
||||
static constexpr IndexType InputDimensions = PSQFeatureSet::Dimensions;
|
||||
static constexpr IndexType ThreatInputDimensions = ThreatFeatureSet::Dimensions;
|
||||
static constexpr IndexType TotalInputDimensions =
|
||||
InputDimensions + (UseThreats ? ThreatInputDimensions : 0);
|
||||
static constexpr IndexType PairInputDimensions = PairFeatureSet::Dimensions;
|
||||
static constexpr IndexType InputDimensions =
|
||||
PSQFeatureSet::Dimensions + ThreatInputDimensions + PairInputDimensions;
|
||||
static constexpr IndexType OutputDimensions = HalfDimensions;
|
||||
|
||||
// Size of forward propagation buffer
|
||||
static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType);
|
||||
static constexpr usize BufferSize = OutputDimensions * sizeof(OutputType);
|
||||
|
||||
// Store the order by which 128-bit blocks of a 1024-bit data must
|
||||
// be permuted so that calling packus on adjacent vectors of 16-bit
|
||||
// integers loaded from the data results in the pre-permutation order
|
||||
static constexpr auto PackusEpi16Order = []() -> std::array<std::size_t, 8> {
|
||||
static constexpr auto PackusEpi16Order = []() -> std::array<usize, 8> {
|
||||
#if defined(USE_AVX512)
|
||||
// _mm512_packus_epi16 after permutation:
|
||||
// | 0 | 2 | 4 | 6 | // Vector 0
|
||||
// | 1 | 3 | 5 | 7 | // Vector 1
|
||||
// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | // Packed Result
|
||||
return {0, 2, 4, 6, 1, 3, 5, 7};
|
||||
#elif defined(USE_AVX2)
|
||||
#elif defined(USE_AVX2) || defined(USE_LASX)
|
||||
// _mm256_packus_epi16 after permutation:
|
||||
// | 0 | 2 | | 4 | 6 | // Vector 0, 2
|
||||
// | 1 | 3 | | 5 | 7 | // Vector 1, 3
|
||||
@@ -122,9 +118,20 @@ class FeatureTransformer {
|
||||
|
||||
static constexpr auto InversePackusEpi16Order = invert_permutation(PackusEpi16Order);
|
||||
|
||||
static constexpr u32 combine_hash(std::initializer_list<u32> hashes) {
|
||||
u32 hash = 0;
|
||||
for (const auto component_hash : hashes)
|
||||
{
|
||||
hash = (hash << 1) | (hash >> 31);
|
||||
hash ^= component_hash;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Hash value embedded in the evaluation file
|
||||
static constexpr std::uint32_t get_hash_value() {
|
||||
return (UseThreats ? ThreatFeatureSet::HashValue : PSQFeatureSet::HashValue)
|
||||
static constexpr u32 get_hash_value() {
|
||||
return combine_hash(
|
||||
{ThreatFeatureSet::HashValue, PairFeatureSet::HashValue, PSQFeatureSet::HashValue})
|
||||
^ (OutputDimensions * 2);
|
||||
}
|
||||
|
||||
@@ -132,48 +139,48 @@ class FeatureTransformer {
|
||||
permute<16>(biases, PackusEpi16Order);
|
||||
permute<16>(weights, PackusEpi16Order);
|
||||
|
||||
if constexpr (UseThreats)
|
||||
permute<8>(threatWeights, PackusEpi16Order);
|
||||
permute<8>(threatAndPpWeights, PackusEpi16Order);
|
||||
}
|
||||
|
||||
void unpermute_weights() {
|
||||
permute<16>(biases, InversePackusEpi16Order);
|
||||
permute<16>(weights, InversePackusEpi16Order);
|
||||
|
||||
if constexpr (UseThreats)
|
||||
permute<8>(threatWeights, InversePackusEpi16Order);
|
||||
permute<8>(threatAndPpWeights, InversePackusEpi16Order);
|
||||
}
|
||||
|
||||
inline void scale_weights(bool read) {
|
||||
for (auto& w : weights)
|
||||
w = read ? w * 2 : w / 2;
|
||||
for (auto& b : biases)
|
||||
b = read ? b * 2 : b / 2;
|
||||
auto threatWeights() { return threatAndPpWeights.data(); }
|
||||
auto threatWeights() const { return threatAndPpWeights.data(); }
|
||||
auto ppWeights() { return &threatAndPpWeights[ThreatFeatureSet::Dimensions * HalfDimensions]; }
|
||||
auto ppWeights() const {
|
||||
return &threatAndPpWeights[ThreatFeatureSet::Dimensions * HalfDimensions];
|
||||
}
|
||||
|
||||
auto threatPsqtWeights() { return threatAndPpPsqtWeights.data(); }
|
||||
auto threatPsqtWeights() const { return threatAndPpPsqtWeights.data(); }
|
||||
auto ppPsqtWeights() {
|
||||
return &threatAndPpPsqtWeights[ThreatFeatureSet::Dimensions * PSQTBuckets];
|
||||
}
|
||||
auto ppPsqtWeights() const {
|
||||
return &threatAndPpPsqtWeights[ThreatFeatureSet::Dimensions * PSQTBuckets];
|
||||
}
|
||||
|
||||
|
||||
// Read network parameters
|
||||
bool read_parameters(std::istream& stream) {
|
||||
read_leb_128(stream, biases);
|
||||
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
read_little_endian<ThreatWeightType>(stream, threatWeights.data(),
|
||||
ThreatInputDimensions * HalfDimensions);
|
||||
read_leb_128(stream, weights);
|
||||
read_little_endian<ThreatWeightType>(stream, threatWeights(),
|
||||
ThreatInputDimensions * HalfDimensions);
|
||||
read_leb_128(stream, threatPsqtWeights(), ThreatFeatureSet::Dimensions * PSQTBuckets);
|
||||
read_little_endian<ThreatWeightType>(stream, ppWeights(),
|
||||
PairInputDimensions * HalfDimensions);
|
||||
read_leb_128(stream, ppPsqtWeights(), PairFeatureSet::Dimensions * PSQTBuckets);
|
||||
|
||||
read_leb_128(stream, threatPsqtWeights, psqtWeights);
|
||||
}
|
||||
else
|
||||
{
|
||||
read_leb_128(stream, weights);
|
||||
read_leb_128(stream, psqtWeights);
|
||||
}
|
||||
read_leb_128(stream, weights);
|
||||
read_leb_128(stream, psqtWeights);
|
||||
|
||||
permute_weights();
|
||||
|
||||
if constexpr (!UseThreats)
|
||||
scale_weights(true);
|
||||
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
@@ -183,92 +190,74 @@ class FeatureTransformer {
|
||||
|
||||
copy->unpermute_weights();
|
||||
|
||||
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>>();
|
||||
write_little_endian<ThreatWeightType>(stream, copy->threatWeights(),
|
||||
ThreatInputDimensions * HalfDimensions);
|
||||
write_leb_128<PSQTWeightType>(stream, copy->threatPsqtWeights(),
|
||||
ThreatFeatureSet::Dimensions * PSQTBuckets);
|
||||
write_little_endian<ThreatWeightType>(stream, copy->ppWeights(),
|
||||
PairInputDimensions * HalfDimensions);
|
||||
write_leb_128<PSQTWeightType>(stream, copy->ppPsqtWeights(),
|
||||
PairFeatureSet::Dimensions * 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);
|
||||
}
|
||||
write_leb_128<WeightType>(stream, copy->weights);
|
||||
write_leb_128<PSQTWeightType>(stream, copy->psqtWeights);
|
||||
|
||||
return !stream.fail();
|
||||
}
|
||||
|
||||
std::size_t get_content_hash() const {
|
||||
std::size_t h = 0;
|
||||
usize get_content_hash() const {
|
||||
usize 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_raw_data_hash(threatAndPpWeights));
|
||||
hash_combine(h, get_raw_data_hash(threatAndPpPsqtWeights));
|
||||
|
||||
hash_combine(h, get_hash_value());
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
// Convert input features
|
||||
std::int32_t transform(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches::Cache<HalfDimensions>& cache,
|
||||
OutputType* output,
|
||||
int bucket) const {
|
||||
i32 transform(const Position& pos,
|
||||
AccumulatorStack& accumulatorStack,
|
||||
AccumulatorCaches& cache,
|
||||
OutputType* output,
|
||||
int bucket,
|
||||
NNZInfo<OutputDimensions>& nnzInfo) const {
|
||||
|
||||
using namespace SIMD;
|
||||
accumulatorStack.evaluate(pos, *this, cache);
|
||||
const auto& accumulatorState = accumulatorStack.latest<PSQFeatureSet>();
|
||||
const auto& threatAccumulatorState = accumulatorStack.latest<ThreatFeatureSet>();
|
||||
const auto& accumulatorState = accumulatorStack.latest();
|
||||
|
||||
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
|
||||
const auto& psqtAccumulation = (accumulatorState.acc<HalfDimensions>()).psqtAccumulation;
|
||||
auto psqt =
|
||||
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]);
|
||||
const auto& psqtAccumulation = accumulatorState.psqtAccumulation;
|
||||
const auto psqt =
|
||||
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket])
|
||||
/ 2;
|
||||
|
||||
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;
|
||||
const auto& accumulation = accumulatorState.accumulation;
|
||||
|
||||
for (IndexType p = 0; p < 2; ++p)
|
||||
{
|
||||
const IndexType offset = (HalfDimensions / 2) * p;
|
||||
|
||||
[[maybe_unused]] auto cursor = nnzInfo.make_cursor(p);
|
||||
|
||||
#if defined(VECTOR)
|
||||
|
||||
constexpr IndexType OutputChunkSize = MaxChunkSize;
|
||||
static_assert((HalfDimensions / 2) % OutputChunkSize == 0);
|
||||
constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize;
|
||||
|
||||
const vec_t Zero = vec_zero();
|
||||
const vec_t One = vec_set_16(UseThreats ? 255 : 127 * 2);
|
||||
[[maybe_unused]] const vec_t Zero = vec_zero();
|
||||
[[maybe_unused]] const vec_t FtMax = vec_set_16(FtMaxVal);
|
||||
[[maybe_unused]] constexpr int shift = 7;
|
||||
|
||||
const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
|
||||
const vec_t* in1 =
|
||||
@@ -291,7 +280,7 @@ class FeatureTransformer {
|
||||
// means our pairwise product is zero. If we perform packus and
|
||||
// remove the lower-side clip for the second element, then our
|
||||
// product before packus will be negative, and is zeroed on pack.
|
||||
// The two operation produce equivalent results, but the second
|
||||
// The two operations produce equivalent results, but the second
|
||||
// one (using packus) saves one max operation per pair.
|
||||
|
||||
// But here we run into a problem: mullo does not preserve the
|
||||
@@ -322,60 +311,97 @@ class FeatureTransformer {
|
||||
// 8 bits. Shifting it by 7 bits left will no longer occupy the
|
||||
// signed bit, so we are safe.
|
||||
|
||||
// Note that on NEON processors, we shift left by 6 instead
|
||||
// because the instruction "vqdmulhq_s16" also doubles the
|
||||
// return value after the multiplication, adding an extra shift
|
||||
// to the left by 1, so we compensate by shifting less before
|
||||
// the multiplication.
|
||||
for (IndexType j = 0; j < NumOutputChunks; j += 2)
|
||||
{
|
||||
vec_t packed[2];
|
||||
for (IndexType k = 0; k < 2; ++k)
|
||||
{
|
||||
const IndexType i = (j + k) * 2;
|
||||
|
||||
constexpr int shift =
|
||||
#if defined(USE_SSE2)
|
||||
7;
|
||||
vec_t acc0a = in0[i + 0];
|
||||
vec_t acc0b = in0[i + 1];
|
||||
vec_t acc1a = in1[i + 0];
|
||||
vec_t acc1b = in1[i + 1];
|
||||
|
||||
static_assert(FtMaxVal == 255);
|
||||
|
||||
#if defined(USE_NEON)
|
||||
uint16x8_t mul0 = vmull_u8(vqmovun_s16(acc0a), vqmovun_s16(acc1a));
|
||||
uint16x8_t mul1 = vmull_u8(vqmovun_s16(acc0b), vqmovun_s16(acc1b));
|
||||
|
||||
uint8x16x2_t uzp =
|
||||
vuzpq_u8(vreinterpretq_u8_u16(mul0), vreinterpretq_u8_u16(mul1));
|
||||
uint8x16_t pab = vshrq_n_u8(uzp.val[1], 1);
|
||||
vec_t result = reinterpret_cast<vec_t>(pab);
|
||||
#elif defined(USE_LSX) || defined(USE_LASX)
|
||||
vec_t pa = vec_packus_16(acc0a, acc0b);
|
||||
vec_t pb = vec_packus_16(acc1a, acc1b);
|
||||
|
||||
vec_t hi = vec_mulhi_8(pa, pb);
|
||||
vec_t result = vec_srli_8(hi, 1);
|
||||
#elif defined(__wasm__)
|
||||
// _mm_mulhi_epi16 is lowered to 32-bit multiplies, so we take
|
||||
// a similar approach as the NEON path.
|
||||
vec_t mul0 = vec_packus_16(acc0a, acc0b);
|
||||
vec_t mul1 = vec_packus_16(acc1a, acc1b);
|
||||
|
||||
vec_t low = wasm_u16x8_extmul_low_u8x16(mul0, mul1);
|
||||
vec_t hi = wasm_u16x8_extmul_high_u8x16(mul0, mul1);
|
||||
|
||||
// equivalent to vuzp2_u8
|
||||
vec_t merged = wasm_i8x16_shuffle(low, hi, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19,
|
||||
21, 23, 25, 27, 29, 31);
|
||||
vec_t result = wasm_u8x16_shr(merged, 1);
|
||||
#else
|
||||
6;
|
||||
vec_t sum0a = vec_slli_16(vec_max_16(vec_min_16(acc0a, FtMax), Zero), shift);
|
||||
vec_t sum0b = vec_slli_16(vec_max_16(vec_min_16(acc0b, FtMax), Zero), shift);
|
||||
vec_t sum1a = vec_min_16(acc1a, FtMax);
|
||||
vec_t sum1b = vec_min_16(acc1b, FtMax);
|
||||
|
||||
vec_t pa = vec_mulhi_16(sum0a, sum1a);
|
||||
vec_t pb = vec_mulhi_16(sum0b, sum1b);
|
||||
|
||||
vec_t result = vec_packus_16(pa, pb);
|
||||
#endif
|
||||
if constexpr (UseThreats)
|
||||
{
|
||||
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 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);
|
||||
|
||||
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);
|
||||
packed[k] = out[j + k] = result;
|
||||
}
|
||||
|
||||
cursor.record2(packed[0], packed[1]);
|
||||
}
|
||||
else
|
||||
|
||||
#elif defined(USE_RVV)
|
||||
|
||||
usize j = 0;
|
||||
|
||||
while (j < HalfDimensions / 2)
|
||||
{
|
||||
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);
|
||||
usize vl = __riscv_vsetvl_e16m8(HalfDimensions / 2 - j);
|
||||
|
||||
const vec_t pa = vec_mulhi_16(sum0a, sum1a);
|
||||
const vec_t pb = vec_mulhi_16(sum0b, sum1b);
|
||||
vint16m8_t acc0 = __riscv_vle16_v_i16m8(&accumulation[perspectives[p]][j], vl);
|
||||
vint16m8_t acc1 =
|
||||
__riscv_vle16_v_i16m8(&accumulation[perspectives[p]][j + HalfDimensions / 2], vl);
|
||||
|
||||
out[j] = vec_packus_16(pa, pb);
|
||||
}
|
||||
acc0 = __riscv_vmax_vx_i16m8(acc0, 0, vl);
|
||||
acc1 = __riscv_vmax_vx_i16m8(acc1, 0, vl);
|
||||
|
||||
vuint8m4_t pa = __riscv_vnclipu_wx_u8m4(__riscv_vreinterpret_v_i16m8_u16m8(acc0), 0,
|
||||
__RISCV_VXRM_RDN, vl);
|
||||
vuint8m4_t pb = __riscv_vnclipu_wx_u8m4(__riscv_vreinterpret_v_i16m8_u16m8(acc1), 0,
|
||||
__RISCV_VXRM_RDN, vl);
|
||||
|
||||
vuint8m4_t hi = __riscv_vmulhu_vv_u8m4(pa, pb, vl);
|
||||
vuint8m4_t result = __riscv_vsrl_vx_u8m4(hi, 1, vl);
|
||||
|
||||
__riscv_vse8_v_u8m4(&output[offset + j], result, vl);
|
||||
j += vl;
|
||||
|
||||
// Record NNZ
|
||||
usize vl32 = vl / 4;
|
||||
vbool8_t nnzMask =
|
||||
__riscv_vmsne_vx_u32m4_b8(__riscv_vreinterpret_v_u8m4_u32m4(result), 0, vl32);
|
||||
__riscv_vsm_v_b8(cursor.out, nnzMask, vl32);
|
||||
cursor.out += vl32 / 8;
|
||||
}
|
||||
|
||||
#else
|
||||
@@ -386,19 +412,8 @@ class FeatureTransformer {
|
||||
BiasType sum1 =
|
||||
accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 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);
|
||||
}
|
||||
sum0 = std::clamp<BiasType>(sum0, 0, FtMaxVal);
|
||||
sum1 = std::clamp<BiasType>(sum1, 0, FtMaxVal);
|
||||
|
||||
output[offset + j] = static_cast<OutputType>(unsigned(sum0 * sum1) / 512);
|
||||
}
|
||||
@@ -410,24 +425,30 @@ class FeatureTransformer {
|
||||
} // end of function transform()
|
||||
|
||||
alignas(CacheLineSize) std::array<BiasType, HalfDimensions> biases;
|
||||
alignas(CacheLineSize) std::array<WeightType, HalfDimensions * InputDimensions> weights;
|
||||
alignas(
|
||||
CacheLineSize) std::array<WeightType, HalfDimensions * PSQFeatureSet::Dimensions> weights;
|
||||
|
||||
// Threats and pawn-pair features are concatenated into one array to allow for a single index to address either.
|
||||
// The first pawn-pair feature is at index ThreatFeatureSet::Dimensions.
|
||||
static_assert(PairFeatureSet::IndexBase == ThreatFeatureSet::Dimensions);
|
||||
|
||||
alignas(CacheLineSize) std::array<ThreatWeightType,
|
||||
(ThreatFeatureSet::Dimensions + PairFeatureSet::Dimensions)
|
||||
* HalfDimensions> threatAndPpWeights;
|
||||
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;
|
||||
std::array<PSQTWeightType, PSQTBuckets * PSQFeatureSet::Dimensions> psqtWeights;
|
||||
// As above
|
||||
alignas(CacheLineSize) std::array<PSQTWeightType,
|
||||
(ThreatFeatureSet::Dimensions + PairFeatureSet::Dimensions)
|
||||
* PSQTBuckets> threatAndPpPsqtWeights;
|
||||
};
|
||||
|
||||
} // 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 {
|
||||
template<>
|
||||
struct std::hash<Stockfish::Eval::NNUE::FeatureTransformer> {
|
||||
Stockfish::usize
|
||||
operator()(const Stockfish::Eval::NNUE::FeatureTransformer& ft) const noexcept {
|
||||
return ft.get_content_hash();
|
||||
}
|
||||
};
|
||||
|
||||
+8
-101
@@ -22,15 +22,14 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <iosfwd>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
#include "../position.h"
|
||||
#include "../misc.h"
|
||||
#include "../types.h"
|
||||
#include "../uci.h"
|
||||
#include "network.h"
|
||||
@@ -39,45 +38,7 @@
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
|
||||
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
|
||||
|
||||
|
||||
namespace {
|
||||
// Converts a Value into (centi)pawns and writes it in a buffer.
|
||||
// The buffer must have capacity for at least 5 chars.
|
||||
void format_cp_compact(Value v, char* buffer, const Position& pos) {
|
||||
|
||||
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
|
||||
|
||||
int cp = std::abs(UCIEngine::to_cp(v, pos));
|
||||
if (cp >= 10000)
|
||||
{
|
||||
buffer[1] = '0' + cp / 10000;
|
||||
cp %= 10000;
|
||||
buffer[2] = '0' + cp / 1000;
|
||||
cp %= 1000;
|
||||
buffer[3] = '0' + cp / 100;
|
||||
buffer[4] = ' ';
|
||||
}
|
||||
else if (cp >= 1000)
|
||||
{
|
||||
buffer[1] = '0' + cp / 1000;
|
||||
cp %= 1000;
|
||||
buffer[2] = '0' + cp / 100;
|
||||
cp %= 100;
|
||||
buffer[3] = '.';
|
||||
buffer[4] = '0' + cp / 10;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer[1] = '0' + cp / 100;
|
||||
cp %= 100;
|
||||
buffer[2] = '.';
|
||||
buffer[3] = '0' + cp / 10;
|
||||
cp %= 10;
|
||||
buffer[4] = '0' + cp / 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Converts a Value into pawns, always keeping two decimals
|
||||
@@ -96,77 +57,23 @@ void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& p
|
||||
// Returns a string with the value of each piece on a board,
|
||||
// and a table for (PSQT, Layers) values bucket by bucket.
|
||||
std::string
|
||||
trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) {
|
||||
trace(Position& pos, const Eval::NNUE::Network& network, Eval::NNUE::AccumulatorCaches& caches) {
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
char board[3 * 8 + 1][8 * 8 + 2];
|
||||
std::memset(board, ' ', sizeof(board));
|
||||
for (int row = 0; row < 3 * 8 + 1; ++row)
|
||||
board[row][8 * 8 + 1] = '\0';
|
||||
|
||||
// A lambda to output one box of the board
|
||||
auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) {
|
||||
const int x = int(file) * 8;
|
||||
const int y = (7 - int(rank)) * 3;
|
||||
for (int i = 1; i < 8; ++i)
|
||||
board[y][x + i] = board[y + 3][x + i] = '-';
|
||||
for (int i = 1; i < 3; ++i)
|
||||
board[y + i][x] = board[y + i][x + 8] = '|';
|
||||
board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+';
|
||||
if (pc != NO_PIECE)
|
||||
board[y + 1][x + 4] = PieceToChar[pc];
|
||||
if (is_valid(value))
|
||||
format_cp_compact(value, &board[y + 2][x + 2], pos);
|
||||
};
|
||||
|
||||
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);
|
||||
Value base = psqt + positional;
|
||||
base = pos.side_to_move() == WHITE ? base : -base;
|
||||
|
||||
for (File f = FILE_A; f <= FILE_H; ++f)
|
||||
for (Rank r = RANK_1; r <= RANK_8; ++r)
|
||||
{
|
||||
Square sq = make_square(f, r);
|
||||
Piece pc = pos.piece_on(sq);
|
||||
Value v = VALUE_NONE;
|
||||
|
||||
if (pc != NO_PIECE && type_of(pc) != KING)
|
||||
{
|
||||
pos.remove_piece(sq);
|
||||
|
||||
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;
|
||||
|
||||
pos.put_piece(pc, sq);
|
||||
}
|
||||
|
||||
writeSquare(f, r, pc, v);
|
||||
}
|
||||
|
||||
ss << " NNUE derived piece values:\n";
|
||||
for (int row = 0; row < 3 * 8 + 1; ++row)
|
||||
ss << board[row] << '\n';
|
||||
ss << '\n';
|
||||
|
||||
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
|
||||
auto t = network.trace_evaluate(pos, *accumulators, caches);
|
||||
|
||||
ss << "NNUE network contributions (Normalized, "
|
||||
<< (pos.side_to_move() == WHITE ? "White to move)" : "Black to move)") << std::endl
|
||||
<< "+------------+------------+------------+------------+\n"
|
||||
<< "| Bucket | Material | Positional | Total |\n"
|
||||
<< "| | (PSQT) | (Layers) | |\n"
|
||||
<< "+------------+------------+------------+------------+\n";
|
||||
|
||||
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket)
|
||||
for (usize bucket = 0; bucket < LayerStacks; ++bucket)
|
||||
{
|
||||
ss << "| " << bucket << " " //
|
||||
<< " | ";
|
||||
|
||||
+14
-22
@@ -19,13 +19,15 @@
|
||||
#ifndef NNUE_MISC_H_INCLUDED
|
||||
#define NNUE_MISC_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <optional>
|
||||
#include <filesystem>
|
||||
|
||||
#include "../misc.h"
|
||||
#include "../types.h"
|
||||
#include "nnue_architecture.h"
|
||||
#include "../evaluate.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
@@ -33,42 +35,32 @@ class Position;
|
||||
|
||||
namespace Eval::NNUE {
|
||||
|
||||
// EvalFile uses fixed string types because it's part of the network structure which must be trivial.
|
||||
// NNUE file metadata uses fixed string types so it stays trivially copyable and cheap to move
|
||||
// around between the engine and the network loader.
|
||||
struct EvalFile {
|
||||
// Default net name, will use one of the EvalFileDefaultName* macros defined
|
||||
// in evaluate.h
|
||||
FixedString<256> defaultName;
|
||||
constexpr static std::string_view defaultName = EvalFileDefaultName;
|
||||
// Selected net name, either via uci option or default
|
||||
FixedString<256> current;
|
||||
std::optional<std::filesystem::path> current;
|
||||
// Net description extracted from the net file
|
||||
FixedString<256> netDescription;
|
||||
std::string netDescription;
|
||||
};
|
||||
|
||||
struct NnueEvalTrace {
|
||||
static_assert(LayerStacks == PSQTBuckets);
|
||||
|
||||
Value psqt[LayerStacks];
|
||||
Value positional[LayerStacks];
|
||||
std::size_t correctBucket;
|
||||
Value psqt[LayerStacks];
|
||||
Value positional[LayerStacks];
|
||||
usize correctBucket;
|
||||
};
|
||||
|
||||
struct Networks;
|
||||
class Network;
|
||||
struct AccumulatorCaches;
|
||||
|
||||
std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches);
|
||||
std::string trace(Position& pos, const Network& network, AccumulatorCaches& caches);
|
||||
|
||||
} // 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
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
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 NNZ_HELPER_H_INCLUDED
|
||||
#define NNZ_HELPER_H_INCLUDED
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "nnue_common.h"
|
||||
#include "simd.h"
|
||||
|
||||
namespace Stockfish::Eval::NNUE {
|
||||
|
||||
template<usize Dimensions>
|
||||
struct NNZInfo {
|
||||
|
||||
#if defined(USE_AVX512)
|
||||
unsigned count = 0;
|
||||
// indices of non-zero chunks
|
||||
u16 nnz[Dimensions / 4];
|
||||
|
||||
#ifdef USE_AVX512ICL
|
||||
alignas(64) static constexpr auto Indices = []() {
|
||||
std::array<std::array<u16, 32>, 2> indices{};
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
indices[i] = {0, 1, 2, 3, 16, 17, 18, 19, 4, 5, 6, 7, 20, 21, 22, 23,
|
||||
8, 9, 10, 11, 24, 25, 26, 27, 12, 13, 14, 15, 28, 29, 30, 31};
|
||||
for (u16& m : indices[i])
|
||||
m += u16(i * Dimensions / 8);
|
||||
}
|
||||
return indices;
|
||||
}();
|
||||
#else
|
||||
alignas(64) static constexpr auto Indices = []() {
|
||||
std::array<std::array<u32, 16>, 2> indices{};
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
indices[i] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
for (u32& m : indices[i])
|
||||
m += i * Dimensions / 8;
|
||||
}
|
||||
return indices;
|
||||
}();
|
||||
#endif
|
||||
|
||||
struct NNZCursor {
|
||||
NNZInfo& info;
|
||||
__m512i indices;
|
||||
unsigned count;
|
||||
|
||||
NNZCursor(NNZInfo& info_, bool perspective, unsigned count_) :
|
||||
info(info_),
|
||||
count(count_) {
|
||||
indices = _mm512_load_si512(&Indices[perspective]);
|
||||
}
|
||||
|
||||
void record2(SIMD::vec_t neurons1, SIMD::vec_t neurons2) {
|
||||
#if defined(USE_AVX512ICL)
|
||||
const __m512i increment = _mm512_set1_epi16(32);
|
||||
|
||||
// Get a bitmask and gather non zero indices
|
||||
const __m512i inputV01 = _mm512_packs_epi32(neurons1, neurons2);
|
||||
const __mmask32 nnzMask = _mm512_test_epi16_mask(inputV01, inputV01);
|
||||
|
||||
// Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4
|
||||
__m512i nnzIndices = _mm512_maskz_compress_epi16(nnzMask, indices);
|
||||
_mm512_storeu_si512(info.nnz + count, nnzIndices);
|
||||
|
||||
count += popcount(nnzMask);
|
||||
indices = _mm512_add_epi16(indices, increment);
|
||||
#else
|
||||
const __m512i increment = _mm512_set1_epi32(16);
|
||||
|
||||
for (auto neurons : {neurons1, neurons2})
|
||||
{
|
||||
// Get a bitmask and gather non zero indices
|
||||
const __mmask16 nnzMask = _mm512_test_epi32_mask(neurons, neurons);
|
||||
const __m512i nnzV = _mm512_maskz_compress_epi32(nnzMask, indices);
|
||||
_mm512_mask_cvtepi32_storeu_epi16(info.nnz + count, 0xFFFF, nnzV);
|
||||
|
||||
count += popcount(nnzMask);
|
||||
indices = _mm512_add_epi32(indices, increment);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
~NNZCursor() { info.count = count; }
|
||||
};
|
||||
|
||||
NNZCursor make_cursor(bool perspective) { return {*this, perspective, count}; }
|
||||
#else
|
||||
// Each 8-bit chunk
|
||||
alignas(8) u8 bitset[(Dimensions + 31) / 32];
|
||||
|
||||
struct NNZCursor {
|
||||
u8* out;
|
||||
|
||||
NNZCursor(NNZInfo& info, bool perspective) {
|
||||
out = info.bitset + perspective * Dimensions / 64;
|
||||
}
|
||||
|
||||
#if (defined(USE_SSSE3) || defined(USE_LSX) || defined(USE_LASX) || (USE_NEON >= 8))
|
||||
void record2(SIMD::vec_t neurons1, SIMD::vec_t neurons2) {
|
||||
using namespace SIMD;
|
||||
|
||||
#ifdef USE_NEON
|
||||
alignas(16) static constexpr u16 Mask8[8] = {1, 16, 2, 32, 4, 64, 8, 128};
|
||||
|
||||
uint32x4_t n1 = vreinterpretq_u32_s16(neurons1);
|
||||
uint32x4_t n2 = vreinterpretq_u32_s16(neurons2);
|
||||
|
||||
const uint32x4_t t1 = vtstq_u32(n1, n1);
|
||||
const uint32x4_t t2 = vtstq_u32(n2, n2);
|
||||
|
||||
const uint16x8_t packed =
|
||||
vtrn1q_u16(vreinterpretq_u16_u32(t1), vreinterpretq_u16_u32(t2));
|
||||
const uint16x8_t bits = vandq_u16(packed, vld1q_u16(Mask8));
|
||||
|
||||
*out++ = vaddvq_u16(bits);
|
||||
#elif defined(__wasm__)
|
||||
__m128i packed = _mm_packus_epi32(neurons1, neurons2);
|
||||
packed = _mm_packs_epi16(packed, packed);
|
||||
*out++ = ~_mm_movemask_epi8(_mm_cmpeq_epi8(packed, _mm_setzero_si128()));
|
||||
#else
|
||||
auto m1 = vec_nnz(neurons1);
|
||||
auto m2 = vec_nnz(neurons2);
|
||||
|
||||
if (sizeof(neurons1) == 16)
|
||||
{
|
||||
*out++ = m1 + (m2 << 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
usize bytes = sizeof(neurons1) / 32;
|
||||
memcpy(out, &m1, bytes);
|
||||
out += bytes;
|
||||
memcpy(out, &m2, bytes);
|
||||
out += bytes;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#elif defined(VECTOR)
|
||||
void record2(SIMD::vec_t, SIMD::vec_t) {}
|
||||
#endif
|
||||
};
|
||||
|
||||
NNZCursor make_cursor(bool perspective) { return {*this, perspective}; }
|
||||
#endif
|
||||
};
|
||||
} // namespace Stockfish::Eval::NNUE
|
||||
|
||||
#endif
|
||||
+242
-116
@@ -33,6 +33,17 @@
|
||||
|
||||
#elif defined(USE_NEON)
|
||||
#include <arm_neon.h>
|
||||
|
||||
#elif defined(USE_LASX)
|
||||
#include <lasxintrin.h>
|
||||
#include <lsxintrin.h>
|
||||
|
||||
#elif defined(USE_LSX)
|
||||
#include <lsxintrin.h>
|
||||
|
||||
#elif defined(USE_RVV)
|
||||
#include <riscv_vector.h>
|
||||
|
||||
#endif
|
||||
|
||||
#include "../types.h"
|
||||
@@ -40,6 +51,10 @@
|
||||
|
||||
namespace Stockfish::Eval::NNUE::SIMD {
|
||||
|
||||
#if defined(USE_AVX2) && !defined(USE_VNNI) && !defined(USE_AVX512)
|
||||
#define USE_AVX2_PAIR_ACTIVATIONS
|
||||
#endif
|
||||
|
||||
// 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.
|
||||
@@ -48,9 +63,7 @@ namespace Stockfish::Eval::NNUE::SIMD {
|
||||
#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)
|
||||
@@ -70,24 +83,15 @@ using vec_uint_t = __m512i;
|
||||
#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 vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
|
||||
|
||||
#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)
|
||||
@@ -107,31 +111,16 @@ using vec_uint_t = __m256i;
|
||||
#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 vec_nnz(a) \
|
||||
_mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
|
||||
|
||||
#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 vec_i8_t = u64;
|
||||
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)
|
||||
@@ -155,37 +144,39 @@ using vec_uint_t = __m128i;
|
||||
#endif
|
||||
|
||||
#ifdef __i386__
|
||||
inline __m128i _mm_cvtsi64_si128(int64_t val) {
|
||||
inline __m128i _mm_cvtsi64_si128(i64 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)))
|
||||
#ifdef __wasm__
|
||||
#define vec_convert_8_16(a) wasm_i16x8_load8x8(reinterpret_cast<const void*>(&a))
|
||||
#else
|
||||
#define vec_convert_8_16(a) _mm_cvtepi8_epi16(_mm_cvtsi64_si128(static_cast<i64>(a)))
|
||||
#endif
|
||||
#else
|
||||
// Credit: Yoshie2000
|
||||
inline __m128i vec_convert_8_16(uint64_t x) {
|
||||
__m128i v8 = _mm_cvtsi64_si128(static_cast<int64_t>(x));
|
||||
inline __m128i vec_convert_8_16(u64 x) {
|
||||
__m128i v8 = _mm_cvtsi64_si128(static_cast<i64>(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;
|
||||
using vec_i8x8_t __attribute__((may_alias)) = int8x8_t;
|
||||
using vec_i16x8_t __attribute__((may_alias)) = int16x8_t;
|
||||
using vec_i8x16_t __attribute__((may_alias)) = int8x16_t;
|
||||
using vec_u16x8_t __attribute__((may_alias)) = uint16x8_t;
|
||||
using vec_i32x4_t __attribute__((may_alias)) = int32x4_t;
|
||||
|
||||
using vec_t __attribute__((may_alias)) = int16x8_t;
|
||||
using vec_i8_t __attribute__((may_alias)) = int8x16_t;
|
||||
using psqt_vec_t __attribute__((may_alias)) = int32x4_t;
|
||||
#define vec_load(a) (*(a))
|
||||
#define vec_store(a, b) *(a) = (b)
|
||||
#define vec_add_16(a, b) vaddq_s16(a, b)
|
||||
@@ -203,85 +194,152 @@ using vec_uint_t = uint32x4_t;
|
||||
#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)); }
|
||||
// Instructions that don't exist on 32-bit ARM
|
||||
inline int16x8_t vaddw_high_s8(int16x8_t a, int8x16_t b) { return vaddw_s8(a, vget_high_s8(b)); }
|
||||
inline int16x8_t vsubw_high_s8(int16x8_t a, int8x16_t b) { return vsubw_s8(a, vget_high_s8(b)); }
|
||||
#endif
|
||||
|
||||
#elif USE_LASX
|
||||
using vec_t = __m256i;
|
||||
using vec_i8_t = __m128i;
|
||||
using psqt_vec_t = __m256i;
|
||||
|
||||
inline __m256i lasx_load256(const __m256i* a) {
|
||||
return __lasx_xvld(reinterpret_cast<const void*>(a), 0);
|
||||
}
|
||||
|
||||
inline void lasx_store256(__m256i* a, __m256i b) { __lasx_xvst(b, reinterpret_cast<void*>(a), 0); }
|
||||
|
||||
inline __m256i lasx_packus_16(__m256i a, __m256i b) {
|
||||
#if defined(__clang__) && defined(__has_builtin) && __has_builtin(__builtin_lasx_xvssrani_bu_h)
|
||||
return (__m256i) __builtin_lasx_xvssrani_bu_h((v32i8) b, (v32i8) a, 0);
|
||||
#else
|
||||
return __lasx_xvssrani_bu_h(b, a, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline __m256i lasx_packus_32(__m256i a, __m256i b) {
|
||||
#if defined(__clang__) && defined(__has_builtin) && __has_builtin(__builtin_lasx_xvssrani_hu_w)
|
||||
return (__m256i) __builtin_lasx_xvssrani_hu_w((v16i16) b, (v16i16) a, 0);
|
||||
#else
|
||||
return __lasx_xvssrani_hu_w(b, a, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define vec_load(a) lasx_load256(a)
|
||||
#define vec_store(a, b) lasx_store256(a, b)
|
||||
#define vec_add_16(a, b) __lasx_xvadd_h(a, b)
|
||||
#define vec_sub_16(a, b) __lasx_xvsub_h(a, b)
|
||||
#define vec_mulhi_16(a, b) __lasx_xvmuh_h(a, b)
|
||||
#define vec_zero() __lasx_xvldi(0)
|
||||
#define vec_set_16(a) __lasx_xvreplgr2vr_h(a)
|
||||
#define vec_max_16(a, b) __lasx_xvmax_h(a, b)
|
||||
#define vec_min_16(a, b) __lasx_xvmin_h(a, b)
|
||||
#define vec_slli_16(a, b) __lasx_xvslli_h(a, b)
|
||||
// Inverse permuted at load time
|
||||
#define vec_packus_16(a, b) lasx_packus_16(a, b)
|
||||
#define vec_load_psqt(a) lasx_load256(a)
|
||||
#define vec_store_psqt(a, b) lasx_store256(a, b)
|
||||
#define vec_add_psqt_32(a, b) __lasx_xvadd_w(a, b)
|
||||
#define vec_sub_psqt_32(a, b) __lasx_xvsub_w(a, b)
|
||||
#define vec_zero_psqt() __lasx_xvldi(0)
|
||||
#define vec_nnz(a) lasx_vec_nnz(a)
|
||||
#define vec_convert_8_16(a) lasx_cvtepi8_epi16(a)
|
||||
#define vec_mulhi_8 __lasx_xvmuh_bu
|
||||
#define vec_srli_8 __lasx_xvsrli_b
|
||||
|
||||
#define NumRegistersSIMD 24
|
||||
#define MaxChunkSize 32
|
||||
|
||||
inline __m256i lasx_cvtepi8_epi16(__m128i a) {
|
||||
#if defined(__has_builtin) && __has_builtin(__builtin_lasx_cast_128)
|
||||
return __lasx_vext2xv_h_b(__lasx_cast_128(a));
|
||||
#elif defined(__GNUC__) && !defined(__clang__)
|
||||
__m256i out;
|
||||
__asm__("vext2xv.h.b %u0, %u1" : "=f"(out) : "f"(a));
|
||||
return out;
|
||||
#else
|
||||
i64 lo = (i64) __lsx_vpickve2gr_d(a, 0);
|
||||
i64 hi = (i64) __lsx_vpickve2gr_d(a, 1);
|
||||
__m256i v = __lasx_xvldi(0);
|
||||
v = __lasx_xvinsgr2vr_d(v, lo, 0);
|
||||
v = __lasx_xvinsgr2vr_d(v, hi, 2);
|
||||
return __lasx_xvsllwil_h_b(v, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline int lasx_vec_nnz(__m256i a) {
|
||||
const __m256i cmp = __lasx_xvslt_w(__lasx_xvldi(0), a);
|
||||
const __m256i msk = __lasx_xvmskltz_w(cmp);
|
||||
return ((int) __lasx_xvpickve2gr_w(msk, 0)) | (((int) __lasx_xvpickve2gr_w(msk, 4)) << 4);
|
||||
}
|
||||
|
||||
#elif USE_LSX
|
||||
using vec_t = __m128i;
|
||||
using vec_i8_t = u64;
|
||||
using psqt_vec_t = __m128i;
|
||||
|
||||
inline __m128i lsx_packus_16(__m128i a, __m128i b) {
|
||||
#if defined(__clang__) && defined(__has_builtin) && __has_builtin(__builtin_lsx_vssrani_bu_h)
|
||||
return (__m128i) __builtin_lsx_vssrani_bu_h((v16i8) b, (v16i8) a, 0);
|
||||
#else
|
||||
return __lsx_vssrani_bu_h(b, a, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline __m128i lsx_packus_32(__m128i a, __m128i b) {
|
||||
#if defined(__clang__) && defined(__has_builtin) && __has_builtin(__builtin_lsx_vssrani_hu_w)
|
||||
return (__m128i) __builtin_lsx_vssrani_hu_w((v8i16) b, (v8i16) a, 0);
|
||||
#else
|
||||
return __lsx_vssrani_hu_w(b, a, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#define vec_load(a) (*(a))
|
||||
#define vec_store(a, b) *(a) = (b)
|
||||
#define vec_add_16(a, b) __lsx_vadd_h(a, b)
|
||||
#define vec_sub_16(a, b) __lsx_vsub_h(a, b)
|
||||
#define vec_mulhi_16(a, b) __lsx_vmuh_h(a, b)
|
||||
#define vec_zero() __lsx_vldi(0)
|
||||
#define vec_set_16(a) __lsx_vreplgr2vr_h(a)
|
||||
#define vec_max_16(a, b) __lsx_vmax_h(a, b)
|
||||
#define vec_min_16(a, b) __lsx_vmin_h(a, b)
|
||||
#define vec_slli_16(a, b) __lsx_vslli_h(a, b)
|
||||
// Inverse permuted at load time
|
||||
#define vec_packus_16(a, b) lsx_packus_16(a, b)
|
||||
#define vec_load_psqt(a) (*(a))
|
||||
#define vec_store_psqt(a, b) *(a) = (b)
|
||||
#define vec_add_psqt_32(a, b) __lsx_vadd_w(a, b)
|
||||
#define vec_sub_psqt_32(a, b) __lsx_vsub_w(a, b)
|
||||
#define vec_zero_psqt() __lsx_vldi(0)
|
||||
|
||||
inline int lsx_vec_nnz(__m128i a) {
|
||||
const __m128i cmp = __lsx_vslt_w(__lsx_vldi(0), a);
|
||||
const __m128i msk = __lsx_vmskltz_w(cmp);
|
||||
return ((int) __lsx_vpickve2gr_w(msk, 0));
|
||||
}
|
||||
#define vec_nnz(a) lsx_vec_nnz(a)
|
||||
|
||||
inline __m128i vec_convert_8_16(u64 x) {
|
||||
__m128i v = __lsx_vldrepl_d(reinterpret_cast<const void*>(&x), 0);
|
||||
return __lsx_vsllwil_h_b(v, 0);
|
||||
}
|
||||
|
||||
#define vec_mulhi_8 __lsx_vmuh_bu
|
||||
#define vec_srli_8 __lsx_vsrli_b
|
||||
|
||||
#define NumRegistersSIMD 24
|
||||
#define MaxChunkSize 16
|
||||
|
||||
#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) {
|
||||
@@ -332,10 +390,13 @@ fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operan
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
|
||||
|
||||
#if defined(__wasm_relaxed_simd__)
|
||||
acc = wasm_i32x4_relaxed_dot_i8x16_i7x16_add(b, a, acc);
|
||||
#else
|
||||
__m128i product0 = _mm_maddubs_epi16(a, b);
|
||||
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
|
||||
acc = _mm_add_epi32(acc, product0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -375,6 +436,42 @@ dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_LASX)
|
||||
|
||||
[[maybe_unused]] static int lasx_m256_hadd(__m256i sum, int bias) {
|
||||
__m256i v = sum;
|
||||
v = __lasx_xvadd_w(v, __lasx_xvshuf4i_w(v, 0x4E)); // [C,D,A,B] per lane
|
||||
v = __lasx_xvadd_w(v, __lasx_xvshuf4i_w(v, 0xB1)); // [B,A,D,C] per lane
|
||||
int lo_sum = (int) __lasx_xvpickve2gr_w(v, 0);
|
||||
int hi_sum = (int) __lasx_xvpickve2gr_w(v, 4);
|
||||
return lo_sum + hi_sum + bias;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void lasx_m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) {
|
||||
__m256i tmp = __lasx_xvmulwev_h_bu_b(a, b);
|
||||
tmp = __lasx_xvmaddwod_h_bu_b(tmp, a, b);
|
||||
acc = __lasx_xvadd_w(acc, __lasx_xvhaddw_w_h(tmp, tmp));
|
||||
}
|
||||
|
||||
#endif // USE_LASX
|
||||
|
||||
#if defined(USE_LSX)
|
||||
|
||||
[[maybe_unused]] static int lsx_m128_hadd(__m128i sum, int bias) {
|
||||
sum = __lsx_vadd_w(sum, __lsx_vshuf4i_w(sum, 0x4E)); // [C,D,A,B]
|
||||
sum = __lsx_vadd_w(sum, __lsx_vshuf4i_w(sum, 0xB1)); // [B,A,D,C]
|
||||
return __lsx_vpickve2gr_w(sum, 0) + bias;
|
||||
}
|
||||
|
||||
[[maybe_unused]] static void lsx_m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
|
||||
// tmp[i] = a[2i]*b[2i] + a[2i+1]*b[2i+1]
|
||||
__m128i tmp = __lsx_vmulwev_h_bu_b(a, b);
|
||||
tmp = __lsx_vmaddwod_h_bu_b(tmp, a, b);
|
||||
acc = __lsx_vadd_w(acc, __lsx_vhaddw_w_h(tmp, tmp));
|
||||
}
|
||||
|
||||
#endif // USE_LSX
|
||||
|
||||
|
||||
// Compute optimal SIMD register count for feature transformer accumulation.
|
||||
template<IndexType TransformedFeatureWidth, IndexType HalfDimensions, IndexType PSQTBuckets>
|
||||
@@ -390,8 +487,8 @@ class SIMDTiling {
|
||||
|
||||
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);
|
||||
constexpr usize RegisterSize = sizeof(SIMDRegisterType);
|
||||
constexpr usize LaneSize = sizeof(LaneType);
|
||||
|
||||
static_assert(RegisterSize >= LaneSize);
|
||||
static_assert(MaxRegisters <= NumRegistersSIMD);
|
||||
@@ -429,6 +526,35 @@ class SIMDTiling {
|
||||
static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets");
|
||||
#endif
|
||||
};
|
||||
|
||||
#if defined(USE_RVV)
|
||||
|
||||
#define RVV_DEFINE_DPBUSD(A, W, H) \
|
||||
inline vint32##A##_t rvv_dpbusd_##A(vuint8##A##_t a, vint8##A##_t b, usize n) { \
|
||||
vint16##W##_t prod = __riscv_vwmulsu_vv_i16##W(b, a, 4 * n); \
|
||||
vuint32##W##_t prod32 = __riscv_vreinterpret_v_u16##W##_u32##W( \
|
||||
__riscv_vreinterpret_v_i16##W##_u16##W(prod)); \
|
||||
vint16##A##_t even = \
|
||||
__riscv_vreinterpret_v_u16##A##_i16##A(__riscv_vnsrl_wx_u16##A(prod32, 0, 2 * n)); \
|
||||
vint16##A##_t odd = \
|
||||
__riscv_vreinterpret_v_u16##A##_i16##A(__riscv_vnsrl_wx_u16##A(prod32, 16, 2 * n)); \
|
||||
vuint32##A##_t pairs = __riscv_vreinterpret_v_u16##A##_u32##A( \
|
||||
__riscv_vreinterpret_v_i16##A##_u16##A(__riscv_vadd_vv_i16##A(even, odd, 2 * n))); \
|
||||
vint16##H##_t lo = \
|
||||
__riscv_vreinterpret_v_u16##H##_i16##H(__riscv_vnsrl_wx_u16##H(pairs, 0, n)); \
|
||||
vint16##H##_t hi = \
|
||||
__riscv_vreinterpret_v_u16##H##_i16##H(__riscv_vnsrl_wx_u16##H(pairs, 16, n)); \
|
||||
return __riscv_vwadd_vv_i32##A(lo, hi, n); \
|
||||
}
|
||||
|
||||
RVV_DEFINE_DPBUSD(m1, m2, mf2)
|
||||
RVV_DEFINE_DPBUSD(m2, m4, m1)
|
||||
RVV_DEFINE_DPBUSD(m4, m8, m2)
|
||||
|
||||
#undef RVV_DEFINE_DPBUSD
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
+66
-61
@@ -37,6 +37,7 @@
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#include "misc.h"
|
||||
#include "shm.h"
|
||||
|
||||
// We support linux very well, but we explicitly do NOT support Android,
|
||||
@@ -53,10 +54,6 @@
|
||||
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
|
||||
#endif
|
||||
|
||||
// On Windows each processor group can have up to 64 processors.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups
|
||||
static constexpr size_t WIN_PROCESSOR_GROUP_SIZE = 64;
|
||||
|
||||
#if !defined(NOMINMAX)
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
@@ -77,8 +74,8 @@ using GetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT,
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
using CpuIndex = size_t;
|
||||
using NumaIndex = size_t;
|
||||
using CpuIndex = usize;
|
||||
using NumaIndex = usize;
|
||||
|
||||
inline CpuIndex get_hardware_concurrency() {
|
||||
CpuIndex concurrency = std::thread::hardware_concurrency();
|
||||
@@ -97,6 +94,10 @@ inline const CpuIndex SYSTEM_THREADS_NB = std::max<CpuIndex>(1, get_hardware_con
|
||||
|
||||
#if defined(_WIN64)
|
||||
|
||||
// On Windows each processor group can have up to 64 processors.
|
||||
// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups
|
||||
static constexpr usize WIN_PROCESSOR_GROUP_SIZE = 64;
|
||||
|
||||
struct WindowsAffinity {
|
||||
std::optional<std::set<CpuIndex>> oldApi;
|
||||
std::optional<std::set<CpuIndex>> newApi;
|
||||
@@ -134,7 +135,7 @@ inline std::pair<BOOL, std::vector<USHORT>> get_process_group_affinity() {
|
||||
|
||||
// GetProcessGroupAffinity requires the GroupArray argument to be
|
||||
// aligned to 4 bytes instead of just 2.
|
||||
static constexpr size_t GroupArrayMinimumAlignment = 4;
|
||||
static constexpr usize GroupArrayMinimumAlignment = 4;
|
||||
static_assert(GroupArrayMinimumAlignment >= alignof(USHORT));
|
||||
|
||||
// The function should succeed the second time, but it may fail if the group
|
||||
@@ -217,9 +218,9 @@ inline WindowsAffinity get_process_affinity() {
|
||||
|
||||
for (USHORT i = 0; i < RequiredMaskCount; ++i)
|
||||
{
|
||||
const size_t procGroupIndex = groupAffinities[i].Group;
|
||||
const usize procGroupIndex = groupAffinities[i].Group;
|
||||
|
||||
for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
|
||||
for (usize j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
|
||||
{
|
||||
if (groupAffinities[i].Mask & (KAFFINITY(1) << j))
|
||||
cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);
|
||||
@@ -271,10 +272,10 @@ inline WindowsAffinity get_process_affinity() {
|
||||
{
|
||||
std::set<CpuIndex> cpus;
|
||||
|
||||
const size_t procGroupIndex = groupAffinity[0];
|
||||
const usize procGroupIndex = groupAffinity[0];
|
||||
|
||||
const uint64_t mask = static_cast<uint64_t>(proc);
|
||||
for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
|
||||
const u64 mask = static_cast<u64>(proc);
|
||||
for (usize j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
|
||||
{
|
||||
if (mask & (KAFFINITY(1) << j))
|
||||
cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);
|
||||
@@ -312,8 +313,8 @@ inline WindowsAffinity get_process_affinity() {
|
||||
// choice could influence the resulting affinity.
|
||||
// We assume the processor IDs within the group are
|
||||
// filled sequentially from 0.
|
||||
uint64_t procCombined = std::numeric_limits<uint64_t>::max();
|
||||
uint64_t sysCombined = std::numeric_limits<uint64_t>::max();
|
||||
u64 procCombined = std::numeric_limits<u64>::max();
|
||||
u64 sysCombined = std::numeric_limits<u64>::max();
|
||||
|
||||
for (int i = 0; i < std::min(numActiveProcessors, 2); ++i)
|
||||
{
|
||||
@@ -341,14 +342,14 @@ inline WindowsAffinity get_process_affinity() {
|
||||
return;
|
||||
}
|
||||
|
||||
procCombined &= static_cast<uint64_t>(proc2);
|
||||
sysCombined &= static_cast<uint64_t>(sys2);
|
||||
procCombined &= static_cast<u64>(proc2);
|
||||
sysCombined &= static_cast<u64>(sys2);
|
||||
}
|
||||
|
||||
if (procCombined != sysCombined)
|
||||
isAffinityFull = false;
|
||||
|
||||
for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
|
||||
for (usize j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
|
||||
{
|
||||
if (procCombined & (KAFFINITY(1) << j))
|
||||
cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);
|
||||
@@ -439,7 +440,7 @@ inline std::set<CpuIndex> get_process_affinity() {
|
||||
if (mask == nullptr)
|
||||
std::exit(EXIT_FAILURE);
|
||||
|
||||
const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus);
|
||||
const usize masksize = CPU_ALLOC_SIZE(MaxNumCpus);
|
||||
|
||||
CPU_ZERO_S(masksize, mask);
|
||||
|
||||
@@ -502,7 +503,7 @@ struct SystemNumaPolicy {};
|
||||
struct L3DomainsPolicy {};
|
||||
// Group system-reported L3 domains until they reach bundleSize
|
||||
struct BundledL3Policy {
|
||||
size_t bundleSize;
|
||||
usize bundleSize;
|
||||
};
|
||||
|
||||
using NumaAutoPolicy = std::variant<SystemNumaPolicy, L3DomainsPolicy, BundledL3Policy>;
|
||||
@@ -581,7 +582,7 @@ class NumaConfig {
|
||||
bool l3Success = false;
|
||||
if (!std::holds_alternative<SystemNumaPolicy>(policy))
|
||||
{
|
||||
size_t l3BundleSize = 0;
|
||||
usize l3BundleSize = 0;
|
||||
if (const auto* v = std::get_if<BundledL3Policy>(&policy))
|
||||
{
|
||||
l3BundleSize = v->bundleSize;
|
||||
@@ -627,10 +628,10 @@ class NumaConfig {
|
||||
if (cpus.empty())
|
||||
continue;
|
||||
|
||||
size_t lastProcGroupIndex = *(cpus.begin()) / WIN_PROCESSOR_GROUP_SIZE;
|
||||
usize lastProcGroupIndex = *(cpus.begin()) / WIN_PROCESSOR_GROUP_SIZE;
|
||||
for (CpuIndex c : cpus)
|
||||
{
|
||||
const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
|
||||
const usize procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
|
||||
if (procGroupIndex != lastProcGroupIndex)
|
||||
{
|
||||
splitNodeIndex += 1;
|
||||
@@ -663,7 +664,7 @@ class NumaConfig {
|
||||
// ','-separated cpu indices
|
||||
// supports "first-last" range syntax for cpu indices
|
||||
// For example "0-15,128-143:16-31,144-159:32-47,160-175:48-63,176-191"
|
||||
static NumaConfig from_string(const std::string& s) {
|
||||
static std::optional<NumaConfig> from_string(const std::string& s) {
|
||||
NumaConfig cfg = empty();
|
||||
|
||||
NumaIndex n = 0;
|
||||
@@ -675,13 +676,16 @@ class NumaConfig {
|
||||
for (auto idx : indices)
|
||||
{
|
||||
if (!cfg.add_cpu_to_node(n, CpuIndex(idx)))
|
||||
std::exit(EXIT_FAILURE);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
n += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (n == 0) // failed to parse any nodes
|
||||
return std::nullopt;
|
||||
|
||||
cfg.customAffinity = true;
|
||||
|
||||
return cfg;
|
||||
@@ -769,7 +773,7 @@ class NumaConfig {
|
||||
if (numThreads <= 1)
|
||||
return false;
|
||||
|
||||
size_t largestNodeSize = 0;
|
||||
usize largestNodeSize = 0;
|
||||
for (auto&& cpus : nodes)
|
||||
if (cpus.size() > largestNodeSize)
|
||||
largestNodeSize = cpus.size();
|
||||
@@ -780,7 +784,7 @@ class NumaConfig {
|
||||
<= SmallNodeThreshold;
|
||||
};
|
||||
|
||||
size_t numNotSmallNodes = 0;
|
||||
usize numNotSmallNodes = 0;
|
||||
for (auto&& cpus : nodes)
|
||||
if (!is_node_small(cpus))
|
||||
numNotSmallNodes += 1;
|
||||
@@ -800,7 +804,7 @@ class NumaConfig {
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<size_t> occupation(nodes.size(), 0);
|
||||
std::vector<usize> occupation(nodes.size(), 0);
|
||||
for (CpuIndex c = 0; c < numThreads; ++c)
|
||||
{
|
||||
NumaIndex bestNode{0};
|
||||
@@ -838,7 +842,7 @@ class NumaConfig {
|
||||
if (mask == nullptr)
|
||||
std::exit(EXIT_FAILURE);
|
||||
|
||||
const size_t masksize = CPU_ALLOC_SIZE(highestCpuIndex + 1);
|
||||
const usize masksize = CPU_ALLOC_SIZE(highestCpuIndex + 1);
|
||||
|
||||
CPU_ZERO_S(masksize, mask);
|
||||
|
||||
@@ -880,8 +884,8 @@ class NumaConfig {
|
||||
|
||||
for (CpuIndex c : nodes[n])
|
||||
{
|
||||
const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
|
||||
const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;
|
||||
const usize procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
|
||||
const usize idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;
|
||||
groupAffinities[procGroupIndex].Mask |= KAFFINITY(1) << idxWithinProcGroup;
|
||||
}
|
||||
|
||||
@@ -919,12 +923,12 @@ class NumaConfig {
|
||||
GROUP_AFFINITY affinity;
|
||||
std::memset(&affinity, 0, sizeof(GROUP_AFFINITY));
|
||||
// We use an ordered set to be sure to get the smallest cpu number here.
|
||||
const size_t forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE;
|
||||
affinity.Group = static_cast<WORD>(forcedProcGroupIndex);
|
||||
const usize forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE;
|
||||
affinity.Group = static_cast<WORD>(forcedProcGroupIndex);
|
||||
for (CpuIndex c : nodes[n])
|
||||
{
|
||||
const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
|
||||
const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;
|
||||
const usize procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
|
||||
const usize idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;
|
||||
// We skip processors that are not in the same processor group.
|
||||
// If everything was set up correctly this will never be an issue,
|
||||
// but we have to account for bad NUMA node specification.
|
||||
@@ -1026,8 +1030,8 @@ class NumaConfig {
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::vector<size_t> indices_from_shortened_string(const std::string& s) {
|
||||
std::vector<size_t> indices;
|
||||
static std::vector<usize> indices_from_shortened_string(const std::string& s) {
|
||||
std::vector<usize> indices;
|
||||
|
||||
if (s.empty())
|
||||
return indices;
|
||||
@@ -1040,16 +1044,22 @@ class NumaConfig {
|
||||
auto parts = split(ss, "-");
|
||||
if (parts.size() == 1)
|
||||
{
|
||||
const CpuIndex c = CpuIndex{str_to_size_t(std::string(parts[0]))};
|
||||
indices.emplace_back(c);
|
||||
auto c = str_to_size_t(std::string(parts[0]));
|
||||
if (c.has_value())
|
||||
indices.emplace_back(*c);
|
||||
}
|
||||
else if (parts.size() == 2)
|
||||
{
|
||||
const CpuIndex cfirst = CpuIndex{str_to_size_t(std::string(parts[0]))};
|
||||
const CpuIndex clast = CpuIndex{str_to_size_t(std::string(parts[1]))};
|
||||
for (size_t c = cfirst; c <= clast; ++c)
|
||||
constexpr usize MaxIndices = 1 << 20; // prevent oom
|
||||
|
||||
auto cfirst = str_to_size_t(std::string(parts[0]));
|
||||
auto clast = str_to_size_t(std::string(parts[1]));
|
||||
if (cfirst.has_value() && clast.has_value() && *clast - *cfirst < MaxIndices)
|
||||
{
|
||||
indices.emplace_back(c);
|
||||
for (usize c = *cfirst; c <= *clast; ++c)
|
||||
{
|
||||
indices.emplace_back(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1088,7 +1098,7 @@ class NumaConfig {
|
||||
else
|
||||
{
|
||||
remove_whitespace(*nodeIdsStr);
|
||||
for (size_t n : indices_from_shortened_string(*nodeIdsStr))
|
||||
for (usize n : indices_from_shortened_string(*nodeIdsStr))
|
||||
{
|
||||
// /sys/devices/system/node/node.../cpulist
|
||||
std::string path =
|
||||
@@ -1105,7 +1115,7 @@ class NumaConfig {
|
||||
else
|
||||
{
|
||||
remove_whitespace(*cpuIdsStr);
|
||||
for (size_t c : indices_from_shortened_string(*cpuIdsStr))
|
||||
for (usize c : indices_from_shortened_string(*cpuIdsStr))
|
||||
{
|
||||
if (is_cpu_allowed(c))
|
||||
cfg.add_cpu_to_node(n, c);
|
||||
@@ -1156,7 +1166,7 @@ class NumaConfig {
|
||||
|
||||
template<typename Pred>
|
||||
static std::optional<NumaConfig> try_get_l3_aware_config(
|
||||
bool respectProcessAffinity, size_t bundleSize, [[maybe_unused]] Pred&& is_cpu_allowed) {
|
||||
bool respectProcessAffinity, usize 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 =
|
||||
@@ -1166,26 +1176,21 @@ class NumaConfig {
|
||||
#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)
|
||||
for (const auto& [next, _] : systemConfig.nodeByCpu)
|
||||
{
|
||||
CpuIndex next = nextUnseenCpu();
|
||||
auto siblingsStr =
|
||||
if (seenCpus.count(next))
|
||||
continue;
|
||||
|
||||
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
|
||||
}
|
||||
continue;
|
||||
|
||||
L3Domain domain;
|
||||
for (size_t c : indices_from_shortened_string(*siblingsStr))
|
||||
for (usize c : indices_from_shortened_string(*siblingsStr))
|
||||
{
|
||||
if (is_cpu_allowed(c))
|
||||
{
|
||||
@@ -1238,7 +1243,7 @@ class NumaConfig {
|
||||
}
|
||||
|
||||
|
||||
static NumaConfig from_l3_info(std::vector<L3Domain>&& domains, size_t bundleSize) {
|
||||
static NumaConfig from_l3_info(std::vector<L3Domain>&& domains, usize bundleSize) {
|
||||
assert(!domains.empty());
|
||||
|
||||
std::map<NumaIndex, std::vector<L3Domain>> list;
|
||||
@@ -1255,7 +1260,7 @@ class NumaConfig {
|
||||
do
|
||||
{
|
||||
changed = false;
|
||||
for (size_t j = 0; j + 1 < ds.size(); ++j)
|
||||
for (usize j = 0; j + 1 < ds.size(); ++j)
|
||||
{
|
||||
if (ds[j].cpus.size() + ds[j + 1].cpus.size() <= bundleSize)
|
||||
{
|
||||
@@ -1579,14 +1584,14 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase {
|
||||
mutable std::vector<SystemWideSharedConstant<T>> instances;
|
||||
mutable std::mutex mutex;
|
||||
|
||||
std::size_t get_discriminator(NumaIndex idx) const {
|
||||
usize 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));
|
||||
return static_cast<usize>(hash_string(s));
|
||||
}
|
||||
|
||||
void ensure_present(NumaIndex idx) const {
|
||||
|
||||
+8
-4
@@ -20,6 +20,7 @@
|
||||
#define PERFT_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
#include <variant>
|
||||
|
||||
#include "movegen.h"
|
||||
#include "position.h"
|
||||
@@ -31,11 +32,11 @@ namespace Stockfish::Benchmark {
|
||||
// Utility to verify move generation. All the leaf nodes up
|
||||
// to the given depth are generated and counted, and the sum is returned.
|
||||
template<bool Root>
|
||||
uint64_t perft(Position& pos, Depth depth) {
|
||||
u64 perft(Position& pos, Depth depth) {
|
||||
|
||||
StateInfo st;
|
||||
|
||||
uint64_t cnt, nodes = 0;
|
||||
u64 cnt, nodes = 0;
|
||||
const bool leaf = (depth == 2);
|
||||
|
||||
for (const auto& m : MoveList<LEGAL>(pos))
|
||||
@@ -55,10 +56,13 @@ uint64_t perft(Position& pos, Depth depth) {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
inline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) {
|
||||
inline std::variant<u64, PositionSetError>
|
||||
perft(const std::string& fen, Depth depth, bool isChess960) {
|
||||
StateInfo st;
|
||||
Position p;
|
||||
p.set(fen, isChess960, &st);
|
||||
|
||||
if (auto err = p.set(fen, isChess960, &st))
|
||||
return {*err};
|
||||
|
||||
return perft<true>(p, depth);
|
||||
}
|
||||
|
||||
+424
-295
File diff suppressed because it is too large
Load Diff
+66
-33
@@ -25,8 +25,11 @@
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "attacks.h"
|
||||
#include "bitboard.h"
|
||||
#include "types.h"
|
||||
|
||||
@@ -70,6 +73,12 @@ struct StateInfo {
|
||||
// elements are not invalidated upon list resizing.
|
||||
using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
|
||||
|
||||
// This error should be used whenever a position is suspected to be unsupported
|
||||
// by the engine. In particular positions that may cause hard errors like segmentation fault.
|
||||
struct PositionSetError: std::runtime_error {
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
// 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
|
||||
@@ -83,9 +92,9 @@ class Position {
|
||||
Position& operator=(const Position&) = delete;
|
||||
|
||||
// FEN string input/output
|
||||
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si);
|
||||
Position& set(const std::string& code, Color c, StateInfo* si);
|
||||
std::string fen() const;
|
||||
std::optional<PositionSetError> set(const std::string& fenStr, bool isChess960, StateInfo* si);
|
||||
std::optional<PositionSetError> set(const std::string& code, Color c, StateInfo* si);
|
||||
std::string fen() const;
|
||||
|
||||
// Position representation
|
||||
Bitboard pieces() const; // All pieces
|
||||
@@ -138,12 +147,11 @@ class Position {
|
||||
void do_move(Move m,
|
||||
StateInfo& newSt,
|
||||
bool givesCheck,
|
||||
DirtyPiece& dp,
|
||||
DirtyThreats& dts,
|
||||
Dirties& dirties,
|
||||
const TranspositionTable* tt,
|
||||
const SharedHistories* worker);
|
||||
void undo_move(Move m);
|
||||
void do_null_move(StateInfo& newSt, const TranspositionTable& tt);
|
||||
void do_null_move(StateInfo& newSt);
|
||||
void undo_null_move();
|
||||
|
||||
// Static Exchange Evaluation
|
||||
@@ -151,6 +159,7 @@ class Position {
|
||||
|
||||
// Accessing hash keys
|
||||
Key key() const;
|
||||
Key prefetch_key(Move m) const;
|
||||
Key material_key() const;
|
||||
Key pawn_key() const;
|
||||
Key minor_piece_key() const;
|
||||
@@ -167,11 +176,12 @@ class Position {
|
||||
int rule50_count() const;
|
||||
Value non_pawn_material(Color c) const;
|
||||
Value non_pawn_material() const;
|
||||
bool dtz_is_dtm() const; // Pawnless && (3-men || 4-men-minors-only)
|
||||
|
||||
// Position consistency check, for debugging
|
||||
bool pos_is_ok() const;
|
||||
bool material_key_is_ok() const;
|
||||
void flip();
|
||||
bool pos_is_ok() const;
|
||||
bool material_key_is_ok() const;
|
||||
std::optional<PositionSetError> flip();
|
||||
|
||||
StateInfo* state() const;
|
||||
|
||||
@@ -187,8 +197,9 @@ class Position {
|
||||
void set_check_info() const;
|
||||
|
||||
// Other helpers
|
||||
template<bool PutPiece, bool ComputeRay = true>
|
||||
template<bool ComputeRay = true>
|
||||
void update_piece_threats(Piece pc,
|
||||
bool putPiece,
|
||||
Square s,
|
||||
DirtyThreats* const dts,
|
||||
Bitboard noRaysContaining = -1ULL) const;
|
||||
@@ -201,23 +212,23 @@ class Position {
|
||||
Square& rto,
|
||||
DirtyThreats* const dts = nullptr,
|
||||
DirtyPiece* const dp = nullptr);
|
||||
Key adjust_key50(Key k) const;
|
||||
template<bool AfterMove = false>
|
||||
Key adjust_key50(Key k) const;
|
||||
|
||||
// Data members
|
||||
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;
|
||||
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;
|
||||
Dirties scratchDirties;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Position& pos);
|
||||
@@ -292,7 +303,7 @@ inline Bitboard Position::attacks_by(Color c) const {
|
||||
Bitboard threats = 0;
|
||||
Bitboard attackers = pieces(c, Pt);
|
||||
while (attackers)
|
||||
threats |= attacks_bb<Pt>(pop_lsb(attackers), pieces());
|
||||
threats |= Attacks::attacks_bb<Pt>(pop_lsb(attackers), pieces());
|
||||
return threats;
|
||||
}
|
||||
}
|
||||
@@ -307,8 +318,9 @@ inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSq
|
||||
|
||||
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 ? k : k ^ make_key((st->rule50 - 14) / 8);
|
||||
return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
|
||||
}
|
||||
|
||||
inline Key Position::pawn_key() const { return st->pawnKey; }
|
||||
@@ -331,9 +343,20 @@ inline int Position::rule50_count() const { return st->rule50; }
|
||||
|
||||
inline bool Position::is_chess960() const { return chess960; }
|
||||
|
||||
inline bool Position::dtz_is_dtm() const {
|
||||
return !count<PAWN>()
|
||||
&& (count<ALL_PIECES>() == 3 || (count<ALL_PIECES>() == 4 && !pieces(QUEEN, ROOK)));
|
||||
}
|
||||
|
||||
inline bool Position::capture(Move m) const {
|
||||
assert(m.is_ok());
|
||||
return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT;
|
||||
|
||||
const MoveType mt = m.type_of();
|
||||
|
||||
if (mt == NORMAL || mt == PROMOTION)
|
||||
return !empty(m.to_sq());
|
||||
|
||||
return mt == EN_PASSANT;
|
||||
}
|
||||
|
||||
// Returns true if a move is generated from the capture stage, having also
|
||||
@@ -341,7 +364,16 @@ inline bool Position::capture(Move m) const {
|
||||
// generation is needed to avoid the generation of duplicate moves.
|
||||
inline bool Position::capture_stage(Move m) const {
|
||||
assert(m.is_ok());
|
||||
return capture(m) || m.promotion_type() == QUEEN;
|
||||
|
||||
const MoveType mt = m.type_of();
|
||||
|
||||
if (mt == NORMAL)
|
||||
return !empty(m.to_sq());
|
||||
|
||||
if (mt == PROMOTION)
|
||||
return !empty(m.to_sq()) || m.promotion_type() == QUEEN;
|
||||
|
||||
return mt == EN_PASSANT;
|
||||
}
|
||||
|
||||
inline Piece Position::captured_piece() const { return st->capturedPiece; }
|
||||
@@ -354,14 +386,14 @@ inline void Position::put_piece(Piece pc, Square s, DirtyThreats* const dts) {
|
||||
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<true>(pc, s, dts);
|
||||
update_piece_threats(pc, true, s, dts);
|
||||
}
|
||||
|
||||
inline void Position::remove_piece(Square s, DirtyThreats* const dts) {
|
||||
Piece pc = board[s];
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<false>(pc, s, dts);
|
||||
update_piece_threats(pc, false, s, dts);
|
||||
|
||||
byTypeBB[ALL_PIECES] ^= s;
|
||||
byTypeBB[type_of(pc)] ^= s;
|
||||
@@ -376,7 +408,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts
|
||||
Bitboard fromTo = from | to;
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<false>(pc, from, dts, fromTo);
|
||||
update_piece_threats(pc, false, from, dts, fromTo);
|
||||
|
||||
byTypeBB[ALL_PIECES] ^= fromTo;
|
||||
byTypeBB[type_of(pc)] ^= fromTo;
|
||||
@@ -385,7 +417,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts
|
||||
board[to] = pc;
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<true>(pc, to, dts, fromTo);
|
||||
update_piece_threats(pc, true, to, dts, fromTo);
|
||||
}
|
||||
|
||||
inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) {
|
||||
@@ -394,17 +426,18 @@ inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) {
|
||||
remove_piece(s);
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<false, false>(old, s, dts);
|
||||
update_piece_threats<false>(old, false, s, dts);
|
||||
|
||||
put_piece(pc, s);
|
||||
|
||||
if (dts)
|
||||
update_piece_threats<true, false>(pc, s, dts);
|
||||
update_piece_threats<false>(pc, true, s, dts);
|
||||
}
|
||||
|
||||
inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) {
|
||||
new (&scratch_dts) DirtyThreats;
|
||||
do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt, nullptr);
|
||||
new (&scratchDirties.dirtyThreats) DirtyThreats;
|
||||
new (&scratchDirties.dirtyPawnPairs) DirtyPawnPairs;
|
||||
do_move(m, newSt, gives_check(m), scratchDirties, tt, nullptr);
|
||||
}
|
||||
|
||||
inline StateInfo* Position::state() const { return st; }
|
||||
|
||||
+476
-362
File diff suppressed because it is too large
Load Diff
+105
-59
@@ -23,14 +23,13 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
|
||||
#include "history.h"
|
||||
#include "misc.h"
|
||||
@@ -58,11 +57,52 @@ class OptionsMap;
|
||||
|
||||
namespace Search {
|
||||
|
||||
struct PVMoves {
|
||||
Move moves[MAX_PLY + 1];
|
||||
usize length = 0;
|
||||
|
||||
Move* begin() { return moves; }
|
||||
const Move* begin() const { return moves; }
|
||||
Move* end() { return moves + length; }
|
||||
const Move* end() const { return moves + length; }
|
||||
|
||||
Move& operator[](usize index) { return moves[index]; }
|
||||
const Move& operator[](usize index) const { return moves[index]; }
|
||||
|
||||
bool empty() const { return length == 0; }
|
||||
usize size() const { return length; }
|
||||
|
||||
void clear() { length = 0; }
|
||||
|
||||
void push_back(Move move) {
|
||||
assert(length < MAX_PLY + 1);
|
||||
moves[length++] = move;
|
||||
}
|
||||
|
||||
void resize(usize newSize) {
|
||||
assert(newSize <= length);
|
||||
length = newSize;
|
||||
}
|
||||
|
||||
void update(Move move, const PVMoves* childPv) {
|
||||
assert(childPv == nullptr || childPv->size() <= MAX_PLY);
|
||||
length = childPv ? childPv->length : 0;
|
||||
|
||||
if (childPv)
|
||||
{
|
||||
std::memcpy(moves + 1, childPv->moves, length * sizeof(Move));
|
||||
}
|
||||
|
||||
moves[0] = move;
|
||||
++length;
|
||||
}
|
||||
};
|
||||
|
||||
// Stack struct keeps track of the information we need to remember from nodes
|
||||
// shallower and deeper in the tree during the search. Each search thread has
|
||||
// its own array of Stack objects, indexed by the current ply.
|
||||
struct Stack {
|
||||
Move* pv;
|
||||
PVMoves* pv;
|
||||
PieceToHistory* continuationHistory;
|
||||
CorrectionHistory<PieceTo>* continuationCorrectionHistory;
|
||||
int ply;
|
||||
@@ -74,6 +114,7 @@ struct Stack {
|
||||
bool inCheck;
|
||||
bool ttPv;
|
||||
bool ttHit;
|
||||
bool followPV;
|
||||
int cutoffCnt;
|
||||
int reduction;
|
||||
};
|
||||
@@ -84,27 +125,32 @@ struct Stack {
|
||||
// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.
|
||||
struct RootMove {
|
||||
|
||||
explicit RootMove(Move m) :
|
||||
pv(1, m) {}
|
||||
explicit RootMove(Move m) { pv.push_back(m); }
|
||||
bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos);
|
||||
bool score_is_bound() const { return scoreLowerbound || scoreUpperbound; }
|
||||
bool score_is_exact_loss() const {
|
||||
return score != -VALUE_INFINITE && is_loss(score) && !score_is_bound();
|
||||
}
|
||||
void unset_bound_flags() { scoreLowerbound = scoreUpperbound = false; }
|
||||
bool operator==(const Move& m) const { return pv[0] == m; }
|
||||
// Sort in descending order
|
||||
bool operator<(const RootMove& m) const {
|
||||
return m.score != score ? m.score < score : m.previousScore < previousScore;
|
||||
}
|
||||
|
||||
uint64_t effort = 0;
|
||||
Value score = -VALUE_INFINITE;
|
||||
Value previousScore = -VALUE_INFINITE;
|
||||
Value averageScore = -VALUE_INFINITE;
|
||||
Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;
|
||||
Value uciScore = -VALUE_INFINITE;
|
||||
bool scoreLowerbound = false;
|
||||
bool scoreUpperbound = false;
|
||||
int selDepth = 0;
|
||||
int tbRank = 0;
|
||||
Value tbScore;
|
||||
std::vector<Move> pv;
|
||||
u64 effort = 0;
|
||||
Value score = -VALUE_INFINITE;
|
||||
Value previousScore = -VALUE_INFINITE;
|
||||
Value averageScore = -VALUE_INFINITE;
|
||||
Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;
|
||||
Value uciScore = -VALUE_INFINITE;
|
||||
bool scoreLowerbound = false;
|
||||
bool scoreUpperbound = false;
|
||||
bool previousScoreExact = false;
|
||||
int selDepth = 0;
|
||||
int tbRank = 0;
|
||||
Value tbScore;
|
||||
PVMoves pv, previousPV;
|
||||
};
|
||||
|
||||
using RootMoves = std::vector<RootMove>;
|
||||
@@ -126,7 +172,7 @@ struct LimitsType {
|
||||
std::vector<std::string> searchmoves;
|
||||
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
|
||||
int movestogo, depth, mate, perft, infinite;
|
||||
uint64_t nodes;
|
||||
u64 nodes;
|
||||
bool ponderMode;
|
||||
};
|
||||
|
||||
@@ -134,22 +180,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,
|
||||
std::map<NumaIndex, SharedHistories>& sharedHists,
|
||||
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& nets) :
|
||||
SharedState(const OptionsMap& optionsMap,
|
||||
ThreadPool& threadPool,
|
||||
TranspositionTable& transpositionTable,
|
||||
std::map<NumaIndex, SharedHistories>& sharedHists,
|
||||
const LazyNumaReplicatedSystemWide<Eval::NNUE::Network>& net) :
|
||||
options(optionsMap),
|
||||
threads(threadPool),
|
||||
tt(transpositionTable),
|
||||
sharedHistories(sharedHists),
|
||||
networks(nets) {}
|
||||
network(net) {}
|
||||
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
std::map<NumaIndex, SharedHistories>& sharedHistories;
|
||||
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks;
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
std::map<NumaIndex, SharedHistories>& sharedHistories;
|
||||
const LazyNumaReplicatedSystemWide<Eval::NNUE::Network>& network;
|
||||
};
|
||||
|
||||
class Worker;
|
||||
@@ -169,13 +215,13 @@ struct InfoShort {
|
||||
|
||||
struct InfoFull: InfoShort {
|
||||
int selDepth;
|
||||
size_t multiPV;
|
||||
usize multiPV;
|
||||
std::string_view wdl;
|
||||
std::string_view bound;
|
||||
size_t timeMs;
|
||||
size_t nodes;
|
||||
size_t nps;
|
||||
size_t tbHits;
|
||||
usize timeMs;
|
||||
usize nodes;
|
||||
usize nps;
|
||||
usize tbHits;
|
||||
std::string_view pv;
|
||||
int hashfull;
|
||||
};
|
||||
@@ -183,7 +229,7 @@ struct InfoFull: InfoShort {
|
||||
struct InfoIteration {
|
||||
int depth;
|
||||
std::string_view currmove;
|
||||
size_t currmovenumber;
|
||||
usize currmovenumber;
|
||||
};
|
||||
|
||||
// Skill structure is used to implement strength limit. If we have a UCI_Elo,
|
||||
@@ -208,7 +254,7 @@ struct Skill {
|
||||
}
|
||||
bool enabled() const { return level < 20.0; }
|
||||
bool time_to_pick(Depth depth) const { return depth == 1 + int(level); }
|
||||
Move pick_best(const RootMoves&, size_t multiPV);
|
||||
Move pick_best(const RootMoves&, usize multiPV);
|
||||
|
||||
double level;
|
||||
Move best = Move::none();
|
||||
@@ -236,10 +282,10 @@ class SearchManager: public ISearchManager {
|
||||
|
||||
void check_time(Search::Worker& worker) override;
|
||||
|
||||
void pv(Search::Worker& worker,
|
||||
const ThreadPool& threads,
|
||||
const TranspositionTable& tt,
|
||||
Depth depth);
|
||||
void output_pv(Search::Worker& worker,
|
||||
const ThreadPool& threads,
|
||||
const TranspositionTable& tt,
|
||||
Depth depth);
|
||||
|
||||
Stockfish::TimeManagement tm;
|
||||
double originalTimeAdjust;
|
||||
@@ -252,8 +298,6 @@ class SearchManager: public ISearchManager {
|
||||
Value bestPreviousAverageScore;
|
||||
bool stopOnPonderhit;
|
||||
|
||||
size_t id;
|
||||
|
||||
const UpdateContext& updates;
|
||||
};
|
||||
|
||||
@@ -269,9 +313,9 @@ class Worker {
|
||||
public:
|
||||
Worker(SharedState&,
|
||||
std::unique_ptr<ISearchManager>,
|
||||
size_t,
|
||||
size_t,
|
||||
size_t,
|
||||
usize,
|
||||
usize,
|
||||
usize,
|
||||
NumaReplicatedAccessToken);
|
||||
|
||||
// Called at instantiation to initialize reductions tables.
|
||||
@@ -291,14 +335,14 @@ class Worker {
|
||||
LowPlyHistory lowPlyHistory;
|
||||
|
||||
CapturePieceToHistory captureHistory;
|
||||
ContinuationHistory continuationHistory[2][2];
|
||||
CorrectionHistory<Continuation> continuationCorrectionHistory;
|
||||
|
||||
TTMoveHistory ttMoveHistory;
|
||||
SharedHistories& sharedHistory;
|
||||
ContinuationHistory (&continuationHistory)[2][2];
|
||||
|
||||
private:
|
||||
void iterative_deepening();
|
||||
bool iterative_deepening();
|
||||
|
||||
void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss);
|
||||
void
|
||||
@@ -309,13 +353,14 @@ class Worker {
|
||||
|
||||
// This is the main search function, for both PV and non-PV nodes
|
||||
template<NodeType nodeType>
|
||||
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
|
||||
Value
|
||||
search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, const bool cutNode);
|
||||
|
||||
// Quiescence search function, which is called by the main search
|
||||
template<NodeType nodeType>
|
||||
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta);
|
||||
|
||||
Depth reduction(bool i, Depth d, int mn, int delta) const;
|
||||
int reduction(bool i, Depth d, int mn, int delta) const;
|
||||
|
||||
// Pointer to the search manager, only allowed to be called by the main thread
|
||||
SearchManager* main_manager() const {
|
||||
@@ -324,25 +369,26 @@ class Worker {
|
||||
}
|
||||
|
||||
TimePoint elapsed() const;
|
||||
TimePoint elapsed_time() const;
|
||||
|
||||
Value evaluate(const Position&);
|
||||
|
||||
LimitsType limits;
|
||||
|
||||
size_t pvIdx, pvLast;
|
||||
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
||||
int selDepth, nmpMinPly;
|
||||
usize pvIdx, pvLast;
|
||||
RelaxedAtomic<u64> nodes, tbHits, bestMoveChanges;
|
||||
int selDepth, nmpMinPly;
|
||||
|
||||
Value optimism[COLOR_NB];
|
||||
|
||||
Position rootPos;
|
||||
StateInfo rootState;
|
||||
RootMoves rootMoves;
|
||||
Depth rootDepth, completedDepth;
|
||||
Depth rootDepth;
|
||||
Value rootDelta;
|
||||
|
||||
size_t threadIdx, numaThreadIdx, numaTotal;
|
||||
PVMoves lastIterationIdxPV;
|
||||
|
||||
usize threadIdx, numaThreadIdx, numaTotal;
|
||||
NumaReplicatedAccessToken numaAccessToken;
|
||||
|
||||
// Reductions lookup table initialized at startup
|
||||
@@ -353,10 +399,10 @@ class Worker {
|
||||
|
||||
Tablebases::Config tbConfig;
|
||||
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks;
|
||||
const OptionsMap& options;
|
||||
ThreadPool& threads;
|
||||
TranspositionTable& tt;
|
||||
const LazyNumaReplicatedSystemWide<Eval::NNUE::Network>& network;
|
||||
|
||||
// Used by NNUE
|
||||
Eval::NNUE::AccumulatorStack accumulatorStack;
|
||||
|
||||
@@ -40,11 +40,6 @@
|
||||
#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"
|
||||
@@ -100,14 +95,14 @@ namespace Stockfish {
|
||||
// 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;
|
||||
char executable_path[4096] = {0};
|
||||
usize path_length = 0;
|
||||
|
||||
#if defined(_WIN32)
|
||||
path_length = GetModuleFileNameA(NULL, executable_path, sizeof(executable_path));
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
uint32_t size = sizeof(executable_path);
|
||||
u32 size = sizeof(executable_path);
|
||||
if (_NSGetExecutablePath(executable_path, &size) == 0)
|
||||
{
|
||||
path_length = std::strlen(executable_path);
|
||||
@@ -122,8 +117,8 @@ inline std::string getExecutablePathHash() {
|
||||
}
|
||||
|
||||
#elif defined(__FreeBSD__)
|
||||
size_t size = sizeof(executable_path);
|
||||
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
|
||||
usize 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);
|
||||
@@ -171,10 +166,10 @@ inline std::string GetLastErrorAsString(DWORD error) {
|
||||
|
||||
//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);
|
||||
usize 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);
|
||||
@@ -278,12 +273,12 @@ class SharedMemoryBackend {
|
||||
|
||||
private:
|
||||
void initialize(const std::string& shm_name, const T& value) {
|
||||
const size_t total_size = sizeof(T) + sizeof(IS_INITIALIZED_VALUE);
|
||||
const usize 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 =
|
||||
[&](usize largePageSize) {
|
||||
const usize total_size_aligned =
|
||||
(total_size + largePageSize - 1) / largePageSize * largePageSize;
|
||||
|
||||
#if defined(_WIN64)
|
||||
@@ -530,12 +525,13 @@ struct SystemWideSharedConstant {
|
||||
|
||||
// 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());
|
||||
SystemWideSharedConstant(const T& value, usize discriminator = 0) {
|
||||
usize content_hash = std::hash<T>{}(value);
|
||||
usize executable_hash = hash_string(getExecutablePathHash());
|
||||
|
||||
char buf[1024];
|
||||
std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash, discriminator);
|
||||
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__)
|
||||
|
||||
+45
-38
@@ -20,7 +20,7 @@
|
||||
#define SHM_LINUX_H_INCLUDED
|
||||
|
||||
#if !defined(__linux__) || defined(__ANDROID__)
|
||||
#error shm_linux.h should not be included on this platform.
|
||||
#error shm_linux.h should not be included on this platform.
|
||||
#endif
|
||||
|
||||
#include <atomic>
|
||||
@@ -54,23 +54,23 @@ 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;
|
||||
static constexpr u32 SHM_MAGIC = 0xAD5F1A12;
|
||||
pthread_mutex_t mutex;
|
||||
std::atomic<u32> ref_count{0};
|
||||
std::atomic<bool> initialized{false};
|
||||
u32 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;
|
||||
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::mutex registry_mutex_;
|
||||
static std::vector<SharedMemoryBase*> active_instances_;
|
||||
|
||||
public:
|
||||
@@ -82,7 +82,8 @@ class SharedMemoryRegistry {
|
||||
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());
|
||||
std::remove(active_instances_.begin(), active_instances_.end(), instance),
|
||||
active_instances_.end());
|
||||
}
|
||||
|
||||
static void cleanup_all(bool skip_unmap = false) noexcept {
|
||||
@@ -93,7 +94,7 @@ class SharedMemoryRegistry {
|
||||
}
|
||||
};
|
||||
|
||||
inline std::mutex SharedMemoryRegistry::registry_mutex_;
|
||||
inline std::mutex SharedMemoryRegistry::registry_mutex_;
|
||||
inline std::vector<SharedMemoryBase*> SharedMemoryRegistry::active_instances_;
|
||||
|
||||
class CleanupHooks {
|
||||
@@ -140,23 +141,6 @@ class CleanupHooks {
|
||||
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>
|
||||
@@ -170,16 +154,16 @@ class SharedMemory: public detail::SharedMemoryBase {
|
||||
void* mapped_ptr_ = nullptr;
|
||||
T* data_ptr_ = nullptr;
|
||||
detail::ShmHeader* header_ptr_ = nullptr;
|
||||
size_t total_size_ = 0;
|
||||
usize total_size_ = 0;
|
||||
std::string sentinel_base_;
|
||||
std::string sentinel_path_;
|
||||
|
||||
static constexpr size_t calculate_total_size() noexcept {
|
||||
static constexpr usize calculate_total_size() noexcept {
|
||||
return sizeof(T) + sizeof(detail::ShmHeader);
|
||||
}
|
||||
|
||||
static std::string make_sentinel_base(const std::string& name) {
|
||||
char buf[32];
|
||||
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));
|
||||
@@ -386,7 +370,7 @@ class SharedMemory: public detail::SharedMemoryBase {
|
||||
|
||||
[[nodiscard]] const T& operator*() const noexcept { return *data_ptr_; }
|
||||
|
||||
[[nodiscard]] uint32_t ref_count() const noexcept {
|
||||
[[nodiscard]] u32 ref_count() const noexcept {
|
||||
return header_ptr_ ? header_ptr_->ref_count.load(std::memory_order_acquire) : 0;
|
||||
}
|
||||
|
||||
@@ -451,7 +435,7 @@ class SharedMemory: public detail::SharedMemoryBase {
|
||||
if (!header_ptr_)
|
||||
return;
|
||||
|
||||
uint32_t expected = header_ptr_->ref_count.load(std::memory_order_relaxed);
|
||||
u32 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))
|
||||
@@ -598,9 +582,6 @@ class SharedMemory: public detail::SharedMemoryBase {
|
||||
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)
|
||||
{
|
||||
@@ -608,6 +589,28 @@ class SharedMemory: public detail::SharedMemoryBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef MADV_POPULATE_WRITE
|
||||
// Pre-populate, attempting first with THP, to guarantee that the memory
|
||||
// is allocated and avoid crashing on the first write.
|
||||
madvise(mapped_ptr_, total_size_, MADV_HUGEPAGE);
|
||||
|
||||
const bool populated = madvise(mapped_ptr_, total_size_, MADV_POPULATE_WRITE) == 0;
|
||||
#else
|
||||
const bool populated = false;
|
||||
#endif
|
||||
// If the THP population failed, try with fallocate
|
||||
if (!populated && posix_fallocate(fd_, 0, static_cast<off_t>(total_size_)) != 0)
|
||||
{
|
||||
// Release any partially populated pages by a failed MADV_POPULATE_WRITE.
|
||||
// TODO: Not robust. Need to avoid residual pages if the process
|
||||
// is terminated between the failed madvise and the ftruncate.
|
||||
int err = ftruncate(fd_, 0);
|
||||
(void) err;
|
||||
munmap(mapped_ptr_, total_size_);
|
||||
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));
|
||||
@@ -628,7 +631,7 @@ class SharedMemory: public detail::SharedMemoryBase {
|
||||
|
||||
struct stat st;
|
||||
fstat(fd_, &st);
|
||||
if (static_cast<size_t>(st.st_size) < total_size_)
|
||||
if (static_cast<usize>(st.st_size) < total_size_)
|
||||
{
|
||||
invalid_header = true;
|
||||
return false;
|
||||
@@ -641,6 +644,10 @@ class SharedMemory: public detail::SharedMemoryBase {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef MADV_HUGEPAGE
|
||||
madvise(mapped_ptr_, total_size_, MADV_HUGEPAGE);
|
||||
#endif
|
||||
|
||||
data_ptr_ = static_cast<T*>(mapped_ptr_);
|
||||
header_ptr_ = std::launder(
|
||||
reinterpret_cast<detail::ShmHeader*>(static_cast<char*>(mapped_ptr_) + sizeof(T)));
|
||||
|
||||
+187
-119
@@ -29,14 +29,17 @@
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include "../attacks.h"
|
||||
#include "../bitboard.h"
|
||||
#include "../misc.h"
|
||||
#include "../movegen.h"
|
||||
@@ -57,6 +60,40 @@
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef NO_TABLEBASES
|
||||
|
||||
// Stubbed out impls
|
||||
namespace Stockfish::Tablebases {
|
||||
|
||||
int MaxCardinality;
|
||||
|
||||
void init(const std::string&) {}
|
||||
|
||||
WDLScore probe_wdl(Position&, ProbeState* result) {
|
||||
*result = FAIL;
|
||||
return WDLDraw;
|
||||
}
|
||||
|
||||
int probe_dtz(Position&, ProbeState* result) {
|
||||
*result = FAIL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool root_probe(Position&, Search::RootMoves&, bool, bool, const std::function<bool()>&) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool root_probe_wdl(Position&, Search::RootMoves&, bool) { return false; }
|
||||
|
||||
Config rank_root_moves(
|
||||
const OptionsMap&, Position&, Search::RootMoves&, bool, const std::function<bool()>&) {
|
||||
return Config{};
|
||||
}
|
||||
|
||||
} // namespace Stockfish::Tablebases
|
||||
|
||||
#else
|
||||
|
||||
using namespace Stockfish::Tablebases;
|
||||
|
||||
int Stockfish::Tablebases::MaxCardinality;
|
||||
@@ -113,12 +150,12 @@ template<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>
|
||||
inline void swap_endian(T& x) {
|
||||
static_assert(std::is_unsigned_v<T>, "Argument of swap_endian not unsigned");
|
||||
|
||||
uint8_t tmp, *c = (uint8_t*) &x;
|
||||
u8 tmp, *c = (u8*) &x;
|
||||
for (int i = 0; i < Half; ++i)
|
||||
tmp = c[i], c[i] = c[End - i], c[End - i] = tmp;
|
||||
}
|
||||
template<>
|
||||
inline void swap_endian<uint8_t>(uint8_t&) {}
|
||||
inline void swap_endian<u8>(u8&) {}
|
||||
|
||||
template<typename T, int LE>
|
||||
T number(void* addr) {
|
||||
@@ -159,7 +196,7 @@ struct SparseEntry {
|
||||
|
||||
static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");
|
||||
|
||||
using Sym = uint16_t; // Huffman symbol
|
||||
using Sym = u16; // Huffman symbol
|
||||
|
||||
struct LR {
|
||||
enum Side {
|
||||
@@ -167,9 +204,9 @@ struct LR {
|
||||
Right
|
||||
};
|
||||
|
||||
uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12
|
||||
// bits is the right-hand symbol. If the symbol has length 1,
|
||||
// then the left-hand symbol is the stored value.
|
||||
u8 lr[3]; // The first 12 bits is the left-hand symbol, the second 12
|
||||
// bits is the right-hand symbol. If the symbol has length 1,
|
||||
// then the left-hand symbol is the stored value.
|
||||
template<Side S>
|
||||
Sym get() {
|
||||
return S == Left ? ((lr[1] & 0xF) << 8) | lr[0]
|
||||
@@ -191,7 +228,7 @@ static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes");
|
||||
// time only existence of the file is checked.
|
||||
class TBFile: public std::ifstream {
|
||||
|
||||
std::string fname;
|
||||
std::filesystem::path fname;
|
||||
|
||||
public:
|
||||
// Look for and open the file among the Paths directories where the .rtbw
|
||||
@@ -200,21 +237,13 @@ class TBFile: public std::ifstream {
|
||||
//
|
||||
// Example:
|
||||
// C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6
|
||||
static std::string Paths;
|
||||
static std::vector<std::filesystem::path> Paths;
|
||||
|
||||
TBFile(const std::string& f) {
|
||||
|
||||
#ifndef _WIN32
|
||||
constexpr char SepChar = ':';
|
||||
#else
|
||||
constexpr char SepChar = ';';
|
||||
#endif
|
||||
std::stringstream ss(Paths);
|
||||
std::string path;
|
||||
|
||||
while (std::getline(ss, path, SepChar))
|
||||
for (const auto& path : Paths)
|
||||
{
|
||||
fname = path + "/" + f;
|
||||
fname = path / std::filesystem::path(f);
|
||||
std::ifstream::open(fname);
|
||||
if (is_open())
|
||||
return;
|
||||
@@ -222,11 +251,11 @@ class TBFile: public std::ifstream {
|
||||
}
|
||||
|
||||
// Memory map the file and check it.
|
||||
uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {
|
||||
u8* map(void** baseAddress, u64* mapping, TBType type) {
|
||||
if (is_open())
|
||||
close(); // Need to re-open to get native file descriptor
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifndef _WIN32
|
||||
struct stat statbuf;
|
||||
int fd = ::open(fname.c_str(), O_RDONLY);
|
||||
|
||||
@@ -237,25 +266,25 @@ class TBFile: public std::ifstream {
|
||||
|
||||
if (statbuf.st_size % 64 != 16)
|
||||
{
|
||||
std::cerr << "Corrupt tablebase file " << fname << std::endl;
|
||||
std::cerr << "Corrupt tablebase file " << fname.string() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
*mapping = statbuf.st_size;
|
||||
*baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
#if defined(MADV_RANDOM)
|
||||
#if defined(MADV_RANDOM)
|
||||
madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);
|
||||
#endif
|
||||
#endif
|
||||
::close(fd);
|
||||
|
||||
if (*baseAddress == MAP_FAILED)
|
||||
{
|
||||
std::cerr << "Could not mmap() " << fname << std::endl;
|
||||
std::cerr << "Could not mmap() " << fname.string() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
#else
|
||||
#else
|
||||
// Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.
|
||||
HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||
HANDLE fd = CreateFileW(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||
OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
|
||||
|
||||
if (fd == INVALID_HANDLE_VALUE)
|
||||
@@ -266,7 +295,7 @@ class TBFile: public std::ifstream {
|
||||
|
||||
if (size_low % 64 != 16)
|
||||
{
|
||||
std::cerr << "Corrupt tablebase file " << fname << std::endl;
|
||||
std::cerr << "Corrupt tablebase file " << fname.string() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@@ -279,23 +308,23 @@ class TBFile: public std::ifstream {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
*mapping = uint64_t(mmap);
|
||||
*mapping = u64(mmap);
|
||||
*baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);
|
||||
|
||||
if (!*baseAddress)
|
||||
{
|
||||
std::cerr << "MapViewOfFile() failed, name = " << fname
|
||||
std::cerr << "MapViewOfFile() failed, name = " << fname.string()
|
||||
<< ", error = " << GetLastError() << std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
#endif
|
||||
uint8_t* data = (uint8_t*) *baseAddress;
|
||||
#endif
|
||||
u8* data = (u8*) *baseAddress;
|
||||
|
||||
constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}};
|
||||
constexpr u8 Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}};
|
||||
|
||||
if (memcmp(data, Magics[type == WDL], 4))
|
||||
{
|
||||
std::cerr << "Corrupted table in file " << fname << std::endl;
|
||||
std::cerr << "Corrupted table in file " << fname.string() << std::endl;
|
||||
unmap(*baseAddress, *mapping);
|
||||
return *baseAddress = nullptr, nullptr;
|
||||
}
|
||||
@@ -303,44 +332,43 @@ class TBFile: public std::ifstream {
|
||||
return data + 4; // Skip Magics's header
|
||||
}
|
||||
|
||||
static void unmap(void* baseAddress, uint64_t mapping) {
|
||||
static void unmap(void* baseAddress, u64 mapping) {
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifndef _WIN32
|
||||
munmap(baseAddress, mapping);
|
||||
#else
|
||||
#else
|
||||
UnmapViewOfFile(baseAddress);
|
||||
CloseHandle((HANDLE) mapping);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
std::string TBFile::Paths;
|
||||
std::vector<std::filesystem::path> TBFile::Paths;
|
||||
|
||||
// struct PairsData contains low-level indexing information to access TB data.
|
||||
// There are 8, 4, or 2 PairsData records for each TBTable, according to the type
|
||||
// of table and if positions have pawns or not. It is populated at first access.
|
||||
struct PairsData {
|
||||
uint8_t flags; // Table flags, see enum TBFlag
|
||||
uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols
|
||||
uint8_t minSymLen; // Minimum length in bits of the Huffman symbols
|
||||
uint32_t blocksNum; // Number of blocks in the TB file
|
||||
size_t sizeofBlock; // Block size in bytes
|
||||
size_t span; // About every span values there is a SparseIndex[] entry
|
||||
Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value
|
||||
LR* btree; // btree[sym] stores the left and right symbols that expand sym
|
||||
uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536
|
||||
uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum
|
||||
SparseEntry* sparseIndex; // Partial indices into blockLength[]
|
||||
size_t sparseIndexSize; // Size of SparseIndex[] table
|
||||
uint8_t* data; // Start of Huffman compressed data
|
||||
std::vector<uint64_t>
|
||||
u8 flags; // Table flags, see enum TBFlag
|
||||
u8 maxSymLen; // Maximum length in bits of the Huffman symbols
|
||||
u8 minSymLen; // Minimum length in bits of the Huffman symbols
|
||||
u32 blocksNum; // Number of blocks in the TB file
|
||||
usize sizeofBlock; // Block size in bytes
|
||||
usize span; // About every span values there is a SparseIndex[] entry
|
||||
Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value
|
||||
LR* btree; // btree[sym] stores the left and right symbols that expand sym
|
||||
u16* blockLength; // Number of stored positions (minus one) for each block: 1..65536
|
||||
u32 blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum
|
||||
SparseEntry* sparseIndex; // Partial indices into blockLength[]
|
||||
usize sparseIndexSize; // Size of SparseIndex[] table
|
||||
u8* data; // Start of Huffman compressed data
|
||||
std::vector<u64>
|
||||
base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l
|
||||
std::vector<uint8_t>
|
||||
symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256
|
||||
Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups
|
||||
uint64_t groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces
|
||||
int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1)
|
||||
uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)
|
||||
std::vector<u8> symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256
|
||||
Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups
|
||||
u64 groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces
|
||||
int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1)
|
||||
u16 map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)
|
||||
};
|
||||
|
||||
// struct TBTable contains indexing information to access the corresponding TBFile.
|
||||
@@ -355,14 +383,14 @@ struct TBTable {
|
||||
|
||||
std::atomic_bool ready;
|
||||
void* baseAddress;
|
||||
uint8_t* map;
|
||||
uint64_t mapping;
|
||||
u8* map;
|
||||
u64 mapping;
|
||||
Key key;
|
||||
Key key2;
|
||||
int pieceCount;
|
||||
bool hasPawns;
|
||||
bool hasUniquePieces;
|
||||
uint8_t pawnCount[2]; // [Lead color / other color]
|
||||
u8 pawnCount[2]; // [Lead color / other color]
|
||||
PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0]
|
||||
|
||||
PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; }
|
||||
@@ -386,7 +414,16 @@ TBTable<WDL>::TBTable(const std::string& code) :
|
||||
StateInfo st;
|
||||
Position pos;
|
||||
|
||||
key = pos.set(code, WHITE, &st).material_key();
|
||||
auto err = pos.set(code, WHITE, &st);
|
||||
// IMPORTANT: We cannot assert here because it WILL produce validation errors
|
||||
// on some TB7 and higher positions due to the black king being attacked
|
||||
// while white is to move. This is not fixable without significant changes.
|
||||
// As using pos.set here is already a very hacky way to achieve the desired
|
||||
// result here so we leave it for now. The validation checks that fail are
|
||||
// done after the position is fully set up, so it's fine for now.
|
||||
// assert(!err.has_value());
|
||||
(void) err;
|
||||
key = pos.material_key();
|
||||
pieceCount = pos.count<ALL_PIECES>();
|
||||
hasPawns = pos.pieces(PAWN);
|
||||
|
||||
@@ -404,7 +441,16 @@ TBTable<WDL>::TBTable(const std::string& code) :
|
||||
pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);
|
||||
pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);
|
||||
|
||||
key2 = pos.set(code, BLACK, &st).material_key();
|
||||
err = pos.set(code, BLACK, &st);
|
||||
// IMPORTANT: We cannot assert here because it WILL produce validation errors
|
||||
// on some TB7 and higher positions due to the black king being attacked
|
||||
// while white is to move. This is not fixable without significant changes.
|
||||
// As using pos.set here is already a very hacky way to achieve the desired
|
||||
// result here so we leave it for now. The validation checks that fail are
|
||||
// done after the position is fully set up, so it's fine for now.
|
||||
// assert(!err.has_value());
|
||||
(void) err;
|
||||
key2 = pos.material_key();
|
||||
}
|
||||
|
||||
template<>
|
||||
@@ -444,15 +490,15 @@ class TBTables {
|
||||
|
||||
std::deque<TBTable<WDL>> wdlTable;
|
||||
std::deque<TBTable<DTZ>> dtzTable;
|
||||
size_t foundDTZFiles = 0;
|
||||
size_t foundWDLFiles = 0;
|
||||
usize foundDTZFiles = 0;
|
||||
usize foundWDLFiles = 0;
|
||||
|
||||
void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
|
||||
uint32_t homeBucket = uint32_t(key) & (Size - 1);
|
||||
Entry entry{key, wdl, dtz};
|
||||
u32 homeBucket = u32(key) & (Size - 1);
|
||||
Entry entry{key, wdl, dtz};
|
||||
|
||||
// Ensure last element is empty to avoid overflow when looking up
|
||||
for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)
|
||||
for (u32 bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)
|
||||
{
|
||||
Key otherKey = hashTable[bucket].key;
|
||||
if (otherKey == key || !hashTable[bucket].get<WDL>())
|
||||
@@ -463,7 +509,7 @@ class TBTables {
|
||||
|
||||
// Robin Hood hashing: If we've probed for longer than this element,
|
||||
// insert here and search for a new spot for the other element instead.
|
||||
uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1);
|
||||
u32 otherHomeBucket = u32(otherKey) & (Size - 1);
|
||||
if (otherHomeBucket > homeBucket)
|
||||
{
|
||||
std::swap(entry, hashTable[bucket]);
|
||||
@@ -478,7 +524,7 @@ class TBTables {
|
||||
public:
|
||||
template<TBType Type>
|
||||
TBTable<Type>* get(Key key) {
|
||||
for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry)
|
||||
for (const Entry* entry = &hashTable[u32(key) & (Size - 1)];; ++entry)
|
||||
{
|
||||
if (entry->key == key || !entry->get<Type>())
|
||||
return entry->get<Type>();
|
||||
@@ -553,7 +599,7 @@ void TBTables::add(const std::vector<PieceType>& pieces) {
|
||||
// Huffman codes are the same for all blocks in the table. A non-symmetric pawnless TB file
|
||||
// will have one table for wtm and one for btm, a TB file with pawns will have tables per
|
||||
// file a,b,c,d also, in this case, one set for wtm and one for btm.
|
||||
int decompress_pairs(PairsData* d, uint64_t idx) {
|
||||
int decompress_pairs(PairsData* d, u64 idx) {
|
||||
|
||||
// Special case where all table positions store the same value
|
||||
if (d->flags & TBFlag::SingleValue)
|
||||
@@ -574,11 +620,11 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
||||
// I(k) = k * d->span + d->span / 2 (1)
|
||||
|
||||
// First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)
|
||||
uint32_t k = uint32_t(idx / d->span);
|
||||
u32 k = u32(idx / d->span);
|
||||
|
||||
// Then we read the corresponding SparseIndex[] entry
|
||||
uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
|
||||
int offset = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);
|
||||
u32 block = number<u32, LittleEndian>(&d->sparseIndex[k].block);
|
||||
int offset = number<u16, LittleEndian>(&d->sparseIndex[k].offset);
|
||||
|
||||
// Now compute the difference idx - I(k). From the definition of k, we know that
|
||||
//
|
||||
@@ -599,12 +645,12 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
||||
offset -= d->blockLength[block++] + 1;
|
||||
|
||||
// Finally, we find the start address of our block of canonical Huffman symbols
|
||||
uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(block) * d->sizeofBlock));
|
||||
u32* ptr = (u32*) (d->data + (u64(block) * d->sizeofBlock));
|
||||
|
||||
// Read the first 64 bits in our block, this is a (truncated) sequence of
|
||||
// unknown number of symbols of unknown length but we know the first one
|
||||
// is at the beginning of this 64-bit sequence.
|
||||
uint64_t buf64 = number<uint64_t, BigEndian>(ptr);
|
||||
u64 buf64 = number<u64, BigEndian>(ptr);
|
||||
ptr += 2;
|
||||
int buf64Size = 64;
|
||||
Sym sym;
|
||||
@@ -641,7 +687,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
|
||||
if (buf64Size <= 32)
|
||||
{ // Refill the buffer
|
||||
buf64Size += 32;
|
||||
buf64 |= uint64_t(number<uint32_t, BigEndian>(ptr++)) << (64 - buf64Size);
|
||||
buf64 |= u64(number<u32, BigEndian>(ptr++)) << (64 - buf64Size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -689,12 +735,12 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
|
||||
|
||||
auto flags = entry->get(0, f)->flags;
|
||||
|
||||
uint8_t* map = entry->map;
|
||||
uint16_t* idx = entry->get(0, f)->map_idx;
|
||||
u8* map = entry->map;
|
||||
u16* idx = entry->get(0, f)->map_idx;
|
||||
if (flags & TBFlag::Mapped)
|
||||
{
|
||||
if (flags & TBFlag::Wide)
|
||||
value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value];
|
||||
value = ((u16*) map)[idx[WDLMap[wdl + 2]] + value];
|
||||
else
|
||||
value = map[idx[WDLMap[wdl + 2]] + value];
|
||||
}
|
||||
@@ -709,12 +755,12 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
|
||||
return value + 1;
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// encode k pieces of the same type and color, first sort the pieces by square in
|
||||
@@ -727,7 +773,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
|
||||
|
||||
Square squares[TBPIECES];
|
||||
Piece pieces[TBPIECES];
|
||||
uint64_t idx;
|
||||
u64 idx;
|
||||
int next = 0, size = 0, leadPawnsCnt = 0;
|
||||
PairsData* d;
|
||||
Bitboard b, leadPawns = 0;
|
||||
@@ -924,14 +970,16 @@ encode_remaining:
|
||||
while (d->groupLen[++next])
|
||||
{
|
||||
std::stable_sort(groupSq, groupSq + d->groupLen[next]);
|
||||
uint64_t n = 0;
|
||||
u64 n = 0;
|
||||
|
||||
// Map down a square if "comes later" than a square in the previous
|
||||
// groups (similar to what was done earlier for leading group pieces).
|
||||
for (int i = 0; i < d->groupLen[next]; ++i)
|
||||
{
|
||||
auto f = [&](Square s) { return groupSq[i] > s; };
|
||||
auto adjust = std::count_if(squares, groupSq, f);
|
||||
int adjust = 0;
|
||||
DISABLE_CLANG_LOOP_VEC
|
||||
for (Square* sq = squares; sq != groupSq; ++sq)
|
||||
adjust += groupSq[i] > *sq;
|
||||
n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns];
|
||||
}
|
||||
|
||||
@@ -981,10 +1029,10 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
|
||||
// pawns/pieces -> remaining pawns -> remaining pieces. In particular the
|
||||
// first group is at order[0] position and the remaining pawns, when present,
|
||||
// are at order[1] position.
|
||||
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
|
||||
int next = pp ? 2 : 1;
|
||||
int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0);
|
||||
uint64_t idx = 1;
|
||||
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
|
||||
int next = pp ? 2 : 1;
|
||||
int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0);
|
||||
u64 idx = 1;
|
||||
|
||||
for (int k = 0; next < n || k == order[0] || k == order[1]; ++k)
|
||||
if (k == order[0]) // Leading pawns or pieces
|
||||
@@ -1010,7 +1058,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
|
||||
// In Recursive Pairing each symbol represents a pair of children symbols. So
|
||||
// read d->btree[] symbols data and expand each one in his left and right child
|
||||
// symbol until reaching the leaves that represent the symbol value.
|
||||
uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
|
||||
u8 set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
|
||||
|
||||
visited[s] = true; // We can set it now because tree is acyclic
|
||||
Sym sr = d->btree[s].get<LR::Right>();
|
||||
@@ -1029,7 +1077,7 @@ uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
|
||||
return d->symlen[sl] + d->symlen[sr] + 1;
|
||||
}
|
||||
|
||||
uint8_t* set_sizes(PairsData* d, uint8_t* data) {
|
||||
u8* set_sizes(PairsData* d, u8* data) {
|
||||
|
||||
d->flags = *data++;
|
||||
|
||||
@@ -1043,14 +1091,14 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
|
||||
|
||||
// groupLen[] is a zero-terminated list of group lengths, the last groupIdx[]
|
||||
// element stores the biggest index that is the tb size.
|
||||
uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen];
|
||||
u64 tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen];
|
||||
|
||||
d->sizeofBlock = 1ULL << *data++;
|
||||
d->span = 1ULL << *data++;
|
||||
d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up
|
||||
auto padding = number<uint8_t, LittleEndian>(data++);
|
||||
d->blocksNum = number<uint32_t, LittleEndian>(data);
|
||||
data += sizeof(uint32_t);
|
||||
d->sparseIndexSize = usize((tbSize + d->span - 1) / d->span); // Round up
|
||||
auto padding = number<u8, LittleEndian>(data++);
|
||||
d->blocksNum = number<u32, LittleEndian>(data);
|
||||
data += sizeof(u32);
|
||||
d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
|
||||
// does not point out of range.
|
||||
d->maxSymLen = *data++;
|
||||
@@ -1087,8 +1135,8 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
|
||||
d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits
|
||||
|
||||
data += base64_size * sizeof(Sym);
|
||||
d->symlen.resize(number<uint16_t, LittleEndian>(data));
|
||||
data += sizeof(uint16_t);
|
||||
d->symlen.resize(number<u16, LittleEndian>(data));
|
||||
data += sizeof(u16);
|
||||
d->btree = (LR*) data;
|
||||
|
||||
// The compression scheme used is "Recursive Pairing", that replaces the most
|
||||
@@ -1105,9 +1153,9 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
|
||||
return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1);
|
||||
}
|
||||
|
||||
uint8_t* set_dtz_map(TBTable<WDL>&, uint8_t* data, File) { return data; }
|
||||
u8* set_dtz_map(TBTable<WDL>&, u8* data, File) { return data; }
|
||||
|
||||
uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {
|
||||
u8* set_dtz_map(TBTable<DTZ>& e, u8* data, File maxFile) {
|
||||
|
||||
e.map = data;
|
||||
|
||||
@@ -1121,15 +1169,15 @@ uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {
|
||||
data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{ // Sequence like 3,x,x,x,1,x,0,2,x,x
|
||||
e.get(0, f)->map_idx[i] = uint16_t((uint16_t*) data - (uint16_t*) e.map + 1);
|
||||
data += 2 * number<uint16_t, LittleEndian>(data) + 2;
|
||||
e.get(0, f)->map_idx[i] = u16((u16*) data - (u16*) e.map + 1);
|
||||
data += 2 * number<u16, LittleEndian>(data) + 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1);
|
||||
e.get(0, f)->map_idx[i] = u16(data - e.map + 1);
|
||||
data += *data + 1;
|
||||
}
|
||||
}
|
||||
@@ -1142,7 +1190,7 @@ uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {
|
||||
// Populate entry's PairsData records with data from the just memory-mapped file.
|
||||
// Called at first access.
|
||||
template<typename T>
|
||||
void set(T& e, uint8_t* data) {
|
||||
void set(T& e, u8* data) {
|
||||
|
||||
PairsData* d;
|
||||
|
||||
@@ -1199,14 +1247,14 @@ void set(T& e, uint8_t* data) {
|
||||
for (File f = FILE_A; f <= maxFile; ++f)
|
||||
for (int i = 0; i < sides; i++)
|
||||
{
|
||||
(d = e.get(i, f))->blockLength = (uint16_t*) data;
|
||||
data += d->blockLengthSize * sizeof(uint16_t);
|
||||
(d = e.get(i, f))->blockLength = (u16*) data;
|
||||
data += d->blockLengthSize * sizeof(u16);
|
||||
}
|
||||
|
||||
for (File f = FILE_A; f <= maxFile; ++f)
|
||||
for (int i = 0; i < sides; i++)
|
||||
{
|
||||
data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment
|
||||
data = (u8*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment
|
||||
(d = e.get(i, f))->data = data;
|
||||
data += d->blocksNum * d->sizeofBlock;
|
||||
}
|
||||
@@ -1244,7 +1292,7 @@ void* mapped(TBTable<Type>& e, const Position& pos) {
|
||||
fname =
|
||||
(e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? ".rtbw" : ".rtbz");
|
||||
|
||||
uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type);
|
||||
u8* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type);
|
||||
|
||||
if (data)
|
||||
set(e, data);
|
||||
@@ -1286,8 +1334,8 @@ WDLScore search(Position& pos, ProbeState* result) {
|
||||
WDLScore value, bestValue = WDLLoss;
|
||||
StateInfo st;
|
||||
|
||||
auto moveList = MoveList<LEGAL>(pos);
|
||||
size_t totalCount = moveList.size(), moveCount = 0;
|
||||
auto moveList = MoveList<LEGAL>(pos);
|
||||
usize totalCount = moveList.size(), moveCount = 0;
|
||||
|
||||
for (const Move move : moveList)
|
||||
{
|
||||
@@ -1350,9 +1398,24 @@ void Tablebases::init(const std::string& paths) {
|
||||
|
||||
TBTables.clear();
|
||||
MaxCardinality = 0;
|
||||
TBFile::Paths = paths;
|
||||
TBFile::Paths.clear();
|
||||
|
||||
if (paths.empty())
|
||||
if (!paths.empty())
|
||||
{
|
||||
#ifndef _WIN32
|
||||
constexpr char SepChar = ':';
|
||||
#else
|
||||
constexpr char SepChar = ';';
|
||||
#endif
|
||||
|
||||
std::stringstream ss(paths);
|
||||
std::string path;
|
||||
while (std::getline(ss, path, SepChar))
|
||||
if (!path.empty())
|
||||
TBFile::Paths.emplace_back(path_from_utf8(path));
|
||||
}
|
||||
|
||||
if (TBFile::Paths.empty())
|
||||
return;
|
||||
|
||||
// MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27
|
||||
@@ -1385,7 +1448,7 @@ void Tablebases::init(const std::string& paths) {
|
||||
if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0
|
||||
{
|
||||
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
|
||||
if ((PseudoAttacks[KING][s1] | s1) & s2)
|
||||
if ((Attacks::PseudoAttacks[KING][s1] | s1) & s2)
|
||||
continue; // Illegal position
|
||||
|
||||
else if (!off_A1H8(s1) && off_A1H8(s2) > 0)
|
||||
@@ -1739,8 +1802,11 @@ Config Tablebases::rank_root_moves(const OptionsMap& options,
|
||||
config.probeDepth = 0;
|
||||
}
|
||||
|
||||
if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
|
||||
if (config.cardinality >= pos.count<ALL_PIECES>() && !pos.can_castle(ANY_CASTLING))
|
||||
{
|
||||
// Use DTZ to rank the moves if checkmate is the only zeroing move
|
||||
rankDTZ = rankDTZ || pos.dtz_is_dtm();
|
||||
|
||||
// Rank moves using DTZ tables, bail out if time_abort flags zeitnot
|
||||
config.rootInTB =
|
||||
root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ, time_abort);
|
||||
@@ -1774,3 +1840,5 @@ Config Tablebases::rank_root_moves(const OptionsMap& options,
|
||||
return config;
|
||||
}
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif // NO_TABLEBASES
|
||||
|
||||
+61
-60
@@ -20,11 +20,14 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
#include "bitboard.h"
|
||||
@@ -44,9 +47,9 @@ namespace Stockfish {
|
||||
// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
||||
Thread::Thread(Search::SharedState& sharedState,
|
||||
std::unique_ptr<Search::ISearchManager> sm,
|
||||
size_t n,
|
||||
size_t numaN,
|
||||
size_t totalNumaCount,
|
||||
usize n,
|
||||
usize numaN,
|
||||
usize totalNumaCount,
|
||||
OptionalThreadToNumaNodeBinder binder) :
|
||||
idx(n),
|
||||
idxInNuma(numaN),
|
||||
@@ -138,10 +141,10 @@ void Thread::idle_loop() {
|
||||
|
||||
Search::SearchManager* ThreadPool::main_manager() { return main_thread()->worker->main_manager(); }
|
||||
|
||||
uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); }
|
||||
uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); }
|
||||
u64 ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); }
|
||||
u64 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; }
|
||||
static usize next_power_of_two(u64 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.
|
||||
@@ -159,7 +162,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
|
||||
boundThreadToNumaNode.clear();
|
||||
}
|
||||
|
||||
const size_t requested = sharedState.options["Threads"];
|
||||
const usize requested = sharedState.options["Threads"];
|
||||
|
||||
if (requested > 0) // create new thread(s)
|
||||
{
|
||||
@@ -181,7 +184,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
|
||||
return true;
|
||||
}();
|
||||
|
||||
std::map<NumaIndex, size_t> counts;
|
||||
std::map<NumaIndex, usize> counts;
|
||||
boundThreadToNumaNode = doBindThreads
|
||||
? numaConfig.distribute_threads_among_numa_nodes(requested)
|
||||
: std::vector<NumaIndex>{};
|
||||
@@ -190,7 +193,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
|
||||
counts[0] = requested; // Pretend all threads are part of numa node 0
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < boundThreadToNumaNode.size(); ++i)
|
||||
for (usize i = 0; i < boundThreadToNumaNode.size(); ++i)
|
||||
counts[boundThreadToNumaNode[i]]++;
|
||||
}
|
||||
|
||||
@@ -198,7 +201,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
|
||||
for (auto pair : counts)
|
||||
{
|
||||
NumaIndex numaIndex = pair.first;
|
||||
uint64_t count = pair.second;
|
||||
u64 count = pair.second;
|
||||
auto f = [&]() {
|
||||
sharedState.sharedHistories.try_emplace(numaIndex, next_power_of_two(count));
|
||||
};
|
||||
@@ -213,7 +216,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
|
||||
|
||||
while (threads.size() < requested)
|
||||
{
|
||||
const size_t threadId = threads.size();
|
||||
const usize threadId = threads.size();
|
||||
const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0;
|
||||
auto create_thread = [&]() {
|
||||
auto manager = threadId == 0
|
||||
@@ -268,17 +271,17 @@ void ThreadPool::clear() {
|
||||
main_manager()->tm.clear();
|
||||
}
|
||||
|
||||
void ThreadPool::run_on_thread(size_t threadId, std::function<void()> f) {
|
||||
void ThreadPool::run_on_thread(usize threadId, std::function<void()> f) {
|
||||
assert(threads.size() > threadId);
|
||||
threads[threadId]->run_custom_job(std::move(f));
|
||||
}
|
||||
|
||||
void ThreadPool::wait_on_thread(size_t threadId) {
|
||||
void ThreadPool::wait_on_thread(usize threadId) {
|
||||
assert(threads.size() > threadId);
|
||||
threads[threadId]->wait_for_search_finished();
|
||||
}
|
||||
|
||||
size_t ThreadPool::num_threads() const { return threads.size(); }
|
||||
usize ThreadPool::num_threads() const { return threads.size(); }
|
||||
|
||||
|
||||
// Wakes up main thread waiting in idle_loop() and returns immediately.
|
||||
@@ -290,8 +293,8 @@ void ThreadPool::start_thinking(const OptionsMap& options,
|
||||
|
||||
main_thread()->wait_for_search_finished();
|
||||
|
||||
main_manager()->stopOnPonderhit = stop = abortedSearch = false;
|
||||
main_manager()->ponder = limits.ponderMode;
|
||||
main_manager()->stopOnPonderhit = stop = false;
|
||||
main_manager()->ponder = limits.ponderMode;
|
||||
|
||||
increaseDepth = true;
|
||||
|
||||
@@ -330,8 +333,8 @@ void ThreadPool::start_thinking(const OptionsMap& options,
|
||||
th->worker->limits = limits;
|
||||
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->rootDepth = 0;
|
||||
th->worker->rootMoves = rootMoves;
|
||||
th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState);
|
||||
th->worker->rootState = setupStates->back();
|
||||
th->worker->tbConfig = tbConfig;
|
||||
@@ -347,63 +350,50 @@ void ThreadPool::start_thinking(const OptionsMap& options,
|
||||
Thread* ThreadPool::get_best_thread() const {
|
||||
|
||||
Thread* bestThread = threads.front().get();
|
||||
Value minScore = VALUE_NONE;
|
||||
Value minScore = VALUE_INFINITE;
|
||||
|
||||
std::unordered_map<Move, int64_t, Move::MoveHash> votes(
|
||||
std::unordered_map<Move, i64, Move::MoveHash> votes(
|
||||
2 * std::min(size(), bestThread->worker->rootMoves.size()));
|
||||
|
||||
// Find the minimum score of all threads
|
||||
for (auto&& th : threads)
|
||||
minScore = std::min(minScore, th->worker->rootMoves[0].score);
|
||||
|
||||
// Vote according to score and depth, and select the best thread
|
||||
auto thread_voting_value = [minScore](Thread* th) {
|
||||
return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth);
|
||||
};
|
||||
|
||||
// Vote according to score, and select the best thread
|
||||
for (auto&& th : threads)
|
||||
votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th.get());
|
||||
votes[th->worker->rootMoves[0].pv[0]] += th->worker->rootMoves[0].score - minScore + 14;
|
||||
|
||||
for (auto&& th : threads)
|
||||
{
|
||||
const auto bestThreadScore = bestThread->worker->rootMoves[0].score;
|
||||
const auto newThreadScore = th->worker->rootMoves[0].score;
|
||||
const auto& bestThreadMove = bestThread->worker->rootMoves[0];
|
||||
const auto& newThreadMove = th->worker->rootMoves[0];
|
||||
|
||||
const auto& bestThreadPV = bestThread->worker->rootMoves[0].pv;
|
||||
const auto& newThreadPV = th->worker->rootMoves[0].pv;
|
||||
const auto bestThreadMoveVote = votes[bestThreadMove.pv[0]];
|
||||
const auto newThreadMoveVote = votes[newThreadMove.pv[0]];
|
||||
|
||||
const auto bestThreadMoveVote = votes[bestThreadPV[0]];
|
||||
const auto newThreadMoveVote = votes[newThreadPV[0]];
|
||||
// Aborted (d1) searches may lead to inexact win (or loss) scores.
|
||||
const bool bestThreadDecisive = bestThreadMove.score != -VALUE_INFINITE
|
||||
&& is_decisive(bestThreadMove.score)
|
||||
&& !bestThreadMove.score_is_bound();
|
||||
const bool newThreadDecisive = newThreadMove.score != -VALUE_INFINITE
|
||||
&& is_decisive(newThreadMove.score)
|
||||
&& !newThreadMove.score_is_bound();
|
||||
|
||||
const bool bestThreadInProvenWin = is_win(bestThreadScore);
|
||||
const bool newThreadInProvenWin = is_win(newThreadScore);
|
||||
|
||||
const bool bestThreadInProvenLoss =
|
||||
bestThreadScore != -VALUE_INFINITE && is_loss(bestThreadScore);
|
||||
const bool newThreadInProvenLoss =
|
||||
newThreadScore != -VALUE_INFINITE && is_loss(newThreadScore);
|
||||
|
||||
// We make sure not to pick a thread with truncated principal variation
|
||||
const bool betterVotingValue =
|
||||
thread_voting_value(th.get()) * int(newThreadPV.size() > 2)
|
||||
> thread_voting_value(bestThread) * int(bestThreadPV.size() > 2);
|
||||
|
||||
if (bestThreadInProvenWin)
|
||||
if (bestThreadDecisive)
|
||||
{
|
||||
// Make sure we pick the shortest mate / TB conversion
|
||||
if (newThreadScore > bestThreadScore)
|
||||
// Make sure we pick the shortest mate / TB conversion.
|
||||
if (newThreadDecisive && std::abs(newThreadMove.score) > std::abs(bestThreadMove.score))
|
||||
{
|
||||
assert((is_win(bestThreadMove.score) && is_win(newThreadMove.score))
|
||||
|| (is_loss(bestThreadMove.score) && is_loss(newThreadMove.score)));
|
||||
|
||||
bestThread = th.get();
|
||||
}
|
||||
}
|
||||
else if (bestThreadInProvenLoss)
|
||||
{
|
||||
// Make sure we pick the shortest mated / TB conversion
|
||||
if (newThreadInProvenLoss && newThreadScore < bestThreadScore)
|
||||
bestThread = th.get();
|
||||
}
|
||||
else if (newThreadInProvenWin || newThreadInProvenLoss
|
||||
|| (!is_loss(newThreadScore)
|
||||
else if (newThreadDecisive
|
||||
|| (!is_loss(newThreadMove.score)
|
||||
&& (newThreadMoveVote > bestThreadMoveVote
|
||||
|| (newThreadMoveVote == bestThreadMoveVote && betterVotingValue))))
|
||||
|| (newThreadMoveVote == bestThreadMoveVote
|
||||
&& newThreadMove.pv.size() > bestThreadMove.pv.size()))))
|
||||
bestThread = th.get();
|
||||
}
|
||||
|
||||
@@ -429,8 +419,12 @@ void ThreadPool::wait_for_search_finished() const {
|
||||
th->wait_for_search_finished();
|
||||
}
|
||||
|
||||
std::vector<size_t> ThreadPool::get_bound_thread_count_by_numa_node() const {
|
||||
std::vector<size_t> counts;
|
||||
std::vector<usize> ThreadPool::get_bound_thread_to_numa_node() const {
|
||||
return boundThreadToNumaNode;
|
||||
}
|
||||
|
||||
std::vector<usize> ThreadPool::get_bound_thread_count_by_numa_node() const {
|
||||
std::vector<usize> counts;
|
||||
|
||||
if (!boundThreadToNumaNode.empty())
|
||||
{
|
||||
@@ -448,6 +442,13 @@ std::vector<size_t> ThreadPool::get_bound_thread_count_by_numa_node() const {
|
||||
return counts;
|
||||
}
|
||||
|
||||
usize ThreadPool::numa_nodes() const {
|
||||
std::unordered_set<usize> seen;
|
||||
for (NumaIndex n : boundThreadToNumaNode)
|
||||
seen.insert(n);
|
||||
return std::max(seen.size(), usize(1));
|
||||
}
|
||||
|
||||
void ThreadPool::ensure_network_replicated() {
|
||||
for (auto&& th : threads)
|
||||
th->ensure_network_replicated();
|
||||
|
||||
+24
-23
@@ -21,18 +21,17 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "memory.h"
|
||||
#include "misc.h"
|
||||
#include "numa.h"
|
||||
#include "position.h"
|
||||
#include "search.h"
|
||||
#include "thread_win32_osx.h"
|
||||
#include "thread_native.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
@@ -75,9 +74,9 @@ class Thread {
|
||||
public:
|
||||
Thread(Search::SharedState&,
|
||||
std::unique_ptr<Search::ISearchManager>,
|
||||
size_t,
|
||||
size_t,
|
||||
size_t,
|
||||
usize,
|
||||
usize,
|
||||
usize,
|
||||
OptionalThreadToNumaNodeBinder);
|
||||
virtual ~Thread();
|
||||
|
||||
@@ -93,8 +92,8 @@ class Thread {
|
||||
// require further work to make them properly generic while maintaining
|
||||
// appropriate specificity regarding search, from the point of view of an
|
||||
// outside user, so renaming of this function is left for whenever that happens.
|
||||
void wait_for_search_finished();
|
||||
size_t id() const { return idx; }
|
||||
void wait_for_search_finished();
|
||||
usize id() const { return idx; }
|
||||
|
||||
LargePagePtr<Search::Worker> worker;
|
||||
std::function<void()> jobFunc;
|
||||
@@ -102,7 +101,7 @@ class Thread {
|
||||
private:
|
||||
std::mutex mutex;
|
||||
std::condition_variable cv;
|
||||
size_t idx, idxInNuma, totalNuma, nthreads;
|
||||
usize idx, idxInNuma, totalNuma, nthreads;
|
||||
bool exit = false, searching = true; // Set before starting std::thread
|
||||
NativeThread stdThread;
|
||||
NumaReplicatedAccessToken numaAccessToken;
|
||||
@@ -132,28 +131,30 @@ class ThreadPool {
|
||||
ThreadPool& operator=(const ThreadPool&) = delete;
|
||||
ThreadPool& operator=(ThreadPool&&) = delete;
|
||||
|
||||
void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType);
|
||||
void run_on_thread(size_t threadId, std::function<void()> f);
|
||||
void wait_on_thread(size_t threadId);
|
||||
size_t num_threads() const;
|
||||
void clear();
|
||||
void set(const NumaConfig& numaConfig,
|
||||
Search::SharedState,
|
||||
const Search::SearchManager::UpdateContext&);
|
||||
void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType);
|
||||
void run_on_thread(usize threadId, std::function<void()> f);
|
||||
void wait_on_thread(usize threadId);
|
||||
usize num_threads() const;
|
||||
void clear();
|
||||
void set(const NumaConfig& numaConfig,
|
||||
Search::SharedState,
|
||||
const Search::SearchManager::UpdateContext&);
|
||||
|
||||
Search::SearchManager* main_manager();
|
||||
Thread* main_thread() const { return threads.front().get(); }
|
||||
uint64_t nodes_searched() const;
|
||||
uint64_t tb_hits() const;
|
||||
u64 nodes_searched() const;
|
||||
u64 tb_hits() const;
|
||||
Thread* get_best_thread() const;
|
||||
void start_searching();
|
||||
void wait_for_search_finished() const;
|
||||
|
||||
std::vector<size_t> get_bound_thread_count_by_numa_node() const;
|
||||
std::vector<usize> get_bound_thread_to_numa_node() const;
|
||||
std::vector<usize> get_bound_thread_count_by_numa_node() const;
|
||||
usize numa_nodes() const;
|
||||
|
||||
void ensure_network_replicated();
|
||||
|
||||
std::atomic_bool stop, abortedSearch, increaseDepth;
|
||||
std::atomic_bool stop, increaseDepth;
|
||||
|
||||
auto cbegin() const noexcept { return threads.cbegin(); }
|
||||
auto begin() noexcept { return threads.begin(); }
|
||||
@@ -167,9 +168,9 @@ class ThreadPool {
|
||||
std::vector<std::unique_ptr<Thread>> threads;
|
||||
std::vector<NumaIndex> boundThreadToNumaNode;
|
||||
|
||||
uint64_t accumulate(std::atomic<uint64_t> Search::Worker::* member) const {
|
||||
u64 accumulate(RelaxedAtomic<u64> Search::Worker::* member) const {
|
||||
|
||||
uint64_t sum = 0;
|
||||
u64 sum = 0;
|
||||
for (auto&& th : threads)
|
||||
sum += (th->worker.get()->*member).load(std::memory_order_relaxed);
|
||||
return sum;
|
||||
|
||||
@@ -16,10 +16,29 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef THREAD_WIN32_OSX_H_INCLUDED
|
||||
#define THREAD_WIN32_OSX_H_INCLUDED
|
||||
#ifndef THREAD_NATIVE_H_INCLUDED
|
||||
#define THREAD_NATIVE_H_INCLUDED
|
||||
|
||||
#include <thread>
|
||||
#ifdef _MSC_VER
|
||||
#include <thread>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include "misc.h"
|
||||
#endif
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
// MSVC-compatible toolchains use std::thread because they do not provide
|
||||
// pthreads by default. On all other platforms, pthreads is required and used.
|
||||
|
||||
using NativeThread = std::thread;
|
||||
|
||||
#else
|
||||
|
||||
// On OSX threads other than the main thread are created with a reduced stack
|
||||
// size of 512KB by default, this is too low for deep searches, which require
|
||||
@@ -27,17 +46,10 @@
|
||||
// The implementation calls pthread_create() with the stack size parameter
|
||||
// equal to the Linux 8MB default, on platforms that support it.
|
||||
|
||||
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
|
||||
|
||||
#include <pthread.h>
|
||||
#include <functional>
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
class NativeThread {
|
||||
pthread_t thread;
|
||||
|
||||
static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
||||
static constexpr usize TH_STACK_SIZE = 8 * 1024 * 1024;
|
||||
|
||||
public:
|
||||
template<class Function, class... Args>
|
||||
@@ -63,16 +75,8 @@ class NativeThread {
|
||||
void join() { pthread_join(thread, nullptr); }
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#else // Default case: use STL classes
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
using NativeThread = std::thread;
|
||||
#endif // _MSC_VER
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
#endif
|
||||
|
||||
#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
|
||||
#endif // #ifndef THREAD_NATIVE_H_INCLUDED
|
||||
+20
-23
@@ -21,7 +21,6 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
#include "search.h"
|
||||
#include "ucioption.h"
|
||||
@@ -35,9 +34,9 @@ void TimeManagement::clear() {
|
||||
availableNodes = -1; // When in 'nodes as time' mode
|
||||
}
|
||||
|
||||
void TimeManagement::advance_nodes_time(std::int64_t nodes) {
|
||||
void TimeManagement::advance_nodes_time(i64 nodes) {
|
||||
assert(useNodesTime);
|
||||
availableNodes = std::max(int64_t(0), availableNodes - nodes);
|
||||
availableNodes = std::max(i64(0), availableNodes - nodes);
|
||||
}
|
||||
|
||||
// Called at the beginning of the search and calculates
|
||||
@@ -81,23 +80,21 @@ void TimeManagement::init(Search::LimitsType& limits,
|
||||
moveOverhead *= npmsec;
|
||||
}
|
||||
|
||||
// These numbers are used where multiplications, divisions or comparisons
|
||||
// with constants are involved.
|
||||
const int64_t scaleFactor = useNodesTime ? npmsec : 1;
|
||||
// These numbers are used where multiplications, divisions,
|
||||
// or comparisons with constants are involved.
|
||||
const i64 scaleFactor = useNodesTime ? npmsec : 1;
|
||||
const TimePoint scaledTime = limits.time[us] / scaleFactor;
|
||||
|
||||
// Maximum move horizon
|
||||
int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051;
|
||||
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
|
||||
|
||||
// If less than one second, gradually reduce mtg
|
||||
if (scaledTime < 1000)
|
||||
centiMTG = int(scaledTime * 5.051);
|
||||
mtg = int(scaledTime * 0.05);
|
||||
|
||||
// Make sure timeLeft is > 0 since we may use it as a divisor
|
||||
TimePoint timeLeft =
|
||||
std::max(TimePoint(1),
|
||||
limits.time[us]
|
||||
+ (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100);
|
||||
TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1)
|
||||
- moveOverhead * (2 + mtg));
|
||||
|
||||
// x basetime (+ z increment)
|
||||
// If there is a healthy increment, timeLeft can exceed the actual available
|
||||
@@ -106,32 +103,32 @@ void TimeManagement::init(Search::LimitsType& limits,
|
||||
{
|
||||
// Extra time according to timeLeft
|
||||
if (originalTimeAdjust < 0)
|
||||
originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354;
|
||||
originalTimeAdjust = 0.3272 * std::log10(timeLeft) - 0.4141;
|
||||
|
||||
// Calculate time constants based on current time left.
|
||||
double logTimeInSec = std::log10(scaledTime / 1000.0);
|
||||
double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017);
|
||||
double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761);
|
||||
double optConstant = std::min(0.0029869 + 0.00033554 * logTimeInSec, 0.004905);
|
||||
double maxConstant = std::max(3.3744 + 3.0608 * logTimeInSec, 3.1441);
|
||||
|
||||
optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant,
|
||||
0.213035 * limits.time[us] / timeLeft)
|
||||
optScale = std::min(0.012112 + std::pow(ply + 3.22713, 0.46866) * optConstant,
|
||||
0.19404 * limits.time[us] / timeLeft)
|
||||
* originalTimeAdjust;
|
||||
|
||||
maxScale = std::min(6.67704, maxConstant + ply / 11.9847);
|
||||
maxScale = std::min(6.873, maxConstant + ply / 12.352);
|
||||
}
|
||||
|
||||
// x moves in y seconds (+ z increment)
|
||||
else
|
||||
{
|
||||
optScale =
|
||||
std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * limits.time[us] / timeLeft);
|
||||
maxScale = 1.3 + 0.11 * (centiMTG / 100.0);
|
||||
optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / timeLeft);
|
||||
maxScale = 1.3 + 0.11 * mtg;
|
||||
}
|
||||
|
||||
// Limit the maximum possible time for this move
|
||||
optimumTime = TimePoint(optScale * timeLeft);
|
||||
optimumTime = TimePoint(std::max(1.0, optScale * timeLeft));
|
||||
maximumTime =
|
||||
TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
|
||||
TimePoint(std::max(double(optimumTime), std::min(0.8097 * limits.time[us] - moveOverhead,
|
||||
maxScale * optimumTime)));
|
||||
|
||||
if (options["Ponder"])
|
||||
optimumTime += optimumTime / 4;
|
||||
|
||||
+4
-5
@@ -19,14 +19,13 @@
|
||||
#ifndef TIMEMAN_H_INCLUDED
|
||||
#define TIMEMAN_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
class OptionsMap;
|
||||
enum Color : uint8_t;
|
||||
enum Color : u8;
|
||||
|
||||
namespace Search {
|
||||
struct LimitsType;
|
||||
@@ -51,15 +50,15 @@ class TimeManagement {
|
||||
TimePoint elapsed_time() const { return now() - startTime; };
|
||||
|
||||
void clear();
|
||||
void advance_nodes_time(std::int64_t nodes);
|
||||
void advance_nodes_time(i64 nodes);
|
||||
|
||||
private:
|
||||
TimePoint startTime;
|
||||
TimePoint optimumTime;
|
||||
TimePoint maximumTime;
|
||||
|
||||
std::int64_t availableNodes = -1; // When in 'nodes as time' mode
|
||||
bool useNodesTime = false; // True if we are in 'nodes as time' mode
|
||||
i64 availableNodes = -1; // When in 'nodes as time' mode
|
||||
bool useNodesTime = false; // True if we are in 'nodes as time' mode
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
+115
-85
@@ -18,11 +18,13 @@
|
||||
|
||||
#include "tt.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
#include "memory.h"
|
||||
#include "misc.h"
|
||||
@@ -32,94 +34,101 @@
|
||||
namespace Stockfish {
|
||||
|
||||
|
||||
// TTEntry struct is the 10 bytes transposition table entry, defined as below:
|
||||
// TTEntry struct is the 10 bytes transposition table entry, defined as:
|
||||
//
|
||||
// key 16 bit
|
||||
// depth 8 bit
|
||||
// generation 5 bit
|
||||
// pv node 1 bit
|
||||
// bound type 2 bit
|
||||
// generation 5 bit
|
||||
// move 16 bit
|
||||
// value 16 bit
|
||||
// evaluation 16 bit
|
||||
//
|
||||
// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially.
|
||||
// Equally, the store order in save() matches this order.
|
||||
//
|
||||
// We use `bool(depth8)` as the cheap internal occupancy check, corresponding to `depth == DEPTH_NONE`
|
||||
// externally, so we offset the internal depth by DEPTH_NONE.
|
||||
//
|
||||
// Pv, bound and generation are packed in a single byte.
|
||||
static constexpr u8 GENERATION_BITS = 5;
|
||||
static constexpr u8 GENERATION_MASK = (1 << GENERATION_BITS) - 1;
|
||||
static constexpr u8 BOUND_SHIFT = GENERATION_BITS;
|
||||
static constexpr u8 BOUND_MASK = 0b11 << BOUND_SHIFT;
|
||||
static constexpr u8 PV_SHIFT = BOUND_SHIFT + 2;
|
||||
static constexpr u8 PV_MASK = 1 << PV_SHIFT;
|
||||
|
||||
struct TTEntry {
|
||||
|
||||
// Convert internal bitfields to external types
|
||||
TTData read() const {
|
||||
return TTData{Move(move16), Value(value16),
|
||||
Value(eval16), Depth(depth8 + DEPTH_ENTRY_OFFSET),
|
||||
Bound(genBound8 & 0x3), bool(genBound8 & 0x4)};
|
||||
return TTData{Move(move16),
|
||||
Value(value16),
|
||||
Value(eval16),
|
||||
Depth(DEPTH_NONE + depth8),
|
||||
Bound((genBound8 & BOUND_MASK) >> BOUND_SHIFT),
|
||||
bool(genBound8 & PV_MASK)};
|
||||
}
|
||||
|
||||
bool is_occupied() const;
|
||||
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
|
||||
// The returned age is a multiple of TranspositionTable::GENERATION_DELTA
|
||||
uint8_t relative_age(const uint8_t generation8) const;
|
||||
bool is_occupied() const { return bool(depth8); };
|
||||
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, u8 curr_generation);
|
||||
u8 relative_age(const u8 curr_generation) const;
|
||||
|
||||
private:
|
||||
friend class TranspositionTable;
|
||||
friend struct TTWriter;
|
||||
|
||||
uint16_t key16;
|
||||
uint8_t depth8;
|
||||
uint8_t genBound8;
|
||||
Move move16;
|
||||
int16_t value16;
|
||||
int16_t eval16;
|
||||
RelaxedAtomic<u16> key16;
|
||||
RelaxedAtomic<u8> depth8;
|
||||
RelaxedAtomic<u8> genBound8;
|
||||
RelaxedAtomic<Move> move16;
|
||||
RelaxedAtomic<i16> value16;
|
||||
RelaxedAtomic<i16> eval16;
|
||||
};
|
||||
|
||||
// `genBound8` is where most of the details are. We use the following constants to manipulate 5 leading generation bits
|
||||
// and 3 trailing miscellaneous bits.
|
||||
|
||||
// These bits are reserved for other things.
|
||||
static constexpr unsigned GENERATION_BITS = 3;
|
||||
// increment for generation field
|
||||
static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS);
|
||||
// cycle length
|
||||
static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA;
|
||||
// mask to pull out generation number
|
||||
static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF;
|
||||
|
||||
// DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but
|
||||
// 2) we need to store negative depths for QS. (`depth8` is the only field with "spare bits":
|
||||
// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted in `save`.)
|
||||
bool TTEntry::is_occupied() const { return bool(depth8); }
|
||||
|
||||
// Populates the TTEntry with a new node's data, possibly
|
||||
// overwriting an old position. The update is not atomic and can be racy.
|
||||
// overwriting an old position. The update is non-atomic and can be racy.
|
||||
void TTEntry::save(
|
||||
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {
|
||||
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, u8 curr_generation) {
|
||||
|
||||
// Preserve the old ttmove if we don't have a new one
|
||||
if (m || uint16_t(k) != key16)
|
||||
if (m || u16(k) != key16)
|
||||
move16 = m;
|
||||
|
||||
// Overwrite less valuable entries (cheapest checks first)
|
||||
if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_ENTRY_OFFSET + 2 * pv > depth8 - 4
|
||||
|| relative_age(generation8))
|
||||
if (b == BOUND_EXACT || u16(k) != key16 || d - DEPTH_NONE + 2 * pv > depth8 - 4
|
||||
|| relative_age(curr_generation))
|
||||
{
|
||||
assert(d > DEPTH_ENTRY_OFFSET);
|
||||
assert(d < 256 + DEPTH_ENTRY_OFFSET);
|
||||
assert(d > DEPTH_NONE);
|
||||
assert(d - DEPTH_NONE < 256);
|
||||
assert(curr_generation <= GENERATION_MASK); // TT::new_search() plays nice
|
||||
|
||||
key16 = uint16_t(k);
|
||||
depth8 = uint8_t(d - DEPTH_ENTRY_OFFSET);
|
||||
genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b);
|
||||
value16 = int16_t(v);
|
||||
eval16 = int16_t(ev);
|
||||
key16 = u16(k);
|
||||
depth8 = u8(d - DEPTH_NONE);
|
||||
genBound8 = u8(curr_generation | b << BOUND_SHIFT | u8(pv) << PV_SHIFT);
|
||||
value16 = i16(v);
|
||||
eval16 = i16(ev);
|
||||
}
|
||||
// Secondary aging. Important for elementary mate finding.
|
||||
// (*Scaler) Secondary aging on entries relevant to singular extensions
|
||||
// generally scales poorly and requires VVLTC verification.
|
||||
else if (depth8 + DEPTH_NONE >= 5
|
||||
&& Bound((genBound8 & BOUND_MASK) >> BOUND_SHIFT) != BOUND_EXACT)
|
||||
{
|
||||
auto v16 = value16;
|
||||
if (std::abs(v16) < VALUE_INFINITE && is_decisive(v16))
|
||||
depth8 = std::max(int(depth8) - 1,
|
||||
0); // guard against racy underflows, default to "unoccupied"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint8_t TTEntry::relative_age(const uint8_t generation8) const {
|
||||
// Due to our packed storage format for generation and its cyclic
|
||||
// nature we add GENERATION_CYCLE (256 is the modulus, plus what
|
||||
// is needed to keep the unrelated lowest n bits from affecting
|
||||
// the result) to calculate the entry age correctly even after
|
||||
// generation8 overflows into the next cycle.
|
||||
return (GENERATION_CYCLE + generation8 - genBound8) & GENERATION_MASK;
|
||||
u8 TTEntry::relative_age(const u8 curr_generation) const {
|
||||
// Returns this entry's age. We count generations like clocks count hours,
|
||||
// i.e. we require 0 - 1 == 31. Unsigned subtraction guarantees the required
|
||||
// borrowing regardless of the upper pv/bound bits.
|
||||
return (curr_generation - genBound8) & GENERATION_MASK;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,8 +137,13 @@ TTWriter::TTWriter(TTEntry* tte) :
|
||||
entry(tte) {}
|
||||
|
||||
void TTWriter::write(
|
||||
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {
|
||||
entry->save(k, v, pv, b, d, m, ev, generation8);
|
||||
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, u8 curr_generation) {
|
||||
entry->save(k, v, pv, b, d, m, ev, curr_generation);
|
||||
}
|
||||
|
||||
void TTWriter::penalize(int penalty) {
|
||||
// guard against racy underflows, default to "unoccupied"
|
||||
entry->depth8 = std::max(int(entry->depth8) - penalty, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -150,12 +164,17 @@ static_assert(sizeof(Cluster) == 32, "Suboptimal Cluster size");
|
||||
// Sets the size of the transposition table,
|
||||
// measured in megabytes. Transposition table consists
|
||||
// of clusters and each cluster consists of ClusterSize number of TTEntry.
|
||||
void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) {
|
||||
void TranspositionTable::resize(usize mbSize, ThreadPool& threads) {
|
||||
aligned_large_pages_free(table);
|
||||
|
||||
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
||||
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
||||
usize ttBytes = clusterCount * sizeof(Cluster);
|
||||
|
||||
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
|
||||
// Request 1GB pages if we'd get at least eight per NUMA node, to avoid
|
||||
// memory oversubscription
|
||||
bool hugePageHint = ttBytes >= threads.numa_nodes() * HugePageSize * 8;
|
||||
|
||||
table = static_cast<Cluster*>(aligned_large_pages_alloc_with_hint(ttBytes, hugePageHint));
|
||||
|
||||
if (!table)
|
||||
{
|
||||
@@ -170,60 +189,72 @@ void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) {
|
||||
// Initializes the entire transposition table to zero,
|
||||
// in a multi-threaded way.
|
||||
void TranspositionTable::clear(ThreadPool& threads) {
|
||||
generation8 = 0;
|
||||
const size_t threadCount = threads.num_threads();
|
||||
generation8 = 0;
|
||||
const usize threadCount = threads.num_threads();
|
||||
|
||||
for (size_t i = 0; i < threadCount; ++i)
|
||||
std::vector<usize> threadToNuma = threads.get_bound_thread_to_numa_node();
|
||||
|
||||
std::vector<usize> order(threadCount);
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
|
||||
// To promote good NUMA distribution (esp. with huge pages), we permute threads so that
|
||||
// all threads in a NUMA node clear a contiguous region of the TT.
|
||||
if (threadToNuma.size() == threadCount)
|
||||
{
|
||||
threads.run_on_thread(i, [this, i, threadCount]() {
|
||||
// Each thread will zero its part of the hash table
|
||||
const size_t stride = clusterCount / threadCount;
|
||||
const size_t start = stride * i;
|
||||
const size_t len = i + 1 != threadCount ? stride : clusterCount - start;
|
||||
|
||||
std::memset(&table[start], 0, len * sizeof(Cluster));
|
||||
std::stable_sort(order.begin(), order.end(), [&threadToNuma](usize t1, usize t2) {
|
||||
return threadToNuma.at(t1) < threadToNuma.at(t2);
|
||||
});
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < threadCount; ++i)
|
||||
for (usize i = 0; i < threadCount; ++i)
|
||||
{
|
||||
threads.run_on_thread(order[i], [this, i, threadCount]() {
|
||||
// Each thread will zero its part of the hash table
|
||||
const usize stride = clusterCount / threadCount;
|
||||
const usize start = stride * i;
|
||||
const usize len = i + 1 != threadCount ? stride : clusterCount - start;
|
||||
|
||||
std::memset(static_cast<void*>(&table[start]), 0, len * sizeof(Cluster));
|
||||
});
|
||||
}
|
||||
|
||||
for (usize i = 0; i < threadCount; ++i)
|
||||
threads.wait_on_thread(i);
|
||||
}
|
||||
|
||||
|
||||
// Returns an approximation of the hashtable
|
||||
// occupation during a search. The hash is x permill full, as per UCI protocol.
|
||||
// Only counts entries which match the current generation.
|
||||
// Only counts entries which are younger than maxAge.
|
||||
int TranspositionTable::hashfull(int maxAge) const {
|
||||
int maxAgeInternal = maxAge << GENERATION_BITS;
|
||||
int cnt = 0;
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < 1000; ++i)
|
||||
for (int j = 0; j < ClusterSize; ++j)
|
||||
cnt += table[i].entry[j].is_occupied()
|
||||
&& table[i].entry[j].relative_age(generation8) <= maxAgeInternal;
|
||||
&& table[i].entry[j].relative_age(generation8) <= maxAge;
|
||||
|
||||
return cnt / ClusterSize;
|
||||
}
|
||||
|
||||
|
||||
void TranspositionTable::new_search() {
|
||||
// increment by delta to keep lower bits as is
|
||||
generation8 += GENERATION_DELTA;
|
||||
++generation8;
|
||||
// Don't overflow into the other bits of TTEntry::genBound8
|
||||
generation8 &= GENERATION_MASK;
|
||||
}
|
||||
|
||||
|
||||
uint8_t TranspositionTable::generation() const { return generation8; }
|
||||
u8 TranspositionTable::generation() const { return generation8; }
|
||||
|
||||
|
||||
// Looks up the current position in the transposition
|
||||
// table. It returns true if the position is found.
|
||||
// Looks up the current position in the transposition table.
|
||||
// It returns true if the key is found (which may be a collision), and has non-null data.
|
||||
// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry
|
||||
// to be replaced later. The replace value of an entry is calculated as its depth
|
||||
// minus 8 times its relative age. TTEntry t1 is considered more valuable than
|
||||
// TTEntry t2 if its replace value is greater than that of t2.
|
||||
// to be replaced later. The value of an entry is its depth minus 8 times its relative age.
|
||||
std::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) const {
|
||||
|
||||
TTEntry* const tte = first_entry(key);
|
||||
const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster
|
||||
const u16 key16 = u16(key); // Use the low 16 bits as key inside the cluster
|
||||
|
||||
for (int i = 0; i < ClusterSize; ++i)
|
||||
if (tte[i].key16 == key16)
|
||||
@@ -234,12 +265,11 @@ 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)
|
||||
> tte[i].depth8 - tte[i].relative_age(generation8))
|
||||
if (replace->depth8 - 8 * replace->relative_age(generation8)
|
||||
> tte[i].depth8 - 8 * tte[i].relative_age(generation8))
|
||||
replace = &tte[i];
|
||||
|
||||
return {false,
|
||||
TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_ENTRY_OFFSET, BOUND_NONE, false},
|
||||
return {false, TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_NONE, BOUND_NONE, false},
|
||||
TTWriter(replace)};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,9 @@
|
||||
#ifndef TT_H_INCLUDED
|
||||
#define TT_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
|
||||
#include "misc.h"
|
||||
#include "memory.h"
|
||||
#include "types.h"
|
||||
|
||||
@@ -37,14 +36,11 @@ struct Cluster;
|
||||
// thus elo. As a hash table, collisions are possible and may cause chess playing issues (bizarre blunders, faulty mate
|
||||
// reports, etc). Fixing these also loses elo; however such risk decreases quickly with larger TT size.
|
||||
//
|
||||
// `probe` is the primary method: given a board position, we lookup its entry in the table, and return a tuple of:
|
||||
// 1) whether the entry already has this position
|
||||
// 2) a copy of the prior data (if any) (may be inconsistent due to read races)
|
||||
// 3) a writer object to this entry
|
||||
// The copied data and the writer are separated to maintain clear boundaries between local vs global objects.
|
||||
// We clearly separate TTData, a local copy of an entry, from TTWriter, which writes to the global table.
|
||||
|
||||
|
||||
// A copy of the data already in the entry (possibly collided). `probe` may be racy, resulting in inconsistent data.
|
||||
// A copy of the data already in an entry (possibly collided). Probes and reads are racy and non-atomic,
|
||||
// possibly resulting in inconsistent data.
|
||||
struct TTData {
|
||||
Move move;
|
||||
Value value, eval;
|
||||
@@ -66,10 +62,12 @@ struct TTData {
|
||||
};
|
||||
|
||||
|
||||
// This is used to make racy writes to the global TT.
|
||||
// This is used to make racy, non-atomic writes to the global TT. Writes are not "guaranteed":
|
||||
// for chess reasons, we may decide the new data is less important than the old.
|
||||
struct TTWriter {
|
||||
public:
|
||||
void write(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
|
||||
void write(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, u8 generation8);
|
||||
void penalize(int penalty); // decrement stored depth by the penalty
|
||||
|
||||
private:
|
||||
friend class TranspositionTable;
|
||||
@@ -83,26 +81,30 @@ class TranspositionTable {
|
||||
public:
|
||||
~TranspositionTable() { aligned_large_pages_free(table); }
|
||||
|
||||
void resize(size_t mbSize, ThreadPool& threads); // Set TT size
|
||||
void clear(ThreadPool& threads); // Re-initialize memory, multithreaded
|
||||
int hashfull(int maxAge = 0)
|
||||
const; // Approximate what fraction of entries (permille) have been written to during this root search
|
||||
void resize(usize mbSize, ThreadPool& threads); // Set TT size in MiB
|
||||
void clear(ThreadPool& threads); // Re-initialize memory, multithreaded
|
||||
|
||||
void
|
||||
new_search(); // This must be called at the beginning of each root search to track entry aging
|
||||
uint8_t generation() const; // The current age, used when writing new data to the TT
|
||||
std::tuple<bool, TTData, TTWriter>
|
||||
probe(const Key key) const; // The main method, whose retvals separate local vs global objects
|
||||
TTEntry* first_entry(const Key key)
|
||||
const; // This is the hash function; its only external use is memory prefetching.
|
||||
u8 generation() const; // The current age, used when writing new data to the TT
|
||||
// Approximate what fraction of entries (permille) have been written to during this root search
|
||||
int hashfull(int maxAge = 0) const;
|
||||
|
||||
// `probe` is the primary method: given a board position, we lookup its entry in the table, and return a tuple of:
|
||||
// 1) whether the entry already had data on this position
|
||||
// 2) a copy of the prior data, if any (may be self-inconsistent due to read races)
|
||||
// 3) a writer object to the entry
|
||||
std::tuple<bool, TTData, TTWriter> probe(const Key key) const;
|
||||
// The hash function; its only external use is memory prefetching
|
||||
TTEntry* first_entry(const Key key) const;
|
||||
|
||||
private:
|
||||
friend struct TTEntry;
|
||||
|
||||
size_t clusterCount;
|
||||
usize clusterCount;
|
||||
Cluster* table = nullptr;
|
||||
|
||||
uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8
|
||||
u8 generation8 = 0;
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
+4
-3
@@ -19,13 +19,14 @@
|
||||
#ifndef TUNE_H_INCLUDED
|
||||
#define TUNE_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits> // IWYU pragma: keep
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
namespace Stockfish {
|
||||
|
||||
class OptionsMap;
|
||||
@@ -132,9 +133,9 @@ class Tune {
|
||||
}
|
||||
|
||||
// Template specialization for arrays: recursively handle multi-dimensional arrays
|
||||
template<typename T, size_t N, typename... Args>
|
||||
template<typename T, usize N, typename... Args>
|
||||
int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) {
|
||||
for (size_t i = 0; i < N; i++)
|
||||
for (usize i = 0; i < N; i++)
|
||||
add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]);
|
||||
return add(range, std::move(names), args...);
|
||||
}
|
||||
|
||||
+60
-50
@@ -39,7 +39,6 @@
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include "misc.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
@@ -58,15 +57,15 @@
|
||||
// _WIN32 Building on Windows (any)
|
||||
// _WIN64 Building on Windows 64 bit
|
||||
|
||||
// Enforce minimum GCC version
|
||||
// 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"
|
||||
#if defined(__clang__) && (__clang_major__ < 11)
|
||||
#error "Stockfish requires Clang 11.0 or later for correct compilation"
|
||||
#endif
|
||||
|
||||
#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
|
||||
@@ -76,7 +75,7 @@
|
||||
#define IS_64BIT
|
||||
#endif
|
||||
|
||||
#if defined(USE_POPCNT) && defined(_MSC_VER)
|
||||
#if defined(_MSC_VER)
|
||||
#include <nmmintrin.h> // Microsoft header for _mm_popcnt_u64()
|
||||
#endif
|
||||
|
||||
@@ -87,8 +86,7 @@
|
||||
#if defined(USE_PEXT)
|
||||
#include <immintrin.h> // Header for _pext_u64() intrinsic
|
||||
#define pext(b, m) _pext_u64(b, m)
|
||||
#else
|
||||
#define pext(b, m) 0
|
||||
#define pdep(b, m) _pdep_u64(b, m)
|
||||
#endif
|
||||
|
||||
namespace Stockfish {
|
||||
@@ -111,19 +109,19 @@ constexpr bool Is64Bit = true;
|
||||
constexpr bool Is64Bit = false;
|
||||
#endif
|
||||
|
||||
using Key = uint64_t;
|
||||
using Bitboard = uint64_t;
|
||||
using Key = u64;
|
||||
using Bitboard = u64;
|
||||
|
||||
constexpr int MAX_MOVES = 256;
|
||||
constexpr int MAX_PLY = 246;
|
||||
|
||||
enum Color : uint8_t {
|
||||
enum Color : u8 {
|
||||
WHITE,
|
||||
BLACK,
|
||||
COLOR_NB = 2
|
||||
};
|
||||
|
||||
enum CastlingRights : uint8_t {
|
||||
enum CastlingRights : u8 {
|
||||
NO_CASTLING,
|
||||
WHITE_OO,
|
||||
WHITE_OOO = WHITE_OO << 1,
|
||||
@@ -139,7 +137,7 @@ enum CastlingRights : uint8_t {
|
||||
CASTLING_RIGHT_NB = 16
|
||||
};
|
||||
|
||||
enum Bound : uint8_t {
|
||||
enum Bound : u8 {
|
||||
BOUND_NONE,
|
||||
BOUND_UPPER,
|
||||
BOUND_LOWER,
|
||||
@@ -179,6 +177,22 @@ constexpr bool is_loss(Value value) {
|
||||
|
||||
constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); }
|
||||
|
||||
constexpr bool is_mate(Value value) {
|
||||
assert(is_valid(value));
|
||||
return value >= VALUE_MATE_IN_MAX_PLY;
|
||||
}
|
||||
|
||||
constexpr bool is_mated(Value value) {
|
||||
assert(is_valid(value));
|
||||
return value <= VALUE_MATED_IN_MAX_PLY;
|
||||
}
|
||||
|
||||
constexpr bool is_mate_or_mated(Value value) { return is_mate(value) || is_mated(value); }
|
||||
|
||||
constexpr Value mate_in(int ply) { return VALUE_MATE - ply; }
|
||||
|
||||
constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; }
|
||||
|
||||
// In the code, we make the assumption that these values
|
||||
// are such that non_pawn_material() can be used to uniquely
|
||||
// identify the material on the board.
|
||||
@@ -190,13 +204,13 @@ constexpr Value QueenValue = 2538;
|
||||
|
||||
|
||||
// clang-format off
|
||||
enum PieceType : std::uint8_t {
|
||||
enum PieceType : u8 {
|
||||
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
|
||||
ALL_PIECES = 0,
|
||||
PIECE_TYPE_NB = 8
|
||||
};
|
||||
|
||||
enum Piece : std::uint8_t {
|
||||
enum Piece : u8 {
|
||||
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,
|
||||
@@ -220,14 +234,14 @@ using Depth = int;
|
||||
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;
|
||||
// compare lower than any quiescence or regular depth. DEPTH_NONE is used
|
||||
// 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_NONE = -3;
|
||||
|
||||
// clang-format off
|
||||
enum Square : uint8_t {
|
||||
enum Square : u8 {
|
||||
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,
|
||||
@@ -243,7 +257,7 @@ enum Square : uint8_t {
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
enum Direction : int8_t {
|
||||
enum Direction : i8 {
|
||||
NORTH = 8,
|
||||
EAST = 1,
|
||||
SOUTH = -NORTH,
|
||||
@@ -255,7 +269,7 @@ enum Direction : int8_t {
|
||||
NORTH_WEST = NORTH + WEST
|
||||
};
|
||||
|
||||
enum File : uint8_t {
|
||||
enum File : u8 {
|
||||
FILE_A,
|
||||
FILE_B,
|
||||
FILE_C,
|
||||
@@ -267,7 +281,7 @@ enum File : uint8_t {
|
||||
FILE_NB
|
||||
};
|
||||
|
||||
enum Rank : uint8_t {
|
||||
enum Rank : u8 {
|
||||
RANK_1,
|
||||
RANK_2,
|
||||
RANK_3,
|
||||
@@ -299,10 +313,10 @@ struct DirtyThreat {
|
||||
static constexpr int PcOffset = 20;
|
||||
|
||||
DirtyThreat() { /* don't initialize data */ }
|
||||
DirtyThreat(uint32_t raw) :
|
||||
DirtyThreat(u32 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)
|
||||
data = (u32(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset)
|
||||
| (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset);
|
||||
}
|
||||
|
||||
@@ -311,10 +325,10 @@ struct DirtyThreat {
|
||||
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; }
|
||||
u32 raw() const { return data; }
|
||||
|
||||
private:
|
||||
uint32_t data;
|
||||
u32 data;
|
||||
};
|
||||
|
||||
// A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks.
|
||||
@@ -328,10 +342,17 @@ using DirtyThreatList = ValueList<DirtyThreat, 96>;
|
||||
|
||||
struct DirtyThreats {
|
||||
DirtyThreatList list;
|
||||
Color us;
|
||||
Square prevKsq, ksq;
|
||||
};
|
||||
|
||||
Bitboard threatenedSqs, threateningSqs;
|
||||
struct DirtyPawnPairs {
|
||||
Bitboard before[COLOR_NB];
|
||||
Bitboard after[COLOR_NB];
|
||||
};
|
||||
|
||||
struct Dirties {
|
||||
DirtyPiece dirtyPiece;
|
||||
DirtyThreats dirtyThreats;
|
||||
DirtyPawnPairs dirtyPawnPairs;
|
||||
};
|
||||
|
||||
#define ENABLE_INCR_OPERATORS_ON(T) \
|
||||
@@ -370,10 +391,6 @@ constexpr CastlingRights operator&(Color c, CastlingRights cr) {
|
||||
return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr);
|
||||
}
|
||||
|
||||
constexpr Value mate_in(int ply) { return VALUE_MATE - ply; }
|
||||
|
||||
constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; }
|
||||
|
||||
constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); }
|
||||
|
||||
constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); }
|
||||
@@ -401,12 +418,10 @@ constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; }
|
||||
|
||||
|
||||
// Based on a congruential pseudo-random number generator
|
||||
constexpr Key make_key(uint64_t seed) {
|
||||
return seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||
}
|
||||
constexpr Key make_key(u64 seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; }
|
||||
|
||||
|
||||
enum MoveType : uint16_t {
|
||||
enum MoveType : u16 {
|
||||
NORMAL,
|
||||
PROMOTION = 1 << 14,
|
||||
EN_PASSANT = 2 << 14,
|
||||
@@ -428,7 +443,7 @@ enum MoveType : uint16_t {
|
||||
class Move {
|
||||
public:
|
||||
Move() = default;
|
||||
constexpr explicit Move(std::uint16_t d) :
|
||||
constexpr explicit Move(u16 d) :
|
||||
data(d) {}
|
||||
|
||||
constexpr Move(Square from, Square to) :
|
||||
@@ -463,24 +478,19 @@ class Move {
|
||||
|
||||
constexpr explicit operator bool() const { return data != 0; }
|
||||
|
||||
constexpr std::uint16_t raw() const { return data; }
|
||||
constexpr u16 raw() const { return data; }
|
||||
|
||||
struct MoveHash {
|
||||
std::size_t operator()(const Move& m) const { return make_key(m.data); }
|
||||
usize operator()(const Move& m) const { return make_key(m.data); }
|
||||
};
|
||||
|
||||
static constexpr int FromSqShift = 6;
|
||||
static constexpr int ToSqShift = 0;
|
||||
|
||||
protected:
|
||||
std::uint16_t data;
|
||||
u16 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
|
||||
|
||||
+61
-31
@@ -21,12 +21,14 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <filesystem>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "benchmark.h"
|
||||
@@ -43,7 +45,6 @@ namespace Stockfish {
|
||||
|
||||
constexpr auto BenchmarkCommand = "speedtest";
|
||||
|
||||
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
template<typename... Ts>
|
||||
struct overload: Ts... {
|
||||
using Ts::operator()...;
|
||||
@@ -64,9 +65,9 @@ void UCIEngine::print_info_string(std::string_view str) {
|
||||
sync_cout_end();
|
||||
}
|
||||
|
||||
UCIEngine::UCIEngine(int argc, char** argv) :
|
||||
engine(argv[0]),
|
||||
cli(argc, argv) {
|
||||
UCIEngine::UCIEngine(CommandLine cli_) :
|
||||
engine(cli_.argc > 0 ? std::optional{path_from_utf8(cli_.argv[0])} : std::nullopt),
|
||||
cli(std::move(cli_)) {
|
||||
|
||||
engine.get_options().add_info_listener([](const std::optional<std::string>& str) {
|
||||
if (str.has_value())
|
||||
@@ -82,10 +83,11 @@ void UCIEngine::init_search_update_listeners() {
|
||||
engine.set_on_update_full(
|
||||
[this](const auto& i) { on_update_full(i, engine.get_options()["UCI_ShowWDL"]); });
|
||||
engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); });
|
||||
engine.set_on_verify_networks([](const auto& s) { print_info_string(s); });
|
||||
engine.set_on_verify_network([](const auto& s) { print_info_string(s); });
|
||||
}
|
||||
|
||||
void UCIEngine::loop() {
|
||||
set_console_utf8();
|
||||
std::string token, cmd;
|
||||
|
||||
for (int i = 1; i < cli.argc; ++i)
|
||||
@@ -97,10 +99,11 @@ void UCIEngine::loop() {
|
||||
&& !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
|
||||
cmd = "quit";
|
||||
|
||||
currentCmd = cmd;
|
||||
std::istringstream is(cmd);
|
||||
|
||||
token.clear(); // Avoid a stale if getline() returns nothing or a blank line
|
||||
is >> std::skipws >> token;
|
||||
is >> token;
|
||||
|
||||
if (token == "quit" || token == "stop")
|
||||
engine.stop();
|
||||
@@ -137,9 +140,13 @@ void UCIEngine::loop() {
|
||||
sync_cout << "readyok" << sync_endl;
|
||||
|
||||
// Add custom non-UCI commands, mainly for debugging purposes.
|
||||
// These commands must not be used during a search!
|
||||
else if (token == "flip")
|
||||
engine.flip();
|
||||
{
|
||||
if (auto err = engine.flip())
|
||||
{
|
||||
terminate_on_critical_error(err->what());
|
||||
}
|
||||
}
|
||||
else if (token == "bench")
|
||||
bench(is);
|
||||
else if (token == BenchmarkCommand)
|
||||
@@ -152,15 +159,13 @@ void UCIEngine::loop() {
|
||||
sync_cout << compiler_info() << sync_endl;
|
||||
else if (token == "export_net")
|
||||
{
|
||||
std::pair<std::optional<std::string>, std::string> files[2];
|
||||
std::optional<std::filesystem::path> file;
|
||||
std::string filename;
|
||||
|
||||
if (is >> std::skipws >> files[0].second)
|
||||
files[0].first = files[0].second;
|
||||
if (is >> filename)
|
||||
file = path_from_utf8(filename);
|
||||
|
||||
if (is >> std::skipws >> files[1].second)
|
||||
files[1].first = files[1].second;
|
||||
|
||||
engine.save_network(files);
|
||||
engine.save_network(file);
|
||||
}
|
||||
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
|
||||
sync_cout
|
||||
@@ -175,7 +180,7 @@ void UCIEngine::loop() {
|
||||
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
|
||||
<< sync_endl;
|
||||
|
||||
} while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot
|
||||
} while (token != "quit" && cli.argc <= 1); // The command-line arguments are one-shot
|
||||
}
|
||||
|
||||
Search::LimitsType UCIEngine::parse_limits(std::istream& is) {
|
||||
@@ -185,9 +190,13 @@ Search::LimitsType UCIEngine::parse_limits(std::istream& is) {
|
||||
limits.startTime = now(); // The search starts as early as possible
|
||||
|
||||
while (is >> token)
|
||||
{
|
||||
if (token == "searchmoves") // Needs to be the last command on the line
|
||||
{
|
||||
while (is >> token)
|
||||
limits.searchmoves.push_back(to_lower(token));
|
||||
break;
|
||||
}
|
||||
|
||||
else if (token == "wtime")
|
||||
is >> limits.time[WHITE];
|
||||
@@ -214,6 +223,10 @@ Search::LimitsType UCIEngine::parse_limits(std::istream& is) {
|
||||
else if (token == "ponder")
|
||||
limits.ponderMode = true;
|
||||
|
||||
if (is.fail())
|
||||
terminate_on_critical_error("Invalid argument for '" + token + "'");
|
||||
}
|
||||
|
||||
return limits;
|
||||
}
|
||||
|
||||
@@ -229,8 +242,8 @@ void UCIEngine::go(std::istringstream& is) {
|
||||
|
||||
void UCIEngine::bench(std::istream& args) {
|
||||
std::string token;
|
||||
uint64_t num, nodes = 0, cnt = 1;
|
||||
uint64_t nodesSearched = 0;
|
||||
u64 num, nodes = 0, cnt = 1;
|
||||
u64 nodesSearched = 0;
|
||||
const auto& options = engine.get_options();
|
||||
|
||||
engine.set_on_update_full([&](const auto& i) {
|
||||
@@ -248,7 +261,7 @@ void UCIEngine::bench(std::istream& args) {
|
||||
for (const auto& cmd : list)
|
||||
{
|
||||
std::istringstream is(cmd);
|
||||
is >> std::skipws >> token;
|
||||
is >> token;
|
||||
|
||||
if (token == "go" || token == "eval")
|
||||
{
|
||||
@@ -301,15 +314,15 @@ void UCIEngine::benchmark(std::istream& args) {
|
||||
static constexpr int NUM_WARMUP_POSITIONS = 3;
|
||||
|
||||
std::string token;
|
||||
uint64_t nodes = 0, cnt = 1;
|
||||
uint64_t nodesSearched = 0;
|
||||
u64 nodes = 0, cnt = 1;
|
||||
u64 nodesSearched = 0;
|
||||
|
||||
engine.set_on_update_full([&](const Engine::InfoFull& i) { nodesSearched = i.nodes; });
|
||||
|
||||
engine.set_on_iter([](const auto&) {});
|
||||
engine.set_on_update_no_moves([](const auto&) {});
|
||||
engine.set_on_bestmove([](const auto&, const auto&) {});
|
||||
engine.set_on_verify_networks([](const auto&) {});
|
||||
engine.set_on_verify_network([](const auto&) {});
|
||||
|
||||
Benchmark::BenchmarkSetup setup = Benchmark::setup_benchmark(args);
|
||||
|
||||
@@ -330,7 +343,7 @@ void UCIEngine::benchmark(std::istream& args) {
|
||||
for (const auto& cmd : setup.commands)
|
||||
{
|
||||
std::istringstream is(cmd);
|
||||
is >> std::skipws >> token;
|
||||
is >> token;
|
||||
|
||||
if (token == "go")
|
||||
{
|
||||
@@ -381,7 +394,7 @@ void UCIEngine::benchmark(std::istream& args) {
|
||||
for (const auto& cmd : setup.commands)
|
||||
{
|
||||
std::istringstream is(cmd);
|
||||
is >> std::skipws >> token;
|
||||
is >> token;
|
||||
|
||||
if (token == "go")
|
||||
{
|
||||
@@ -459,13 +472,19 @@ void UCIEngine::setoption(std::istringstream& is) {
|
||||
engine.get_options().setoption(is);
|
||||
}
|
||||
|
||||
std::uint64_t UCIEngine::perft(const Search::LimitsType& limits) {
|
||||
auto nodes = engine.perft(engine.fen(), limits.perft, engine.get_options()["UCI_Chess960"]);
|
||||
u64 UCIEngine::perft(const Search::LimitsType& limits) {
|
||||
auto result = engine.perft(engine.fen(), limits.perft, engine.get_options()["UCI_Chess960"]);
|
||||
if (auto err = std::get_if<PositionSetError>(&result))
|
||||
terminate_on_critical_error(err->what());
|
||||
|
||||
auto nodes = std::get<u64>(result);
|
||||
sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
|
||||
return nodes;
|
||||
}
|
||||
|
||||
void UCIEngine::position(std::istringstream& is) {
|
||||
const std::string fullCommand = is.str();
|
||||
|
||||
std::string token, fen;
|
||||
|
||||
is >> token;
|
||||
@@ -488,7 +507,11 @@ void UCIEngine::position(std::istringstream& is) {
|
||||
moves.push_back(token);
|
||||
}
|
||||
|
||||
engine.set_position(fen, moves);
|
||||
auto err = engine.set_position(fen, moves);
|
||||
if (err.has_value())
|
||||
{
|
||||
terminate_on_critical_error(err->what());
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -535,8 +558,7 @@ std::string UCIEngine::format_score(const Score& s) {
|
||||
return std::string("mate ") + std::to_string(m);
|
||||
},
|
||||
[](Score::Tablebase tb) -> std::string {
|
||||
return std::string("cp ")
|
||||
+ std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies));
|
||||
return std::string("cp ") + std::to_string((tb.win ? TB_CP : -TB_CP) - tb.plies);
|
||||
},
|
||||
[](Score::InternalUnits units) -> std::string {
|
||||
return std::string("cp ") + std::to_string(units.value);
|
||||
@@ -596,7 +618,8 @@ std::string UCIEngine::move(Move m, bool chess960) {
|
||||
|
||||
|
||||
std::string UCIEngine::to_lower(std::string str) {
|
||||
std::transform(str.begin(), str.end(), str.begin(), [](auto c) { return std::tolower(c); });
|
||||
std::transform(str.begin(), str.end(), str.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
return str;
|
||||
}
|
||||
@@ -658,4 +681,11 @@ void UCIEngine::on_bestmove(std::string_view bestmove, std::string_view ponder)
|
||||
std::cout << sync_endl;
|
||||
}
|
||||
|
||||
void UCIEngine::terminate_on_critical_error(const std::string& message) {
|
||||
sync_cout << "info string CRITICAL ERROR: Command `" << currentCmd
|
||||
<< "` failed. Reason: " << message << '\n'
|
||||
<< sync_endl;
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#ifndef UCI_H_INCLUDED
|
||||
#define UCI_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -33,39 +32,42 @@ namespace Stockfish {
|
||||
class Position;
|
||||
class Move;
|
||||
class Score;
|
||||
enum Square : uint8_t;
|
||||
enum Square : u8;
|
||||
using Value = int;
|
||||
|
||||
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
|
||||
class UCIEngine {
|
||||
public:
|
||||
UCIEngine(int argc, char** argv);
|
||||
UCIEngine(CommandLine cli);
|
||||
|
||||
void loop();
|
||||
|
||||
static int to_cp(Value v, const Position& pos);
|
||||
static std::string format_score(const Score& s);
|
||||
static std::string square(Square s);
|
||||
static std::string move(Move m, bool chess960);
|
||||
static std::string move(Move m, bool chess960 = false);
|
||||
static std::string wdl(Value v, const Position& pos);
|
||||
static std::string to_lower(std::string str);
|
||||
static Move to_move(const Position& pos, std::string str);
|
||||
|
||||
static Search::LimitsType parse_limits(std::istream& is);
|
||||
Search::LimitsType parse_limits(std::istream& is);
|
||||
|
||||
auto& engine_options() { return engine.get_options(); }
|
||||
|
||||
private:
|
||||
Engine engine;
|
||||
CommandLine cli;
|
||||
std::string currentCmd;
|
||||
|
||||
static void print_info_string(std::string_view str);
|
||||
|
||||
void go(std::istringstream& is);
|
||||
void bench(std::istream& args);
|
||||
void benchmark(std::istream& args);
|
||||
void position(std::istringstream& is);
|
||||
void setoption(std::istringstream& is);
|
||||
std::uint64_t perft(const Search::LimitsType&);
|
||||
void go(std::istringstream& is);
|
||||
void bench(std::istream& args);
|
||||
void benchmark(std::istream& args);
|
||||
void position(std::istringstream& is);
|
||||
void setoption(std::istringstream& is);
|
||||
u64 perft(const Search::LimitsType&);
|
||||
|
||||
static void on_update_no_moves(const Engine::InfoShort& info);
|
||||
static void on_update_full(const Engine::InfoFull& info, bool showWDL);
|
||||
@@ -73,6 +75,8 @@ class UCIEngine {
|
||||
static void on_bestmove(std::string_view bestmove, std::string_view ponder);
|
||||
|
||||
void init_search_update_listeners();
|
||||
|
||||
[[noreturn]] void terminate_on_critical_error(const std::string& message);
|
||||
};
|
||||
|
||||
} // namespace Stockfish
|
||||
|
||||
+16
-5
@@ -21,6 +21,7 @@
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
@@ -34,7 +35,7 @@ bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s
|
||||
|
||||
return std::lexicographical_compare(
|
||||
s1.begin(), s1.end(), s2.begin(), s2.end(),
|
||||
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
|
||||
[](unsigned char c1, unsigned char c2) { return std::tolower(c1) < std::tolower(c2); });
|
||||
}
|
||||
|
||||
void OptionsMap::add_info_listener(InfoListener&& message_func) { info = std::move(message_func); }
|
||||
@@ -68,7 +69,7 @@ const Option& OptionsMap::operator[](const std::string& name) const {
|
||||
void OptionsMap::add(const std::string& name, const Option& option) {
|
||||
if (!options_map.count(name))
|
||||
{
|
||||
static size_t insert_order = 0;
|
||||
static usize insert_order = 0;
|
||||
|
||||
options_map[name] = option;
|
||||
|
||||
@@ -83,7 +84,7 @@ void OptionsMap::add(const std::string& name, const Option& option) {
|
||||
}
|
||||
|
||||
|
||||
std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); }
|
||||
usize OptionsMap::count(const std::string& name) const { return options_map.count(name); }
|
||||
|
||||
Option::Option(const OptionsMap* map) :
|
||||
parent(map) {}
|
||||
@@ -144,6 +145,16 @@ bool Option::operator==(const char* s) const {
|
||||
|
||||
bool Option::operator!=(const char* s) const { return !(*this == s); }
|
||||
|
||||
static bool value_in_range(const std::string& v, int min, int max) {
|
||||
if (v.empty())
|
||||
return false;
|
||||
errno = 0;
|
||||
char* end = nullptr;
|
||||
const long long result = std::strtoll(v.c_str(), &end, 10);
|
||||
if (errno == ERANGE || *end != '\0')
|
||||
return false;
|
||||
return result >= min && result <= max;
|
||||
}
|
||||
|
||||
// Updates currentValue and triggers on_change() action. It's up to
|
||||
// the GUI to check for option's limits, but we could receive the new value
|
||||
@@ -154,7 +165,7 @@ Option& Option::operator=(const std::string& v) {
|
||||
|
||||
if ((type != "button" && type != "string" && v.empty())
|
||||
|| (type == "check" && v != "true" && v != "false")
|
||||
|| (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max)))
|
||||
|| (type == "spin" && !value_in_range(v, min, max)))
|
||||
return *this;
|
||||
|
||||
if (type == "combo")
|
||||
@@ -185,7 +196,7 @@ Option& Option::operator=(const std::string& v) {
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
|
||||
for (size_t idx = 0; idx < om.options_map.size(); ++idx)
|
||||
for (usize idx = 0; idx < om.options_map.size(); ++idx)
|
||||
for (const auto& it : om.options_map)
|
||||
if (it.second.idx == idx)
|
||||
{
|
||||
|
||||
+4
-3
@@ -19,13 +19,14 @@
|
||||
#ifndef UCIOPTION_H_INCLUDED
|
||||
#define UCIOPTION_H_INCLUDED
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
namespace Stockfish {
|
||||
// Define a custom comparator, because the UCI options should be case-insensitive
|
||||
struct CaseInsensitiveLess {
|
||||
@@ -64,7 +65,7 @@ class Option {
|
||||
|
||||
std::string defaultValue, currentValue, type;
|
||||
int min, max;
|
||||
size_t idx;
|
||||
usize idx;
|
||||
OnChange on_change;
|
||||
const OptionsMap* parent = nullptr;
|
||||
};
|
||||
@@ -87,7 +88,7 @@ class OptionsMap {
|
||||
|
||||
void add(const std::string&, const Option& option);
|
||||
|
||||
std::size_t count(const std::string&) const;
|
||||
usize count(const std::string&) const;
|
||||
|
||||
private:
|
||||
friend class Engine;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/auxv.h>
|
||||
#ifndef HWCAP_ASIMDDP
|
||||
#define HWCAP_ASIMDDP (1 << 20)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define DEFINE_BUILD(x) \
|
||||
namespace Stockfish_##x { \
|
||||
extern int main(int argc, char* argv[]); \
|
||||
} \
|
||||
extern "C" void (*__start_##x##_init[])(void); \
|
||||
extern "C" void (*__stop_##x##_init[])(void); \
|
||||
int entry_##x(int argc, char* argv[]) { \
|
||||
unsigned count = __stop_##x##_init - __start_##x##_init; \
|
||||
for (unsigned i = 0; i < count; i++) \
|
||||
__start_##x##_init[i](); \
|
||||
return Stockfish_##x::main(argc, argv); \
|
||||
}
|
||||
|
||||
DEFINE_BUILD(armv8)
|
||||
DEFINE_BUILD(armv8_dotprod)
|
||||
|
||||
struct CpuFeatures {
|
||||
bool dotprod;
|
||||
};
|
||||
|
||||
|
||||
static CpuFeatures query_cpu_features() {
|
||||
#if defined(_WIN32)
|
||||
return {
|
||||
.dotprod = (bool) IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE),
|
||||
};
|
||||
#else
|
||||
unsigned long hwcap = getauxval(AT_HWCAP);
|
||||
return {
|
||||
.dotprod = (bool) (hwcap & HWCAP_ASIMDDP),
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// Selects the most capable ISA variant supported by this CPU and OS
|
||||
static int dispatch(const CpuFeatures& f, int argc, char* argv[]) {
|
||||
if (!f.dotprod)
|
||||
return entry_armv8(argc, argv);
|
||||
|
||||
return entry_armv8_dotprod(argc, argv);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CpuFeatures features = query_cpu_features();
|
||||
return dispatch(features, argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/auxv.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
#else
|
||||
#error This file is only supported on Linux
|
||||
#endif
|
||||
|
||||
#define DEFINE_BUILD(x) \
|
||||
namespace Stockfish_##x { \
|
||||
extern int main(int argc, char* argv[]); \
|
||||
} \
|
||||
extern "C" void (*__start_##x##_init[])(void); \
|
||||
extern "C" void (*__stop_##x##_init[])(void); \
|
||||
int entry_##x(int argc, char* argv[]) { \
|
||||
unsigned count = __stop_##x##_init - __start_##x##_init; \
|
||||
for (unsigned i = 0; i < count; i++) \
|
||||
__start_##x##_init[i](); \
|
||||
return Stockfish_##x::main(argc, argv); \
|
||||
}
|
||||
|
||||
DEFINE_BUILD(riscv64)
|
||||
DEFINE_BUILD(riscv64_rva23)
|
||||
|
||||
struct CpuFeatures {
|
||||
bool gcv; // GCV
|
||||
bool zbitmanip; // Zba + Zbb + Zbs
|
||||
bool zicond; // Zicond
|
||||
int vlen; // vector length in bits (valid only if gcv is true), always a power of two
|
||||
};
|
||||
|
||||
static CpuFeatures query_cpu_features() {
|
||||
unsigned long hwcap = getauxval(AT_HWCAP);
|
||||
constexpr long MASK_GCV = 0x20112d;
|
||||
|
||||
bool gcv = (hwcap & MASK_GCV) == MASK_GCV;
|
||||
int vlen = 0;
|
||||
if (gcv)
|
||||
{
|
||||
unsigned long vlenb;
|
||||
asm volatile("csrr %0, 0xc22" : "=r"(vlenb)); // 0xc22 = vlenb
|
||||
vlen = int(vlenb * 8);
|
||||
}
|
||||
|
||||
constexpr long NR_riscv_hwprobe = 258;
|
||||
constexpr int64_t KEY_IMA_EXT_0 = 4;
|
||||
constexpr uint64_t EXT_ZB = 1 << 3 | 1 << 4 | 1 << 5;
|
||||
constexpr uint64_t EXT_ZICOND = uint64_t(1) << 35;
|
||||
|
||||
struct {
|
||||
int64_t key;
|
||||
uint64_t value;
|
||||
} pair = {KEY_IMA_EXT_0, 0};
|
||||
|
||||
long rc = syscall(NR_riscv_hwprobe, &pair, 1UL, 0UL, nullptr, 0U);
|
||||
bool zbitmanip = false, zicond = false;
|
||||
|
||||
if (rc == 0 && pair.key == KEY_IMA_EXT_0)
|
||||
{
|
||||
zbitmanip = (pair.value & EXT_ZB) == EXT_ZB;
|
||||
zicond = (pair.value & EXT_ZICOND) == EXT_ZICOND;
|
||||
}
|
||||
|
||||
return CpuFeatures{.gcv = gcv, .zbitmanip = zbitmanip, .zicond = zicond, .vlen = vlen};
|
||||
}
|
||||
|
||||
|
||||
// Selects the most capable ISA variant supported by this CPU
|
||||
static int dispatch(const CpuFeatures& f, int argc, char* argv[]) {
|
||||
if (!f.gcv || !f.zbitmanip || !f.zicond || f.vlen < 128)
|
||||
return entry_riscv64(argc, argv);
|
||||
|
||||
return entry_riscv64_rva23(argc, argv);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
CpuFeatures f = query_cpu_features();
|
||||
return dispatch(f, argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
#include <cpuid.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
// We locate each arch's initializer pointer array at runtime via getsectiondata().
|
||||
// Example name is "_i_sse41_popcnt", baseline build is just "_i_"
|
||||
#include <stdio.h>
|
||||
#include <mach-o/getsect.h>
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
extern "C" const struct mach_header_64 _mh_execute_header;
|
||||
|
||||
#define DEFINE_BUILD(x) \
|
||||
namespace Stockfish_##x { \
|
||||
extern int main(int argc, char* argv[]); \
|
||||
} \
|
||||
int entry_##x(int argc, char* argv[]) { \
|
||||
char name[17]; \
|
||||
const char* full = #x; \
|
||||
snprintf(name, sizeof(name), "_i_%s", full[6] ? full + 7 : ""); \
|
||||
unsigned long size = 0; \
|
||||
auto** fns = reinterpret_cast<void (**)()>( \
|
||||
getsectiondata(&_mh_execute_header, "__DATA", name, &size)); \
|
||||
for (unsigned long i = 0; i < size / sizeof(*fns); i++) \
|
||||
fns[i](); \
|
||||
return Stockfish_##x::main(argc, argv); \
|
||||
}
|
||||
#else
|
||||
#define DEFINE_BUILD(x) \
|
||||
namespace Stockfish_##x { \
|
||||
extern int main(int argc, char* argv[]); \
|
||||
} \
|
||||
extern "C" void (*__start_##x##_init[])(void); \
|
||||
extern "C" void (*__stop_##x##_init[])(void); \
|
||||
int entry_##x(int argc, char* argv[]) { \
|
||||
unsigned count = __stop_##x##_init - __start_##x##_init; \
|
||||
for (unsigned i = 0; i < count; i++) \
|
||||
__start_##x##_init[i](); \
|
||||
return Stockfish_##x::main(argc, argv); \
|
||||
}
|
||||
#endif
|
||||
|
||||
DEFINE_BUILD(x86_64)
|
||||
DEFINE_BUILD(x86_64_sse41_popcnt)
|
||||
DEFINE_BUILD(x86_64_avx2)
|
||||
DEFINE_BUILD(x86_64_bmi2)
|
||||
DEFINE_BUILD(x86_64_avxvnni)
|
||||
DEFINE_BUILD(x86_64_avx512)
|
||||
DEFINE_BUILD(x86_64_vnni512)
|
||||
DEFINE_BUILD(x86_64_avx512icl)
|
||||
|
||||
// AMD Excavator (family 15h) and Zen/Zen+/Zen2 (family 17h) implement pdep/pext via microcode.
|
||||
static bool has_slow_bmi2() {
|
||||
return __builtin_cpu_is("amd")
|
||||
&& (__builtin_cpu_is("bdver4") || __builtin_cpu_is("znver1") || __builtin_cpu_is("znver2"));
|
||||
}
|
||||
|
||||
struct CpuFeatures {
|
||||
bool sse41; // SSE4.1
|
||||
bool popcnt; // POPCNT
|
||||
bool avx2; // AVX2
|
||||
bool bmi2; // BMI2 (may be slow on AMD Excavator and Zen/Zen+/Zen2)
|
||||
bool avx512f; // AVX-512 Foundation
|
||||
bool avx512vl; // AVX-512 Vector Length extensions
|
||||
bool avx512bw; // AVX-512 Byte and Word instructions
|
||||
bool avx512vnni; // AVX-512 Vector Neural Network Instructions
|
||||
bool avx512ifma; // AVX-512 Integer Fused Multiply-Add
|
||||
bool avx512vbmi; // AVX-512 Vector Bit Manipulation Instructions
|
||||
bool avx512vbmi2; // AVX-512 VBMI2
|
||||
bool avx512vpopcntdq; // AVX-512 VPOPCNTDQ
|
||||
bool avx512bitalg; // AVX-512 BITALG
|
||||
bool vpclmulqdq; // Carry-less multiplication (AVX512 variant)
|
||||
bool gfni; // Galois Field instructions
|
||||
bool vaes; // AES instructions (AVX512 variant)
|
||||
bool avxvnni; // AVX-VNNI (non-512 dot product instructions)
|
||||
};
|
||||
|
||||
|
||||
static CpuFeatures query_cpu_features() {
|
||||
return {
|
||||
.sse41 = (bool) __builtin_cpu_supports("sse4.1"),
|
||||
.popcnt = (bool) __builtin_cpu_supports("popcnt"),
|
||||
.avx2 = (bool) __builtin_cpu_supports("avx2"),
|
||||
.bmi2 = (bool) __builtin_cpu_supports("bmi2"),
|
||||
.avx512f = (bool) __builtin_cpu_supports("avx512f"),
|
||||
.avx512vl = (bool) __builtin_cpu_supports("avx512vl"),
|
||||
.avx512bw = (bool) __builtin_cpu_supports("avx512bw"),
|
||||
.avx512vnni = (bool) __builtin_cpu_supports("avx512vnni"),
|
||||
.avx512ifma = (bool) __builtin_cpu_supports("avx512ifma"),
|
||||
.avx512vbmi = (bool) __builtin_cpu_supports("avx512vbmi"),
|
||||
.avx512vbmi2 = (bool) __builtin_cpu_supports("avx512vbmi2"),
|
||||
.avx512vpopcntdq = (bool) __builtin_cpu_supports("avx512vpopcntdq"),
|
||||
.avx512bitalg = (bool) __builtin_cpu_supports("avx512bitalg"),
|
||||
.vpclmulqdq = (bool) __builtin_cpu_supports("vpclmulqdq"),
|
||||
.gfni = (bool) __builtin_cpu_supports("gfni"),
|
||||
.vaes = (bool) __builtin_cpu_supports("vaes"),
|
||||
.avxvnni = (bool) __builtin_cpu_supports("avxvnni"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Selects the most capable ISA variant supported by this CPU and OS
|
||||
static int dispatch(const CpuFeatures& f, int argc, char* argv[]) {
|
||||
if (!f.sse41 || !f.popcnt)
|
||||
return entry_x86_64(argc, argv);
|
||||
|
||||
if (!f.avx2)
|
||||
return entry_x86_64_sse41_popcnt(argc, argv);
|
||||
|
||||
if (!f.bmi2 || has_slow_bmi2())
|
||||
return entry_x86_64_avx2(argc, argv);
|
||||
|
||||
if (!f.avx512f || !f.avx512vl || !f.avx512bw)
|
||||
{
|
||||
if (f.avxvnni)
|
||||
return entry_x86_64_avxvnni(argc, argv);
|
||||
return entry_x86_64_bmi2(argc, argv);
|
||||
}
|
||||
|
||||
if (!f.avx512vnni)
|
||||
return entry_x86_64_avx512(argc, argv);
|
||||
|
||||
// AVX512ICL requires the full Icelake-client feature suite
|
||||
if (!f.avx512ifma //
|
||||
|| !f.avx512vbmi //
|
||||
|| !f.avx512vbmi2 //
|
||||
|| !f.avx512vpopcntdq //
|
||||
|| !f.avx512bitalg //
|
||||
|| !f.vpclmulqdq //
|
||||
|| !f.gfni //
|
||||
|| !f.vaes //
|
||||
)
|
||||
return entry_x86_64_vnni512(argc, argv);
|
||||
|
||||
return entry_x86_64_avx512icl(argc, argv);
|
||||
}
|
||||
|
||||
static void maybe_promote_thread_to_avx512() {
|
||||
#ifdef __APPLE__
|
||||
// Intel Macs supporting AVX512 don't advertise it in xgetbv and only
|
||||
// do so once at least one avx512 instruction has been executed.
|
||||
// See https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/osfmk/i386/fpu.c#L176
|
||||
|
||||
int supported = 0;
|
||||
size_t len = sizeof(supported);
|
||||
if (sysctlbyname("hw.optional.avx512f", &supported, &len, nullptr, 0) == 0 && supported)
|
||||
{
|
||||
asm volatile(".byte 0x62, 0xf1, 0x7d, 0x48, 0x6f, 0xc0"); // vmovdqa32 zmm0,zmm0
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
maybe_promote_thread_to_avx512();
|
||||
|
||||
__builtin_cpu_init();
|
||||
CpuFeatures features = query_cpu_features();
|
||||
return dispatch(features, argc, argv);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
// Standalone NNUE embedding for universal binary builds
|
||||
|
||||
#include "../evaluate.h"
|
||||
|
||||
#ifdef UNIVERSAL_BINARY_MACOS_X86_SLICE
|
||||
|
||||
// In a macOS universal binary the network is embedded only in the arm64 slice,
|
||||
// and the x86-64 slice mmaps it from the arm64 slice.
|
||||
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <fcntl.h>
|
||||
#include <mach-o/dyld.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Must be kept in sync with patch_x86_slice.sh
|
||||
extern const volatile Stockfish::u64 gUniversalNNUEOffset = 0xCAFE0FF5E70FF5E7ULL;
|
||||
extern const volatile Stockfish::u64 gUniversalNNUESize = 0xCAFE512ECAFE512EULL;
|
||||
|
||||
static const unsigned char* map_embedded_nnue() {
|
||||
char path[PATH_MAX];
|
||||
Stockfish::u32 len = sizeof(path);
|
||||
if (_NSGetExecutablePath(path, &len) != 0)
|
||||
return nullptr;
|
||||
|
||||
char resolved[PATH_MAX];
|
||||
const char* file = realpath(path, resolved) ? resolved : path;
|
||||
|
||||
int fd = open(file, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return nullptr;
|
||||
|
||||
// Align down to page size for mmap
|
||||
const Stockfish::u64 pageSize = Stockfish::u64(sysconf(_SC_PAGESIZE));
|
||||
const Stockfish::u64 base = gUniversalNNUEOffset & ~(pageSize - 1);
|
||||
const Stockfish::u64 pad = gUniversalNNUEOffset - base;
|
||||
|
||||
void* p =
|
||||
mmap(nullptr, size_t(gUniversalNNUESize + pad), PROT_READ, MAP_PRIVATE, fd, off_t(base));
|
||||
close(fd);
|
||||
if (p == MAP_FAILED)
|
||||
return nullptr;
|
||||
|
||||
return reinterpret_cast<const unsigned char*>(p) + pad;
|
||||
}
|
||||
|
||||
extern const unsigned char* const gEmbeddedNNUEData = map_embedded_nnue();
|
||||
extern const unsigned int gEmbeddedNNUESize = static_cast<unsigned int>(gUniversalNNUESize);
|
||||
|
||||
#else
|
||||
|
||||
extern const unsigned char gEmbeddedNNUEData[] =
|
||||
#ifdef __has_embed
|
||||
{
|
||||
#embed EvalFileDefaultName
|
||||
};
|
||||
const unsigned int padding = 0;
|
||||
#else
|
||||
#include "network_dump.inc"
|
||||
;
|
||||
const unsigned int padding = 1; // trailing NUL byte
|
||||
#endif
|
||||
extern const unsigned int gEmbeddedNNUESize = sizeof(gEmbeddedNNUEData) - padding;
|
||||
|
||||
#endif
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
|
||||
# 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/>.
|
||||
|
||||
# This finds the gUniversalNNUEOffset/gUniversalNNUESize symbols in the x86 slice
|
||||
# and patches them to be an offset+size into the full executable.
|
||||
#
|
||||
# Usage: patch_x86_slice.sh <fat_binary> <x86_64_thin_slice> <nnue_file>
|
||||
|
||||
set -eu
|
||||
|
||||
FAT="$1"
|
||||
X86="$2"
|
||||
NET="$3"
|
||||
|
||||
# Must match volatile initializers in nnue_embed.cpp; byte order
|
||||
# is reversed to match how it appears in the binary xxd
|
||||
OFFSET_MAGIC=e7f50fe7f50ffeca
|
||||
SIZE_MAGIC=2e51feca2e51feca
|
||||
|
||||
die() { echo "patch_x86_slice: $*" >&2; exit 1; }
|
||||
|
||||
# Find hex pattern in file, return byte offset
|
||||
# Optional $3 = bytes to skip, $4 = max bytes to scan
|
||||
find_bytes() {
|
||||
file=$1; needle=$2; skip=${3:-0}; window=${4:-0}
|
||||
[ "$window" -gt 0 ] && win="-l $window" || win=""
|
||||
# Disassemble as hex, strip \n and search for the pattern
|
||||
pos=$(xxd -s "$skip" $win -p "$file" | tr -d '\n' \
|
||||
| awk -v n="$needle" '{ p = index($0, n); if (p) { print p; exit } }')
|
||||
[ -n "$pos" ] || return 1
|
||||
echo $(( skip + (pos - 1) / 2 ))
|
||||
}
|
||||
|
||||
# Overwrite 8 bytes at a file offset
|
||||
write_u64_le() {
|
||||
f="$1"; off="$2"; v="$3"; bytes=""; i=0
|
||||
while [ "$i" -lt 8 ]; do
|
||||
bytes="$bytes$(printf '\\%03o' "$(( v & 255 ))")"
|
||||
v=$(( v >> 8 )); i=$(( i + 1 ))
|
||||
done
|
||||
printf "$bytes" | dd of="$f" bs=1 seek="$off" conv=notrunc 2>/dev/null
|
||||
}
|
||||
|
||||
needle=$(xxd -p -l 64 "$NET" | tr -d '\n') # take first bytes of network as needle
|
||||
net_size=$(wc -c < "$NET" | tr -d ' ')
|
||||
|
||||
# The arm64 slice always follows the x86-64 slice in
|
||||
# the fat binary, so scan from ~after the x86-64 slice.
|
||||
x86_size=$(wc -c < "$X86" | tr -d ' ')
|
||||
net_off=$(find_bytes "$FAT" "$needle" "$x86_size" 8000000) || die "network not found"
|
||||
|
||||
off_pos=$(find_bytes "$X86" "$OFFSET_MAGIC") || die "offset sentinel not found"
|
||||
size_pos=$(find_bytes "$X86" "$SIZE_MAGIC") || die "size sentinel not found"
|
||||
|
||||
write_u64_le "$X86" "$off_pos" "$net_off"
|
||||
write_u64_le "$X86" "$size_pos" "$net_size"
|
||||
|
||||
echo "patch_x86_slice.sh: network at offset $net_off + size $net_size "
|
||||
@@ -0,0 +1,83 @@
|
||||
# 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/>.
|
||||
|
||||
# This is used if the universal binary is compiled on clang+Windows.
|
||||
# Usage (avx2 example):
|
||||
# awk -v MODNAME=x86_64_avx2 -f rewrite_asm_sections.awk stockfish.exe.lto.s > renamed.s
|
||||
#
|
||||
# The relevant output from --lto-emit-asm looks like:
|
||||
# ...
|
||||
# .section .ctors,"dw",associative,_ZN21Stockfish_x86_64_avx217SYSTEM_THREADS_NBE,unique,0
|
||||
# .p2align 3, 0x0
|
||||
# .quad __cxx_global_var_init
|
||||
# .section .ctors,"a",unique,0
|
||||
# .p2align 3, 0x0
|
||||
# .quad _GLOBAL__sub_I_benchmark.cpp
|
||||
# .quad _GLOBAL__sub_I_evaluate.cpp
|
||||
# ...
|
||||
# .quad _GLOBAL__sub_I_engine.cpp
|
||||
# .quad _GLOBAL__sub_I_score.cpp
|
||||
#
|
||||
# .section .bss$_ZN21Stockfish_x86_64_avx2L26STARTUP_PROCESSOR_AFFINITYE,"bw",one_only,_ZN21Stockfish_x86_64_avx2L26STARTUP_PROCESSOR_AFFINITYE,unique,570
|
||||
# ...
|
||||
#
|
||||
# We want to consolidate everything into a single section named x86_64_avx2_init, and insert
|
||||
# symbols __start_x86_64_avx2_init/__stop_x86_64_avx2_init for entry_x86.cpp to use.
|
||||
# (On ELF these symbols are implicitly defined, so naming it this way keeps the universal
|
||||
# entry point consistent.)
|
||||
|
||||
BEGIN {
|
||||
in_ctors = 0
|
||||
}
|
||||
|
||||
# Match any .section line containing .ctors
|
||||
/^[[:space:]]*\.section[[:space:]].*\.ctors/ {
|
||||
|
||||
# First .ctors section, emit start symbol
|
||||
if (!in_ctors) {
|
||||
in_ctors = 1
|
||||
section = MODNAME "_init"
|
||||
|
||||
printf "\t.section\t%s,\"a\"\n", section
|
||||
printf "\t.globl\t__start_%s\n", section
|
||||
printf "__start_%s:\n", section
|
||||
}
|
||||
|
||||
# Suppress all original .ctors section directives
|
||||
next
|
||||
}
|
||||
|
||||
# First non-.ctors .section after ctors block, emit stop symbol
|
||||
in_ctors && /^[[:space:]]*\.section/ {
|
||||
section = MODNAME "_init"
|
||||
|
||||
printf "\t.globl\t__stop_%s\n", section
|
||||
printf "__stop_%s:\n", section
|
||||
|
||||
in_ctors = 0
|
||||
}
|
||||
|
||||
{
|
||||
print
|
||||
}
|
||||
|
||||
END {
|
||||
if (in_ctors) {
|
||||
section = MODNAME "_init"
|
||||
printf "\t.globl\t__stop_%s\n", section
|
||||
printf "__stop_%s:\n", section
|
||||
}
|
||||
}
|
||||
+210
-5
@@ -4,10 +4,10 @@ import sys
|
||||
import subprocess
|
||||
import pathlib
|
||||
import os
|
||||
import fnmatch
|
||||
|
||||
from testing import (
|
||||
EPD,
|
||||
TSAN,
|
||||
Stockfish as Engine,
|
||||
MiniTestFramework,
|
||||
OrderedClassMembers,
|
||||
@@ -30,7 +30,7 @@ def get_prefix():
|
||||
|
||||
def get_threads():
|
||||
if args.valgrind_thread or args.sanitizer_thread:
|
||||
return 2
|
||||
return 4
|
||||
return 1
|
||||
|
||||
|
||||
@@ -474,6 +474,203 @@ class TestSyzygy(metaclass=OrderedClassMembers):
|
||||
self.stockfish.check_output(check_output)
|
||||
self.stockfish.expect("bestmove *")
|
||||
|
||||
class TestEnPassantSanitization(metaclass=OrderedClassMembers):
|
||||
def beforeAll(self):
|
||||
self.stockfish = Stockfish()
|
||||
|
||||
def afterAll(self):
|
||||
self.stockfish.quit()
|
||||
assert self.stockfish.close() == 0
|
||||
|
||||
def afterEach(self):
|
||||
assert postfix_check(self.stockfish.get_output()) == True
|
||||
self.stockfish.clear_output()
|
||||
|
||||
def test_position_1(self):
|
||||
self.stockfish.send_command("position fen rnbqkbnr/ppp1p1pp/5p2/3pP3/8/8/PPPP1PPP/RNBQKBNR w kq d6 0 3")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*rnbqkbnr/ppp1p1pp/5p2/3pP3/8/8/PPPP1PPP/RNBQKBNR w kq d6 0 3*")
|
||||
|
||||
def test_position_2(self):
|
||||
self.stockfish.send_command("position fen k7/8/8/1pP5/2K5/8/8/8 w - b6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*k7/8/8/1pP5/2K5/8/8/8 w - b6 0 1*")
|
||||
|
||||
def test_position_3(self):
|
||||
self.stockfish.send_command("position fen k1r5/8/8/1pP5/2K5/8/8/8 w - b6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*k1r5/8/8/1pP5/2K5/8/8/8 w - - 0 1*")
|
||||
|
||||
def test_position_4(self):
|
||||
self.stockfish.send_command("position fen k1r5/8/8/1pP5/8/2K5/8/8 w - b6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*k1r5/8/8/1pP5/8/2K5/8/8 w - - 0 1*")
|
||||
|
||||
def test_position_5(self):
|
||||
self.stockfish.send_command("position fen k1r5/8/8/PpP5/8/2K5/8/8 w - b6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*k1r5/8/8/PpP5/8/2K5/8/8 w - b6 0 1*")
|
||||
|
||||
def test_position_6(self):
|
||||
self.stockfish.send_command("position fen k1r5/8/8/PpP5/2K5/8/8/8 w - b6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*k1r5/8/8/PpP5/2K5/8/8/8 w - b6 0 1*")
|
||||
|
||||
def test_position_7(self):
|
||||
self.stockfish.send_command("position fen k7/4b3/8/PpP5/1K6/8/8/8 w - b6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*k7/4b3/8/PpP5/1K6/8/8/8 w - b6 0 1*")
|
||||
|
||||
def test_position_8(self):
|
||||
self.stockfish.send_command("position fen k7/b5b1/8/2PpP3/3K4/8/8/8 w - d6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*k7/b5b1/8/2PpP3/3K4/8/8/8 w - - 0 1*")
|
||||
|
||||
def test_position_9(self):
|
||||
self.stockfish.send_command("position fen k7/8/8/r2pPK2/8/8/8/8 w - d6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*k7/8/8/r2pPK2/8/8/8/8 w - - 0 1*")
|
||||
|
||||
def test_position_10(self):
|
||||
self.stockfish.send_command("position fen k7/8/8/r1PpPK2/8/8/8/8 w - d6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*k7/8/8/r1PpPK2/8/8/8/8 w - d6 0 1*")
|
||||
|
||||
def test_position_11(self):
|
||||
self.stockfish.send_command("position fen kb6/8/8/3pP3/5K2/8/8/8 w - d6 0 1")
|
||||
self.stockfish.send_command("d")
|
||||
|
||||
self.stockfish.expect_for_line_matching("Fen*", "*kb6/8/8/3pP3/5K2/8/8/8 w - d6 0 1*")
|
||||
|
||||
def test_position_find_draw(self):
|
||||
self.stockfish.send_command("position fen q4kb1/3Q2nq/8/r3PpK1/2n5/7q/8/q7 w - f6 0 1 moves d7c8 f8f7 c8d7 f7f8 d7d8 f8f7")
|
||||
self.stockfish.send_command("go nodes 10000")
|
||||
|
||||
def check_output(output):
|
||||
if fnmatch.fnmatch(output, "* score cp 0 * pv d8d7*"):
|
||||
return True
|
||||
|
||||
self.stockfish.check_output(check_output)
|
||||
self.stockfish.expect("bestmove d8d7*")
|
||||
|
||||
class TestInvalidFEN(metaclass=OrderedClassMembers):
|
||||
def beforeEach(self):
|
||||
self.stockfish = None
|
||||
|
||||
def afterEach(self):
|
||||
assert postfix_check(self.stockfish.get_output()) == True
|
||||
self.stockfish.clear_output()
|
||||
|
||||
def _expect_critical(self, fen):
|
||||
self.stockfish = Stockfish(f"position fen {fen}".split(" "), True)
|
||||
assert self.stockfish.process.returncode != 0
|
||||
assert "CRITICAL ERROR" in self.stockfish.process.stdout
|
||||
|
||||
def test_no_kings(self):
|
||||
self._expect_critical("8/8/8/8/8/8/8/8 w - - 0 1")
|
||||
|
||||
def test_invalid_piece(self):
|
||||
self._expect_critical("rnbqkbnr/pppXpppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
|
||||
def test_invalid_side_to_move(self):
|
||||
self._expect_critical("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR x KQkq - 0 1")
|
||||
|
||||
def test_pawns_on_back_rank(self):
|
||||
self._expect_critical("pppppppp/8/8/8/8/8/8/4K2k w - - 0 1")
|
||||
|
||||
def test_invalid_skip_count(self):
|
||||
self._expect_critical("9/8/8/8/8/8/8/8 w - - 0 1")
|
||||
|
||||
|
||||
class TestInvalidOptions(metaclass=OrderedClassMembers):
|
||||
def beforeAll(self):
|
||||
self.stockfish = Stockfish()
|
||||
|
||||
def afterAll(self):
|
||||
self.stockfish.quit()
|
||||
assert self.stockfish.close() == 0
|
||||
|
||||
def afterEach(self):
|
||||
assert postfix_check(self.stockfish.get_output()) == True
|
||||
self.stockfish.clear_output()
|
||||
|
||||
# Ignore bogus spin values
|
||||
def test_spin_non_numeric(self):
|
||||
self.stockfish.send_command("setoption name Threads value abc")
|
||||
self.stockfish.send_command("isready")
|
||||
self.stockfish.equals("readyok")
|
||||
|
||||
def test_spin_out_of_range(self):
|
||||
self.stockfish.send_command("setoption name Threads value 999999999999")
|
||||
self.stockfish.send_command("isready")
|
||||
self.stockfish.equals("readyok")
|
||||
|
||||
def test_spin_negative(self):
|
||||
self.stockfish.send_command("setoption name Threads value -5")
|
||||
self.stockfish.send_command("isready")
|
||||
self.stockfish.equals("readyok")
|
||||
|
||||
# Warn on bogus NUMA configs
|
||||
def test_numa_garbage(self):
|
||||
self.stockfish.send_command("setoption name NumaPolicy value zzz")
|
||||
self.stockfish.expect("*NumaPolicy: invalid value 'zzz', keeping previous config.*")
|
||||
|
||||
def test_numa_malformed_range(self):
|
||||
self.stockfish.send_command("setoption name NumaPolicy value 0-")
|
||||
self.stockfish.expect("*NumaPolicy: invalid value '0-', keeping previous config.*")
|
||||
|
||||
def test_numa_overflow(self):
|
||||
self.stockfish.send_command(
|
||||
"setoption name NumaPolicy value 99999999999999999999999"
|
||||
)
|
||||
self.stockfish.expect("*NumaPolicy: invalid value*keeping previous config.*")
|
||||
self.stockfish.send_command("isready")
|
||||
self.stockfish.equals("readyok")
|
||||
|
||||
|
||||
class TestBenchFile(metaclass=OrderedClassMembers):
|
||||
def beforeEach(self):
|
||||
self.stockfish = None
|
||||
|
||||
def afterEach(self):
|
||||
assert postfix_check(self.stockfish.get_output()) == True
|
||||
self.stockfish.clear_output()
|
||||
|
||||
def _bench(self, name, content):
|
||||
with open(name, "w") as f:
|
||||
f.write(content)
|
||||
self.stockfish = Stockfish(f"bench 16 1 4 {name} depth".split(" "), True)
|
||||
|
||||
def test_valid_file(self):
|
||||
self._bench("good.epd", "4k3/8/4K3/8/8/8/8/8 w - - 0 1\n")
|
||||
assert self.stockfish.process.returncode == 0
|
||||
assert "Nodes searched" in self.stockfish.process.stderr
|
||||
|
||||
def test_empty_file(self):
|
||||
self._bench("empty.epd", "")
|
||||
assert self.stockfish.process.returncode == 0
|
||||
|
||||
def test_malformed_fen(self):
|
||||
self._bench("bad.epd", "not a valid fen\n")
|
||||
assert self.stockfish.process.returncode != 0
|
||||
assert "CRITICAL ERROR" in self.stockfish.process.stdout
|
||||
|
||||
def test_missing_file(self):
|
||||
self.stockfish = Stockfish(
|
||||
"bench 16 1 4 does_not_exist.epd depth".split(" "), True
|
||||
)
|
||||
assert self.stockfish.process.returncode != 0
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description="Run Stockfish with testing options")
|
||||
@@ -502,16 +699,24 @@ if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
|
||||
EPD.create_bench_epd()
|
||||
TSAN.set_tsan_option()
|
||||
Syzygy.download_syzygy()
|
||||
|
||||
framework = MiniTestFramework()
|
||||
|
||||
# Each test suite will be run inside a temporary directory
|
||||
framework.run([TestCLI, TestInteractive, TestSyzygy])
|
||||
framework.run(
|
||||
[
|
||||
TestCLI,
|
||||
TestInteractive,
|
||||
TestSyzygy,
|
||||
TestEnPassantSanitization,
|
||||
TestInvalidFEN,
|
||||
TestInvalidOptions,
|
||||
TestBenchFile,
|
||||
]
|
||||
)
|
||||
|
||||
EPD.delete_bench_epd()
|
||||
TSAN.unset_tsan_option()
|
||||
|
||||
if framework.has_failed():
|
||||
sys.exit(1)
|
||||
|
||||
+2
-1
@@ -18,7 +18,8 @@ error()
|
||||
trap 'error ${LINENO}' ERR
|
||||
|
||||
# obtain
|
||||
eval "$RUN_PREFIX ./stockfish bench" > "$STDOUT_FILE" 2> "$STDERR_FILE" || error ${LINENO}
|
||||
EXE=${EXE:-./stockfish}
|
||||
eval "$RUN_PREFIX $EXE bench" > "$STDOUT_FILE" 2> "$STDERR_FILE" || error ${LINENO}
|
||||
signature=$(grep "Nodes searched : " "$STDERR_FILE" | awk '{print $4}')
|
||||
|
||||
rm -f "$STDOUT_FILE" "$STDERR_FILE"
|
||||
|
||||
+60
-51
@@ -6,12 +6,12 @@ import time
|
||||
import sys
|
||||
import traceback
|
||||
import fnmatch
|
||||
from functools import wraps
|
||||
from contextlib import redirect_stdout
|
||||
import io
|
||||
import tarfile
|
||||
import pathlib
|
||||
import concurrent.futures
|
||||
import queue
|
||||
import threading
|
||||
import tempfile
|
||||
import shutil
|
||||
import requests
|
||||
@@ -43,27 +43,6 @@ class Valgrind:
|
||||
return ["valgrind", "--error-exitcode=42", "--fair-sched=try"]
|
||||
|
||||
|
||||
class TSAN:
|
||||
@staticmethod
|
||||
def set_tsan_option():
|
||||
with open(f"tsan.supp", "w") as f:
|
||||
f.write(
|
||||
"""
|
||||
race:Stockfish::TTEntry::read
|
||||
race:Stockfish::TTEntry::save
|
||||
race:Stockfish::TranspositionTable::probe
|
||||
race:Stockfish::TranspositionTable::hashfull
|
||||
"""
|
||||
)
|
||||
|
||||
os.environ["TSAN_OPTIONS"] = "suppressions=./tsan.supp"
|
||||
|
||||
@staticmethod
|
||||
def unset_tsan_option():
|
||||
os.environ.pop("TSAN_OPTIONS", None)
|
||||
os.remove(f"tsan.supp")
|
||||
|
||||
|
||||
class EPD:
|
||||
@staticmethod
|
||||
def create_bench_epd():
|
||||
@@ -122,29 +101,15 @@ class OrderedClassMembers(type):
|
||||
|
||||
|
||||
class TimeoutException(Exception):
|
||||
def __init__(self, message: str, timeout: int):
|
||||
def __init__(self, message: str, timeout: float):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.timeout = timeout
|
||||
|
||||
|
||||
def timeout_decorator(timeout: float):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||
future = executor.submit(func, *args, **kwargs)
|
||||
try:
|
||||
result = future.result(timeout=timeout)
|
||||
except concurrent.futures.TimeoutError:
|
||||
raise TimeoutException(
|
||||
f"Function {func.__name__} timed out after {timeout} seconds",
|
||||
timeout,
|
||||
)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
class UnexpectedOutputException(Exception):
|
||||
def __init__(self, actual: str, expected: str):
|
||||
self.actual = actual
|
||||
self.expected = expected
|
||||
|
||||
|
||||
class MiniTestFramework:
|
||||
@@ -230,6 +195,11 @@ class MiniTestFramework:
|
||||
f" {method} (hit execution limit of {e.timeout} seconds)"
|
||||
)
|
||||
|
||||
if isinstance(e, UnexpectedOutputException):
|
||||
self.print_failure(
|
||||
f" {method} encountered unexpected output: \"{e.actual}\" when output matching \"{e.expected}\" was expected"
|
||||
)
|
||||
|
||||
if isinstance(e, AssertionError):
|
||||
self.__handle_assertion_error(t0, method)
|
||||
|
||||
@@ -294,6 +264,8 @@ class Stockfish:
|
||||
self.cli = cli
|
||||
self.prefix = prefix
|
||||
self.output = []
|
||||
self.output_queue = queue.Queue()
|
||||
self.reader_thread = None
|
||||
|
||||
self.start()
|
||||
|
||||
@@ -325,6 +297,21 @@ class Stockfish:
|
||||
universal_newlines=True,
|
||||
bufsize=1,
|
||||
)
|
||||
self.reader_thread = threading.Thread(
|
||||
target=self._read_process_output, daemon=True
|
||||
)
|
||||
self.reader_thread.start()
|
||||
|
||||
def _read_process_output(self):
|
||||
try:
|
||||
for line in self.process.stdout:
|
||||
line = line.strip()
|
||||
self.output.append(line)
|
||||
self.output_queue.put(line)
|
||||
except (OSError, ValueError):
|
||||
pass
|
||||
finally:
|
||||
self.output_queue.put(None)
|
||||
|
||||
def setoption(self, name: str, value: str):
|
||||
self.send_command(f"setoption name {name} value {value}")
|
||||
@@ -338,31 +325,26 @@ class Stockfish:
|
||||
self.process.stdin.write(command + "\n")
|
||||
self.process.stdin.flush()
|
||||
|
||||
@timeout_decorator(MAX_TIMEOUT)
|
||||
def equals(self, expected_output: str):
|
||||
for line in self.readline():
|
||||
if line == expected_output:
|
||||
return
|
||||
|
||||
@timeout_decorator(MAX_TIMEOUT)
|
||||
def expect(self, expected_output: str):
|
||||
for line in self.readline():
|
||||
if fnmatch.fnmatch(line, expected_output):
|
||||
return
|
||||
|
||||
@timeout_decorator(MAX_TIMEOUT)
|
||||
def contains(self, expected_output: str):
|
||||
for line in self.readline():
|
||||
if expected_output in line:
|
||||
return
|
||||
|
||||
@timeout_decorator(MAX_TIMEOUT)
|
||||
def starts_with(self, expected_output: str):
|
||||
for line in self.readline():
|
||||
if line.startswith(expected_output):
|
||||
return
|
||||
|
||||
@timeout_decorator(MAX_TIMEOUT)
|
||||
def check_output(self, callback):
|
||||
if not callback:
|
||||
raise ValueError("Callback function is required")
|
||||
@@ -371,14 +353,41 @@ class Stockfish:
|
||||
if callback(line) == True:
|
||||
return
|
||||
|
||||
def readline(self):
|
||||
def expect_for_line_matching(self, line_match: str, expected: str):
|
||||
for line in self.readline():
|
||||
if fnmatch.fnmatch(line, line_match):
|
||||
if fnmatch.fnmatch(line, expected):
|
||||
break
|
||||
else:
|
||||
raise UnexpectedOutputException(line, expected)
|
||||
|
||||
def readline(self, timeout: float = MAX_TIMEOUT):
|
||||
if not self.process:
|
||||
raise RuntimeError("Stockfish process is not started")
|
||||
|
||||
deadline = time.monotonic() + timeout
|
||||
|
||||
while True:
|
||||
self._check_process_alive()
|
||||
line = self.process.stdout.readline().strip()
|
||||
self.output.append(line)
|
||||
|
||||
remaining_time = deadline - time.monotonic()
|
||||
if remaining_time <= 0:
|
||||
raise TimeoutException(
|
||||
f"No matching output received after {timeout} seconds",
|
||||
timeout,
|
||||
)
|
||||
|
||||
try:
|
||||
line = self.output_queue.get(timeout=remaining_time)
|
||||
except queue.Empty:
|
||||
raise TimeoutException(
|
||||
f"No matching output received after {timeout} seconds",
|
||||
timeout,
|
||||
)
|
||||
|
||||
if line is None:
|
||||
self._check_process_alive()
|
||||
raise RuntimeError("Stockfish process has terminated")
|
||||
|
||||
yield line
|
||||
|
||||
|
||||
Reference in New Issue
Block a user