1
0
Fork 0
mirror of https://github.com/sockspls/badfish synced 2025-06-28 00:19:50 +00:00

Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Kate 2024-05-06 10:35:22 +01:00
commit c8326b9b09
91 changed files with 13248 additions and 12920 deletions

44
.clang-format Normal file
View file

@ -0,0 +1,44 @@
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: Consecutive
AlignConsecutiveDeclarations: Consecutive
AlignEscapedNewlines: DontAlign
AlignOperands: AlignAfterOperator
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortCaseLabelsOnASingleLine: false
AllowShortEnumsOnASingleLine: false
AllowShortIfStatementsOnASingleLine: false
AlwaysBreakTemplateDeclarations: Yes
BasedOnStyle: WebKit
BitFieldColonSpacing: After
BinPackParameters: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BraceWrapping:
AfterFunction: false
AfterClass: false
AfterControlStatement: true
BeforeElse: true
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: AfterColon
BreakStringLiterals: false
ColumnLimit: 100
ContinuationIndentWidth: 2
Cpp11BracedListStyle: true
IndentGotoLabels: false
IndentPPDirectives: BeforeHash
IndentWidth: 4
MaxEmptyLinesToKeep: 2
NamespaceIndentation: None
PackConstructorInitializers: Never
ReflowComments: false
SortIncludes: false
SortUsingDeclarations: false
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeCaseColon: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeInheritanceColon: false
SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 2

7
.git-blame-ignore-revs Normal file
View file

@ -0,0 +1,7 @@
# .git-blame-ignore-revs
# Ignore commit which added clang-format
2d0237db3f0e596fb06e3ffbadba84dcc4e018f6
# Post commit formatting fixes
0fca5605fa2e5e7240fde5e1aae50952b2612231
08ed4c90db31959521b7ef3186c026edd1e90307

65
.github/ISSUE_TEMPLATE/BUG-REPORT.yml vendored Normal file
View file

@ -0,0 +1,65 @@
name: Report issue
description: Create a report to help us fix issues with the engine
body:
- type: textarea
attributes:
label: Describe the issue
description: A clear and concise description of what you're experiencing.
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: |
Steps to reproduce the behavior.
You can also use this section to paste the command line output.
placeholder: |
```
position startpos moves g2g4 e7e5 f2f3
go mate 1
info string NNUE evaluation using nn-6877cd24400e.nnue enabled
info depth 1 seldepth 1 multipv 1 score mate 1 nodes 33 nps 11000 tbhits 0 time 3 pv d8h4
bestmove d8h4
```
validations:
required: true
- type: textarea
attributes:
label: Anything else?
description: |
Anything that will give us more context about the issue you are encountering.
You can also use this section to propose ideas on how to solve the issue.
validations:
required: false
- type: dropdown
attributes:
label: Operating system
options:
- All
- Windows
- Linux
- MacOS
- Android
- Other or N/A
validations:
required: true
- type: input
attributes:
label: Stockfish version
description: |
This can be found by running the engine.
You can also use the commit ID.
placeholder: Stockfish 15 / e6e324e
validations:
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Discord server
url: https://discord.gg/GWDRS3kU6R
about: Feel free to ask for support or have a chat with us on our Discord server!
- name: Discussions, Q&A, ideas, show us something...
url: https://github.com/official-stockfish/Stockfish/discussions/new
about: Do you have an idea for Stockfish? Do you want to show something that you made? Please open a discussion about it!

51
.github/ci/arm_matrix.json vendored Normal file
View file

@ -0,0 +1,51 @@
{
"config": [
{
"name": "Android NDK aarch64",
"os": "ubuntu-22.04",
"simple_name": "android",
"compiler": "aarch64-linux-android21-clang++",
"emu": "qemu-aarch64",
"comp": "ndk",
"shell": "bash",
"archive_ext": "tar"
},
{
"name": "Android NDK arm",
"os": "ubuntu-22.04",
"simple_name": "android",
"compiler": "armv7a-linux-androideabi21-clang++",
"emu": "qemu-arm",
"comp": "ndk",
"shell": "bash",
"archive_ext": "tar"
}
],
"binaries": ["armv8-dotprod", "armv8", "armv7", "armv7-neon"],
"exclude": [
{
"binaries": "armv8-dotprod",
"config": {
"compiler": "armv7a-linux-androideabi21-clang++"
}
},
{
"binaries": "armv8",
"config": {
"compiler": "armv7a-linux-androideabi21-clang++"
}
},
{
"binaries": "armv7",
"config": {
"compiler": "aarch64-linux-android21-clang++"
}
},
{
"binaries": "armv7-neon",
"config": {
"compiler": "aarch64-linux-android21-clang++"
}
}
]
}

21
.github/ci/libcxx17.imp vendored Normal file
View file

@ -0,0 +1,21 @@
[
# Mappings for libcxx's internal headers
{ include: [ "<__fwd/fstream.h>", private, "<iosfwd>", public ] },
{ include: [ "<__fwd/ios.h>", private, "<iosfwd>", public ] },
{ include: [ "<__fwd/istream.h>", private, "<iosfwd>", public ] },
{ include: [ "<__fwd/ostream.h>", private, "<iosfwd>", public ] },
{ include: [ "<__fwd/sstream.h>", private, "<iosfwd>", public ] },
{ include: [ "<__fwd/streambuf.h>", private, "<iosfwd>", public ] },
{ include: [ "<__fwd/string_view.h>", private, "<string_view>", public ] },
# Mappings for includes between public headers
{ include: [ "<ios>", public, "<iostream>", public ] },
{ include: [ "<streambuf>", public, "<iostream>", public ] },
{ include: [ "<istream>", public, "<iostream>", public ] },
{ include: [ "<ostream>", public, "<iostream>", public ] },
{ include: [ "<iosfwd>", public, "<iostream>", public ] },
# Missing mappings in include-what-you-use's libcxx.imp
{ include: ["@<__condition_variable/.*>", private, "<condition_variable>", public ] },
{ include: ["@<__mutex/.*>", private, "<mutex>", public ] },
]

160
.github/ci/matrix.json vendored Normal file
View file

@ -0,0 +1,160 @@
{
"config": [
{
"name": "Ubuntu 20.04 GCC",
"os": "ubuntu-20.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.27.0-2023-09-13-lin/sde -future --"
},
{
"name": "MacOS 13 Apple Clang",
"os": "macos-13",
"simple_name": "macos",
"compiler": "clang++",
"comp": "clang",
"shell": "bash",
"archive_ext": "tar"
},
{
"name": "MacOS 14 Apple Clang M1",
"os": "macos-14",
"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.27.0-2023-09-13-win/sde.exe -future --",
"archive_ext": "zip"
}
],
"binaries": [
"x86-64",
"x86-64-sse41-popcnt",
"x86-64-avx2",
"x86-64-bmi2",
"x86-64-avxvnni",
"x86-64-avx512",
"x86-64-vnni256",
"x86-64-vnni512",
"apple-silicon"
],
"exclude": [
{
"binaries": "x86-64",
"config": {
"os": "macos-14"
}
},
{
"binaries": "x86-64-sse41-popcnt",
"config": {
"os": "macos-14"
}
},
{
"binaries": "x86-64-avx2",
"config": {
"os": "macos-14"
}
},
{
"binaries": "x86-64-bmi2",
"config": {
"os": "macos-14"
}
},
{
"binaries": "x86-64-avxvnni",
"config": {
"os": "macos-14"
}
},
{
"binaries": "x86-64-avxvnni",
"config": {
"os": "macos-14"
}
},
{
"binaries": "x86-64-avx512",
"config": {
"os": "macos-14"
}
},
{
"binaries": "x86-64-vnni256",
"config": {
"os": "macos-14"
}
},
{
"binaries": "x86-64-vnni512",
"config": {
"os": "macos-14"
}
},
{
"binaries": "x86-64-avxvnni",
"config": {
"ubuntu-20.04": null
}
},
{
"binaries": "x86-64-avxvnni",
"config": {
"os": "macos-13"
}
},
{
"binaries": "x86-64-avx512",
"config": {
"os": "macos-13"
}
},
{
"binaries": "x86-64-vnni256",
"config": {
"os": "macos-13"
}
},
{
"binaries": "x86-64-vnni512",
"config": {
"os": "macos-13"
}
},
{
"binaries": "apple-silicon",
"config": {
"os": "windows-2022"
}
},
{
"binaries": "apple-silicon",
"config": {
"os": "macos-13"
}
},
{
"binaries": "apple-silicon",
"config": {
"os": "ubuntu-20.04"
}
}
]
}

94
.github/workflows/arm_compilation.yml vendored Normal file
View file

@ -0,0 +1,94 @@
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 }}
EMU: ${{ matrix.config.emu }}
EXT: ${{ matrix.config.ext }}
BINARY: ${{ matrix.binaries }}
strategy:
fail-fast: false
matrix: ${{ fromJson(inputs.matrix) }}
defaults:
run:
working-directory: src
shell: ${{ matrix.config.shell }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download required linux packages
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install qemu-user
- name: Install NDK
if: runner.os == 'Linux'
run: |
if [ $COMP == ndk ]; then
NDKV="21.4.7075529"
ANDROID_ROOT=/usr/local/lib/android
ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk
SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager
echo "y" | $SDKMANAGER "ndk;$NDKV"
ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV
ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin
echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV
fi
- 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: Check compiler
run: |
if [ $COMP == ndk ]; then
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
fi
$COMPCXX -v
- name: Test help target
run: make help
- name: Check git
run: git --version
# Compile profile guided builds
- name: Compile ${{ matrix.binaries }} build
run: |
if [ $COMP == ndk ]; then
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
fi
make clean
make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU
make strip ARCH=$BINARY COMP=$COMP
WINE_PATH=$EMU ../tests/signature.sh $benchref
mv ./stockfish$EXT ../stockfish-android-$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: .

51
.github/workflows/clang-format.yml vendored Normal file
View file

@ -0,0 +1,51 @@
# This workflow will run clang-format and comment on the PR.
# Because of security reasons, it is crucial that this workflow
# executes no shell script nor runs make.
# Read this before editing: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
name: Clang-Format
on:
pull_request_target:
branches:
- "master"
paths:
- "**.cpp"
- "**.h"
jobs:
Clang-Format:
name: Clang-Format
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Run clang-format style check
uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e # @v4.11.0
id: clang-format
continue-on-error: true
with:
clang-format-version: "17"
exclude-regex: "incbin"
- name: Comment on PR
if: steps.clang-format.outcome == 'failure'
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0
with:
message: |
clang-format 17 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/mantic/clang-format-17.
_(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
comment_tag: execution
- name: Comment on PR
if: steps.clang-format.outcome != 'failure'
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0
with:
message: |
_(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
create_if_not_exists: false
comment_tag: execution
mode: delete

53
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,53 @@
name: "CodeQL"
on:
push:
branches: ["master"]
pull_request:
# The branches below must be a subset of the branches above
branches: ["master"]
schedule:
- cron: "17 18 * * 1"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["cpp"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Use only 'java' to analyze code written in Java, Kotlin, or both
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
- name: Build
working-directory: src
run: make -j build ARCH=x86-64-modern
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

89
.github/workflows/compilation.yml vendored Normal file
View file

@ -0,0 +1,89 @@
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
- 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@91a1a03434384e064706634125a15f7446d2aafb # @v2.3
with:
environmentVariableName: SDE_DIR
sdeVersion: 9.27.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 g++ cpu info
if: runner.os != 'macOS'
run: g++ -Q -march=native --help=target
- name: Show clang++ cpu info
if: runner.os == 'macOS'
run: clang++ -E - -march=native -###
# x86-64 with newer extensions tests
- name: Compile ${{ matrix.config.binaries }} build
run: |
make clean
make -j4 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH="$SDE"
make strip ARCH=$BINARY COMP=$COMP
WINE_PATH="$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: .

47
.github/workflows/iwyu.yml vendored Normal file
View file

@ -0,0 +1,47 @@
name: IWYU
on:
workflow_call:
jobs:
Analyzers:
name: Check includes
runs-on: ubuntu-22.04
defaults:
run:
working-directory: Stockfish/src
shell: bash
steps:
- name: Checkout Stockfish
uses: actions/checkout@v4
with:
path: Stockfish
- name: Checkout include-what-you-use
uses: actions/checkout@v4
with:
repository: include-what-you-use/include-what-you-use
ref: f25caa280dc3277c4086ec345ad279a2463fea0f
path: include-what-you-use
- name: Download required linux packages
run: |
sudo add-apt-repository 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-17 main'
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt update
sudo apt install -y libclang-17-dev clang-17 libc++-17-dev
- name: Set up include-what-you-use
run: |
mkdir build && cd build
cmake -G "Unix Makefiles" -DCMAKE_PREFIX_PATH="/usr/lib/llvm-17" ..
sudo make install
working-directory: include-what-you-use
- name: Check include-what-you-use
run: include-what-you-use --version
- name: Check includes
run: >
make analyze
COMP=clang
CXX=include-what-you-use
CXXFLAGS="-stdlib=libc++ -Xiwyu --comment_style=long -Xiwyu --mapping='${{ github.workspace }}/Stockfish/.github/ci/libcxx17.imp' -Xiwyu --error"

76
.github/workflows/sanitizers.yml vendored Normal file
View file

@ -0,0 +1,76 @@
name: Sanitizers
on:
workflow_call:
jobs:
Test-under-sanitizers:
name: ${{ matrix.sanitizers.name }}
runs-on: ${{ matrix.config.os }}
env:
COMPCXX: ${{ matrix.config.compiler }}
COMP: ${{ matrix.config.comp }}
CXXFLAGS: "-Werror"
strategy:
fail-fast: false
matrix:
config:
- name: Ubuntu 22.04 GCC
os: ubuntu-22.04
compiler: g++
comp: gcc
shell: bash
sanitizers:
- name: Run with thread sanitizer
make_option: sanitize=thread
instrumented_option: sanitizer-thread
- name: Run with UB sanitizer
make_option: sanitize=undefined
instrumented_option: sanitizer-undefined
- name: Run under valgrind
make_option: ""
instrumented_option: valgrind
- name: Run under valgrind-thread
make_option: ""
instrumented_option: valgrind-thread
- name: Run non-instrumented
make_option: ""
instrumented_option: none
defaults:
run:
working-directory: src
shell: ${{ matrix.config.shell }}
steps:
- uses: actions/checkout@v4
- name: Download required linux packages
run: |
sudo apt update
sudo apt install expect valgrind g++-multilib
- 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
# Since Linux Kernel 6.5 we are getting false positives from the ci,
# lower the ALSR entropy to disable ALSR, which works as a temporary workaround.
# https://github.com/google/sanitizers/issues/1716
# https://bugs.launchpad.net/ubuntu/+source/linux/+bug/2056762
- name: Lower ALSR entropy
run: sudo sysctl -w vm.mmap_rnd_bits=28
# Sanitizers
- name: ${{ matrix.sanitizers.name }}
run: |
export CXXFLAGS="-O1 -fno-inline"
make clean
make -j4 ARCH=x86-64-sse41-popcnt ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null
../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }}

View file

@ -1,333 +1,104 @@
name: Stockfish
on:
push:
tags:
- "*"
branches:
- master
- tools
- github_ci
- github_ci_armv7
pull_request:
branches:
- master
- tools
jobs:
Stockfish:
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
env:
COMPILER: ${{ matrix.config.compiler }}
COMP: ${{ matrix.config.comp }}
CXXFLAGS: "-Werror"
strategy:
matrix:
config:
# set the variable for the required tests:
# run_expensive_tests: true
# run_32bit_tests: true
# run_64bit_tests: true
# run_armv8_tests: true
# run_armv7_tests: true
- {
name: "Ubuntu 20.04 GCC",
os: ubuntu-20.04,
compiler: g++,
comp: gcc,
run_expensive_tests: true,
run_32bit_tests: true,
run_64bit_tests: true,
shell: 'bash {0}'
}
- {
name: "Ubuntu 20.04 Clang",
os: ubuntu-20.04,
compiler: clang++,
comp: clang,
run_32bit_tests: true,
run_64bit_tests: true,
shell: 'bash {0}'
}
- {
name: "Ubuntu 20.04 NDK armv8",
os: ubuntu-20.04,
compiler: aarch64-linux-android21-clang++,
comp: ndk,
run_armv8_tests: true,
shell: 'bash {0}'
}
- {
name: "Ubuntu 20.04 NDK armv7",
os: ubuntu-20.04,
compiler: armv7a-linux-androideabi21-clang++,
comp: ndk,
run_armv7_tests: true,
shell: 'bash {0}'
}
- {
name: "MacOS 10.15 Apple Clang",
os: macos-10.15,
compiler: clang++,
comp: clang,
run_64bit_tests: true,
shell: 'bash {0}'
}
- {
name: "MacOS 10.15 GCC 10",
os: macos-10.15,
compiler: g++-10,
comp: gcc,
run_64bit_tests: true,
shell: 'bash {0}'
}
- {
name: "Windows 2022 Mingw-w64 GCC x86_64",
os: windows-2022,
compiler: g++,
comp: gcc,
run_64bit_tests: true,
msys_sys: 'mingw64',
msys_env: 'x86_64',
shell: 'msys2 {0}'
}
- {
name: "Windows 2022 Mingw-w64 GCC i686",
os: windows-2022,
compiler: g++,
comp: gcc,
run_32bit_tests: true,
msys_sys: 'mingw32',
msys_env: 'i686',
shell: 'msys2 {0}'
}
- {
name: "Windows 2022 Mingw-w64 Clang x86_64",
os: windows-2022,
compiler: clang++,
comp: clang,
run_64bit_tests: true,
msys_sys: 'clang64',
msys_env: 'clang-x86_64',
shell: 'msys2 {0}'
}
defaults:
run:
working-directory: src
shell: ${{ matrix.config.shell }}
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
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
# returns null if no pre-release exists
- name: Get Commit SHA of Latest Pre-release
run: |
# Install required packages
sudo apt-get update
sudo apt-get install -y curl jq
echo "COMMIT_SHA_TAG=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV
# delete old previous pre-release and tag
- run: gh release delete ${{ env.COMMIT_SHA_TAG }} --cleanup-tag
if: env.COMMIT_SHA_TAG != 'null'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Make sure that an old ci that still runs on master doesn't recreate a prerelease
- name: Check Pullable Commits
id: check_commits
run: |
git fetch
CHANGES=$(git rev-list HEAD..origin/master --count)
echo "CHANGES=$CHANGES" >> $GITHUB_ENV
- name: Get last commit SHA
id: last_commit
run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV
- name: Get commit date
id: commit_date
run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV
# 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
with:
fetch-depth: 0
name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
prerelease: true
- name: Download required linux packages
if: runner.os == 'Linux'
Matrix:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
arm_matrix: ${{ steps.set-arm-matrix.outputs.arm_matrix }}
steps:
- uses: actions/checkout@v4
- id: set-matrix
run: |
sudo apt update
sudo apt install expect valgrind g++-multilib qemu-user
- 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}}-${{matrix.config.comp}} make git expect
- name: Download the used network from the fishtest framework
TASKS=$(echo $(cat .github/ci/matrix.json) )
echo "MATRIX=$TASKS" >> $GITHUB_OUTPUT
- id: set-arm-matrix
run: |
make net
- name: Extract the bench number from the commit history
run: |
git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig
[ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found"
- name: Check compiler
run: |
export PATH=$PATH:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin
$COMPILER -v
- name: Test help target
run: |
make help
# x86-32 tests
- name: Test debug x86-32 build
if: ${{ matrix.config.run_32bit_tests }}
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
make clean
make -j2 ARCH=x86-32 optimize=no debug=yes build
../tests/signature.sh $benchref
- name: Test x86-32 build
if: ${{ matrix.config.run_32bit_tests }}
run: |
make clean
make -j2 ARCH=x86-32 build
../tests/signature.sh $benchref
- name: Test x86-32-sse41-popcnt build
if: ${{ matrix.config.run_32bit_tests }}
run: |
make clean
make -j2 ARCH=x86-32-sse41-popcnt build
../tests/signature.sh $benchref
- name: Test x86-32-sse2 build
if: ${{ matrix.config.run_32bit_tests }}
run: |
make clean
make -j2 ARCH=x86-32-sse2 build
../tests/signature.sh $benchref
- name: Test general-32 build
if: ${{ matrix.config.run_32bit_tests }}
run: |
make clean
make -j2 ARCH=general-32 build
../tests/signature.sh $benchref
# x86-64 tests
- name: Test debug x86-64-modern build
if: ${{ matrix.config.run_64bit_tests }}
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
make clean
make -j2 ARCH=x86-64-modern optimize=no debug=yes build
../tests/signature.sh $benchref
- name: Test x86-64-modern build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-modern build
../tests/signature.sh $benchref
- name: Test x86-64-ssse3 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-ssse3 build
../tests/signature.sh $benchref
- name: Test x86-64-sse3-popcnt build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-sse3-popcnt build
../tests/signature.sh $benchref
- name: Test x86-64 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64 build
../tests/signature.sh $benchref
- name: Test general-64 build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j2 ARCH=general-64 build
../tests/signature.sh $benchref
# x86-64 with newer extensions tests
- name: Compile x86-64-avx2 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-avx2 build
- name: Compile x86-64-bmi2 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-bmi2 build
- name: Compile x86-64-avx512 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-avx512 build
- name: Compile x86-64-vnni512 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-vnni512 build
- name: Compile x86-64-vnni256 build
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-vnni256 build
# armv8 tests
- name: Test armv8 build
if: ${{ matrix.config.run_armv8_tests }}
run: |
export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j2 ARCH=armv8 build
../tests/signature.sh $benchref
# armv7 tests
- name: Test armv7 build
if: ${{ matrix.config.run_armv7_tests }}
run: |
export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j2 ARCH=armv7 build
../tests/signature.sh $benchref
- name: Test armv7-neon build
if: ${{ matrix.config.run_armv7_tests }}
run: |
export PATH=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j2 ARCH=armv7-neon build
../tests/signature.sh $benchref
# Other tests
- name: Check perft and search reproducibility
if: ${{ matrix.config.run_64bit_tests }}
run: |
make clean
make -j2 ARCH=x86-64-modern build
../tests/perft.sh
../tests/reprosearch.sh
# Sanitizers
- name: Run under valgrind
if: ${{ matrix.config.run_expensive_tests }}
run: |
export CXXFLAGS="-O1 -fno-inline"
make clean
make -j2 ARCH=x86-64-modern debug=yes optimize=no build > /dev/null
../tests/instrumented.sh --valgrind
../tests/instrumented.sh --valgrind-thread
- name: Run with UB sanitizer
if: ${{ matrix.config.run_expensive_tests }}
run: |
export CXXFLAGS="-O1 -fno-inline"
make clean
make -j2 ARCH=x86-64-modern sanitize=undefined optimize=no debug=yes build > /dev/null
../tests/instrumented.sh --sanitizer-undefined
- name: Run with thread sanitizer
if: ${{ matrix.config.run_expensive_tests }}
run: |
export CXXFLAGS="-O1 -fno-inline"
make clean
make -j2 ARCH=x86-64-modern sanitize=thread optimize=no debug=yes build > /dev/null
../tests/instrumented.sh --sanitizer-thread
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 }}
ARMCompilation:
needs: [Matrix]
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
Binaries:
if: github.repository == 'official-stockfish/Stockfish'
needs: [Matrix, Prerelease, Compilation]
uses: ./.github/workflows/upload_binaries.yml
with:
matrix: ${{ needs.Matrix.outputs.matrix }}
ARM_Binaries:
if: github.repository == 'official-stockfish/Stockfish'
needs: [Matrix, Prerelease, ARMCompilation]
uses: ./.github/workflows/upload_binaries.yml
with:
matrix: ${{ needs.Matrix.outputs.arm_matrix }}

365
.github/workflows/tests.yml vendored Normal file
View file

@ -0,0 +1,365 @@
name: Tests
on:
workflow_call:
jobs:
Test-Targets:
name: ${{ matrix.config.name }}
runs-on: ${{ matrix.config.os }}
env:
COMPCXX: ${{ matrix.config.compiler }}
COMP: ${{ matrix.config.comp }}
CXXFLAGS: "-Werror"
strategy:
fail-fast: false
matrix:
config:
- name: Ubuntu 20.04 GCC
os: ubuntu-20.04
compiler: g++
comp: gcc
run_32bit_tests: true
run_64bit_tests: true
shell: bash
- name: Ubuntu 20.04 Clang
os: ubuntu-20.04
compiler: clang++
comp: clang
run_32bit_tests: true
run_64bit_tests: true
shell: bash
- name: Android NDK aarch64
os: ubuntu-22.04
compiler: aarch64-linux-android21-clang++
comp: ndk
run_armv8_tests: true
shell: bash
- name: Android NDK arm
os: ubuntu-22.04
compiler: armv7a-linux-androideabi21-clang++
comp: ndk
run_armv7_tests: true
shell: bash
- name: Linux GCC riscv64
os: ubuntu-22.04
compiler: g++
comp: gcc
run_riscv64_tests: true
base_image: "riscv64/alpine:edge"
platform: linux/riscv64
shell: bash
- name: Linux GCC ppc64
os: ubuntu-22.04
compiler: g++
comp: gcc
run_ppc64_tests: true
base_image: "ppc64le/alpine:latest"
platform: linux/ppc64le
shell: bash
- name: MacOS 13 Apple Clang
os: macos-13
compiler: clang++
comp: clang
run_64bit_tests: true
shell: bash
- name: MacOS 14 Apple Clang M1
os: macos-14
compiler: clang++
comp: clang
run_64bit_tests: false
run_m1_tests: true
shell: bash
- name: MacOS 13 GCC 11
os: macos-13
compiler: g++-11
comp: gcc
run_64bit_tests: true
shell: bash
- name: Windows 2022 Mingw-w64 GCC x86_64
os: windows-2022
compiler: g++
comp: mingw
run_64bit_tests: true
msys_sys: mingw64
msys_env: x86_64-gcc
shell: msys2 {0}
- name: Windows 2022 Mingw-w64 GCC i686
os: windows-2022
compiler: g++
comp: mingw
run_32bit_tests: true
msys_sys: mingw32
msys_env: i686-gcc
shell: msys2 {0}
- name: Windows 2022 Mingw-w64 Clang x86_64
os: windows-2022
compiler: clang++
comp: clang
run_64bit_tests: true
msys_sys: clang64
msys_env: clang-x86_64-clang
shell: msys2 {0}
defaults:
run:
working-directory: src
shell: ${{ matrix.config.shell }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download required linux packages
if: runner.os == 'Linux'
run: |
sudo apt update
sudo apt install expect valgrind g++-multilib qemu-user-static
- name: Install NDK
if: runner.os == 'Linux'
run: |
if [ $COMP == ndk ]; then
NDKV="21.4.7075529"
ANDROID_ROOT=/usr/local/lib/android
ANDROID_SDK_ROOT=$ANDROID_ROOT/sdk
SDKMANAGER=$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager
echo "y" | $SDKMANAGER "ndk;$NDKV"
ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$NDKV
ANDROID_NDK_BIN=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/linux-x86_64/bin
echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV
fi
- name: Set up QEMU
if: matrix.config.base_image
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
if: matrix.config.base_image
uses: docker/setup-buildx-action@v3
- name: Build Docker container
if: matrix.config.base_image
run: |
docker buildx build --load -t sf_builder - << EOF
FROM ${{ matrix.config.base_image }}
WORKDIR /app
RUN apk update && apk add make g++
CMD ["sh", "script.sh"]
EOF
- name: Download required macOS packages
if: runner.os == 'macOS'
run: brew install coreutils
- 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 expect
- name: Download the used network from the fishtest framework
run: make net
- 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 compiler
run: |
if [ -z "${{ matrix.config.base_image }}" ]; then
if [ $COMP == ndk ]; then
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
fi
$COMPCXX -v
else
echo "$COMPCXX -v" > script.sh
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder
fi
- name: Test help target
run: make help
- name: Check git
run: git --version
# x86-32 tests
- name: Test debug x86-32 build
if: matrix.config.run_32bit_tests
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
make clean
make -j4 ARCH=x86-32 optimize=no debug=yes build
../tests/signature.sh $benchref
- name: Test x86-32 build
if: matrix.config.run_32bit_tests
run: |
make clean
make -j4 ARCH=x86-32 build
../tests/signature.sh $benchref
- name: Test x86-32-sse41-popcnt build
if: matrix.config.run_32bit_tests
run: |
make clean
make -j4 ARCH=x86-32-sse41-popcnt build
../tests/signature.sh $benchref
- name: Test x86-32-sse2 build
if: matrix.config.run_32bit_tests
run: |
make clean
make -j4 ARCH=x86-32-sse2 build
../tests/signature.sh $benchref
- name: Test general-32 build
if: matrix.config.run_32bit_tests
run: |
make clean
make -j4 ARCH=general-32 build
../tests/signature.sh $benchref
# x86-64 tests
- name: Test debug x86-64-avx2 build
if: matrix.config.run_64bit_tests
run: |
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
make clean
make -j4 ARCH=x86-64-avx2 optimize=no debug=yes build
../tests/signature.sh $benchref
- name: Test x86-64-bmi2 build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j4 ARCH=x86-64-bmi2 build
../tests/signature.sh $benchref
- name: Test x86-64-avx2 build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j4 ARCH=x86-64-avx2 build
../tests/signature.sh $benchref
# Test a deprecated arch
- name: Test x86-64-modern build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j4 ARCH=x86-64-modern build
../tests/signature.sh $benchref
- name: Test x86-64-sse41-popcnt build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j4 ARCH=x86-64-sse41-popcnt build
../tests/signature.sh $benchref
- name: Test x86-64-ssse3 build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j4 ARCH=x86-64-ssse3 build
../tests/signature.sh $benchref
- name: Test x86-64-sse3-popcnt build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j4 ARCH=x86-64-sse3-popcnt build
../tests/signature.sh $benchref
- name: Test x86-64 build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j4 ARCH=x86-64 build
../tests/signature.sh $benchref
- name: Test general-64 build
if: matrix.config.run_64bit_tests
run: |
make clean
make -j4 ARCH=general-64 build
../tests/signature.sh $benchref
- name: Test apple-silicon build
if: matrix.config.run_m1_tests
run: |
make clean
make -j4 ARCH=apple-silicon build
../tests/signature.sh $benchref
# armv8 tests
- name: Test armv8 build
if: matrix.config.run_armv8_tests
run: |
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j4 ARCH=armv8 build
../tests/signature.sh $benchref
- name: Test armv8-dotprod build
if: matrix.config.run_armv8_tests
run: |
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j4 ARCH=armv8-dotprod build
../tests/signature.sh $benchref
# armv7 tests
- name: Test armv7 build
if: matrix.config.run_armv7_tests
run: |
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j4 ARCH=armv7 build
../tests/signature.sh $benchref
- name: Test armv7-neon build
if: matrix.config.run_armv7_tests
run: |
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
export LDFLAGS="-static -Wno-unused-command-line-argument"
make clean
make -j4 ARCH=armv7-neon build
../tests/signature.sh $benchref
# riscv64 tests
- name: Test riscv64 build
if: matrix.config.run_riscv64_tests
run: |
echo "export LDFLAGS='-static' && make clean && make -j4 ARCH=riscv64 build" > script.sh
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder
../tests/signature.sh $benchref
# ppc64 tests
- name: Test ppc64 build
if: matrix.config.run_ppc64_tests
run: |
echo "export LDFLAGS='-static' && make clean && make -j4 ARCH=ppc-64 build" > script.sh
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder
../tests/signature.sh $benchref
# Other tests
- name: Check perft and search reproducibility
if: matrix.config.run_64bit_tests
run: |
make clean
make -j4 ARCH=x86-64-avx2 build
../tests/perft.sh
../tests/reprosearch.sh

106
.github/workflows/upload_binaries.yml vendored Normal file
View file

@ -0,0 +1,106 @@
name: Upload Binaries
on:
workflow_call:
inputs:
matrix:
type: string
required: true
jobs:
Artifacts:
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:
shell: ${{ matrix.config.shell }}
steps:
- uses: actions/checkout@v4
- name: Download artifact from compilation
uses: actions/download-artifact@v4
with:
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
path: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
- name: Setup msys and install required packages
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.config.msys_sys }}
install: mingw-w64-${{ matrix.config.msys_env }} make git zip
- name: Create Package
run: |
mkdir stockfish
- name: Download wiki
run: |
git clone https://github.com/official-stockfish/Stockfish.wiki.git wiki
rm -rf wiki/.git
mv wiki stockfish/
- name: Copy files
run: |
mv "${{ matrix.config.simple_name }} ${{ matrix.binaries }}" stockfish-workflow
cd stockfish-workflow
cp -r src ../stockfish/
cp stockfish-$NAME-$BINARY$EXT ../stockfish/
cp "Top CPU Contributors.txt" ../stockfish/
cp Copying.txt ../stockfish/
cp AUTHORS ../stockfish/
cp CITATION.cff ../stockfish/
cp README.md ../stockfish/
cp CONTRIBUTING.md ../stockfish/
- name: Create tar
if: runner.os != 'Windows'
run: |
chmod +x ./stockfish/stockfish-$NAME-$BINARY$EXT
tar -cvf stockfish-$NAME-$BINARY.tar stockfish
- name: Create zip
if: runner.os == 'Windows'
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
with:
files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}
- name: Get last commit sha
id: last_commit
run: echo "COMMIT_SHA=$(git rev-parse HEAD | cut -c 1-8)" >> $GITHUB_ENV
- name: Get commit date
id: commit_date
run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV
# Make sure that an old ci that still runs on master doesn't recreate a prerelease
- name: Check Pullable Commits
id: check_commits
run: |
git fetch
CHANGES=$(git rev-list HEAD..origin/master --count)
echo "CHANGES=$CHANGES" >> $GITHUB_ENV
- name: Prerelease
if: github.ref_name == 'master' && env.CHANGES == '0'
continue-on-error: true
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
with:
name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
prerelease: true
files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}

85
AUTHORS
View file

@ -1,19 +1,18 @@
# List of authors for Stockfish
# Founders of the Stockfish project and fishtest infrastructure
# Founders of the Stockfish project and Fishtest infrastructure
Tord Romstad (romstad)
Marco Costalba (mcostalba)
Joona Kiiski (zamar)
Gary Linscott (glinscott)
# Authors and inventors of NNUE, training, NNUE port
# Authors and inventors of NNUE, training, and NNUE port
Yu Nasu (ynasu87)
Motohiro Isozaki (yaneurao)
Hisayori Noda (nodchip)
# all other authors of the code in alphabetical order
# All other authors of Stockfish code (in alphabetical order)
Aditya (absimaldata)
Adrian Petrescu (apetresc)
Ahmed Kerimov (wcdbmv)
Ajith Chandy Jose (ajithcj)
Alain Savard (Rocky640)
Alayan Feh (Alayan-stk-2)
@ -21,6 +20,7 @@ Alexander Kure
Alexander Pagel (Lolligerhans)
Alfredo Menezes (lonfom169)
Ali AlZhrani (Cooffe)
Andreas Matthies (Matthies)
Andrei Vetrov (proukornew)
Andrew Grant (AndyGrant)
Andrey Neporada (nepal)
@ -30,22 +30,33 @@ Aram Tumanian (atumanian)
Arjun Temurnikar
Artem Solopiy (EntityFX)
Auguste Pop
Balazs Szilagyi
Balint Pfliegel
Ben Chaney (Chaneybenjamini)
Ben Koshy (BKSpurgeon)
Bill Henry (VoyagerOne)
Bojun Guo (noobpwnftw, Nooby)
borg323
Boštjan Mejak (PedanticHacker)
braich
Brian Sheppard (SapphireBrand, briansheppard-toast)
Bruno de Melo Costa (BM123499)
Bruno Pellanda (pellanda)
Bryan Cross (crossbr)
candirufish
Chess13234
Chris Cain (ceebo)
Ciekce
clefrks
Clemens L. (rn5f107s2)
Cody Ho (aesrentai)
Dale Weiler (graphitemaster)
Dan Schmidt (dfannius)
Daniel Axtens (daxtens)
Daniel Dugovic (ddugovic)
Daniel Monroe (Ergodice)
Dan Schmidt (dfannius)
Dariusz Orzechowski (dorzechowski)
David (dav1312)
David Zar
Daylen Yang (daylen)
Deshawn Mohan-Smith (GoldenRare)
@ -54,55 +65,62 @@ DiscanX
Dominik Schlösser (domschl)
double-beep
Douglas Matos Gomes (dsmsgms)
Dubslow
Eduardo Cáceres (eduherminio)
Eelco de Groot (KingDefender)
Elvin Liu (solarlight2)
erbsenzaehler
Ernesto Gatti
Linmiao Xu (linrock)
Fabian Beuke (madnight)
Fabian Fichter (ianfab)
Fanael Linithien (Fanael)
fanon
Fauzi Akram Dabat (FauziAkram)
Fauzi Akram Dabat (fauzi2)
Felix Wittmann
gamander
Gabriele Lombardo (gabe)
Gahtan Nahdi
Gary Heckman (gheckman)
George Sobala (gsobala)
gguliash
Giacomo Lorenzetti (G-Lorenz)
Gian-Carlo Pascutto (gcp)
Goh CJ (cj5716)
Gontran Lemaire (gonlem)
Goodkov Vasiliy Aleksandrovich (goodkov)
Gregor Cramer
GuardianRM
Günther Demetz (pb00067, pb00068)
Guy Vreuls (gvreuls)
Günther Demetz (pb00067, pb00068)
Henri Wiechers
Hiraoka Takuya (HiraokaTakuya)
homoSapiensSapiens
Hongzhi Cheng
Ivan Ivec (IIvec)
Jacques B. (Timshel)
Jake Senne (w1wwwwww)
Jan Ondruš (hxim)
Jared Kish (Kurtbusch)
Jared Kish (Kurtbusch, kurt22i)
Jarrod Torriero (DU-jdto)
Jean Gauthier (OuaisBla)
Jasper Shovelton (Beanie496)
Jean-Francois Romang (jromang)
Jean Gauthier (OuaisBla)
Jekaa
Jerry Donald Watson (jerrydonaldwatson)
jjoshua2
Jonathan Calovski (Mysseno)
Jonathan Buladas Dumale (SFisGOD)
Jonathan Calovski (Mysseno)
Jonathan McDermid (jonathanmcdermid)
Joost VandeVondele (vondele)
Jörg Oster (joergoster)
Joseph Ellis (jhellis3)
Joseph R. Prostko
Jörg Oster (joergoster)
Julian Willemer (NightlyKing)
jundery
Justin Blanchard (UncombedCoconut)
Kelly Wilson
Ken Takusagawa
Kian E (KJE-98)
kinderchocolate
Kiran Panditrao (Krgp)
Kojirion
@ -110,6 +128,7 @@ Krystian Kuzniarek (kuzkry)
Leonardo Ljubičić (ICCF World Champion)
Leonid Pechenik (lp--)
Liam Keegan (lkeegan)
Linmiao Xu (linrock)
Linus Arver (listx)
loco-loco
Lub van den Berg (ElbertoOne)
@ -123,6 +142,8 @@ marotear
Matt Ginsberg (mattginsberg)
Matthew Lai (matthewlai)
Matthew Sullivan (Matt14916)
Max A. (Disservin)
Maxim Masiutin (maximmasiutin)
Maxim Molchanov (Maxim)
Michael An (man)
Michael Byrne (MichaelB7)
@ -137,34 +158,40 @@ Mira
Miroslav Fontán (Hexik)
Moez Jellouli (MJZ1977)
Mohammed Li (tthsqe12)
Muzhen J (XInTheDark)
Nathan Rugg (nmrugg)
Nick Pelling (nickpelling)
Nguyen Pham (nguyenpham)
Nicklas Persson (NicklasPersson)
Nick Pelling (nickpelling)
Niklas Fiekas (niklasf)
Nikolay Kostov (NikolayIT)
Nguyen Pham (nguyenpham)
Norman Schmidt (FireFather)
notruck
Ofek Shochat (OfekShochat, ghostway)
Ondrej Mosnáček (WOnder93)
Ondřej Mišina (AndrovT)
Oskar Werkelin Ahlin
Pablo Vazquez
Panthee
Pascal Romaret
Pasquale Pigazzini (ppigazzini)
Patrick Jansen (mibere)
pellanda
Peter Schneider (pschneider1968)
Peter Zsifkovits (CoffeeOne)
PikaCat
Praveen Kumar Tummala (praveentml)
Prokop Randáček (ProkopRandacek)
Rahul Dsilva (silversolver1)
Ralph Stößer (Ralph Stoesser)
Raminder Singh
renouve
Reuven Peleg
Richard Lloyd
Reuven Peleg (R-Peleg)
Richard Lloyd (Richard-Lloyd)
Robert Nürnberg (robertnurnberg)
Rodrigo Exterckötter Tjäder
Ron Britvich (Britvich)
Rodrigo Roim (roim)
Ronald de Man (syzygy1, syzygy)
Ron Britvich (Britvich)
rqs
Rui Coelho (ruicoelhopedro)
Ryan Schmitt
@ -175,20 +202,28 @@ Sergei Antonov (saproj)
Sergei Ivanov (svivanov72)
Sergio Vieri (sergiovieri)
sf-x
Shahin M. Shahin (peregrine)
Shane Booth (shane31)
Shawn Varghese (xXH4CKST3RXx)
Shawn Xu (xu-shawn)
Siad Daboul (Topologist)
Stefan Geschwentner (locutus2)
Stefano Cardanobile (Stefano80)
Stefano Di Martino (StefanoD)
Steinar Gunderson (sesse)
Stéphane Nicolet (snicolet)
Prokop Randáček (ProkopRandacek)
Stephen Touset (stouset)
Syine Mineta (MinetaS)
Taras Vuk (TarasVuk)
Thanar2
thaspel
theo77186
TierynnB
Ting-Hsuan Huang (fffelix-huang)
Tobias Steinmann
Tomasz Sobczyk (Sopel97)
Tom Truscott
Tom Vijlbrief (tomtor)
Tomasz Sobczyk (Sopel97)
Torsten Franz (torfranz, tfranzer)
Torsten Hellwig (Torom)
Tracey Emery (basepr1me)
@ -196,11 +231,13 @@ tttak
Unai Corzo (unaiic)
Uri Blass (uriblass)
Vince Negri (cuddlestmonkey)
Viren
windfishballad
xefoci7612
Xiang Wang (KatyushaScarlet)
zz4032
# Additionally, we acknowledge the authors and maintainers of fishtest,
# an amazing and essential framework for the development of Stockfish!
# an amazing and essential framework for Stockfish development!
#
# https://github.com/glinscott/fishtest/blob/master/AUTHORS
# https://github.com/official-stockfish/fishtest/blob/master/AUTHORS

23
CITATION.cff Normal file
View file

@ -0,0 +1,23 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: Stockfish
message: >-
Please cite this software using the metadata from this
file.
type: software
authors:
- name: The Stockfish developers (see AUTHORS file)
repository-code: 'https://github.com/official-stockfish/Stockfish'
url: 'https://stockfishchess.org/'
repository-artifact: 'https://stockfishchess.org/download/'
abstract: Stockfish is a free and strong UCI chess engine.
keywords:
- chess
- artificial intelligence (AI)
- tree search
- alpha-beta search
- neural networks (NN)
- efficiently updatable neural networks (NNUE)
license: GPL-3.0

97
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,97 @@
# Contributing to Stockfish
Welcome to the Stockfish project! We are excited that you are interested in
contributing. This document outlines the guidelines and steps to follow when
making contributions to Stockfish.
## Table of Contents
- [Building Stockfish](#building-stockfish)
- [Making Contributions](#making-contributions)
- [Reporting Issues](#reporting-issues)
- [Submitting Pull Requests](#submitting-pull-requests)
- [Code Style](#code-style)
- [Community and Communication](#community-and-communication)
- [License](#license)
## Building Stockfish
In case you do not have a C++ compiler installed, you can follow the
instructions from our wiki.
- [Ubuntu][ubuntu-compiling-link]
- [Windows][windows-compiling-link]
- [macOS][macos-compiling-link]
## Making Contributions
### Reporting Issues
If you find a bug, please open an issue on the
[issue tracker][issue-tracker-link]. Be sure to include relevant information
like your operating system, build environment, and a detailed description of the
problem.
_Please note that Stockfish's development is not focused on adding new features.
Thus any issue regarding missing features will potentially be closed without
further discussion._
### Submitting Pull Requests
- Functional changes need to be tested on fishtest. See
[Creating my First Test][creating-my-first-test] for more details.
The accompanying pull request should include a link to the test results and
the new bench.
- Non-functional changes (e.g. refactoring, code style, documentation) do not
need to be tested on fishtest, unless they might impact performance.
- Provide a clear and concise description of the changes in the pull request
description.
_First time contributors should add their name to [AUTHORS](../AUTHORS)._
_Stockfish's development is not focused on adding new features. Thus any pull
request introducing new features will potentially be closed without further
discussion._
## Code Style
Changes to Stockfish C++ code should respect our coding style defined by
[.clang-format](.clang-format). You can format your changes by running
`make format`. This requires clang-format version 17 to be installed on your system.
## Navigate
For experienced Git users who frequently use git blame, it is recommended to
configure the blame.ignoreRevsFile setting.
This setting is useful for excluding noisy formatting commits.
```bash
git config blame.ignoreRevsFile .git-blame-ignore-revs
```
## Community and Communication
- Join the [Stockfish discord][discord-link] to discuss ideas, issues, and
development.
- Participate in the [Stockfish GitHub discussions][discussions-link] for
broader conversations.
## License
By contributing to Stockfish, you agree that your contributions will be licensed
under the GNU General Public License v3.0. See [Copying.txt][copying-link] for
more details.
Thank you for contributing to Stockfish and helping us make it even better!
[copying-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt
[discord-link]: https://discord.gg/GWDRS3kU6R
[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new
[creating-my-first-test]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test#create-your-test
[issue-tracker-link]: https://github.com/official-stockfish/Stockfish/issues
[ubuntu-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-1
[windows-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler
[macos-compiling-link]: https://github.com/official-stockfish/Stockfish/wiki/Developers#user-content-installing-a-compiler-2

File diff suppressed because it is too large Load diff

421
README.md
View file

@ -1,333 +1,156 @@
Modified stockfish to play the worst move. Play against the bot at https://lichess.org/@/KateFish
<div align="center">
[![Stockfish][stockfish128-logo]][website-link]
<h3>Stockfish</h3>
A free and strong UCI chess engine.
<br>
<strong>[Explore Stockfish docs »][wiki-link]</strong>
<br>
<br>
[Report bug][issue-link]
·
[Open a discussion][discussions-link]
·
[Discord][discord-link]
·
[Blog][website-blog-link]
[![Build][build-badge]][build-link]
[![License][license-badge]][license-link]
<br>
[![Release][release-badge]][release-link]
[![Commits][commits-badge]][commits-link]
<br>
[![Website][website-badge]][website-link]
[![Fishtest][fishtest-badge]][fishtest-link]
[![Discord][discord-badge]][discord-link]
</div>
## Overview
[![Build Status](https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml/badge.svg)](https://github.com/official-stockfish/Stockfish/actions)
[![Build Status](https://ci.appveyor.com/api/projects/status/github/official-stockfish/Stockfish?branch=master&svg=true)](https://ci.appveyor.com/project/mcostalba/stockfish/branch/master)
[Stockfish][website-link] is a **free and strong UCI chess engine** derived from
Glaurung 2.1 that analyzes chess positions and computes the optimal moves.
[Stockfish](https://stockfishchess.org) is a free, powerful UCI chess engine
derived from Glaurung 2.1. Stockfish is not a complete chess program and requires a
UCI-compatible graphical user interface (GUI) (e.g. XBoard with PolyGlot, Scid,
Cute Chess, eboard, Arena, Sigma Chess, Shredder, Chess Partner or Fritz) in order
to be used comfortably. Read the documentation for your GUI of choice for information
about how to use Stockfish with it.
The Stockfish engine features two evaluation functions for chess, the classical
evaluation based on handcrafted terms, and the NNUE evaluation based on efficiently
updatable neural networks. The classical evaluation runs efficiently on almost all
CPU architectures, while the NNUE evaluation benefits from the vector
intrinsics available on most CPUs (sse2, avx2, neon, or similar).
Stockfish **does not include a graphical user interface** (GUI) that is required
to display a chessboard and to make it easy to input moves. These GUIs are
developed independently from Stockfish and are available online. **Read the
documentation for your GUI** of choice for information about how to use
Stockfish with it.
See also the Stockfish [documentation][wiki-usage-link] for further usage help.
## Files
This distribution of Stockfish consists of the following files:
* [Readme.md](https://github.com/official-stockfish/Stockfish/blob/master/README.md), the file you are currently reading.
* [README.md][readme-link], the file you are currently reading.
* [Copying.txt](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt), a text file containing the GNU General Public License version 3.
* [Copying.txt][license-link], a text file containing the GNU General Public
License version 3.
* [AUTHORS](https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS), a text file with the list of authors for the project
* [AUTHORS][authors-link], a text file with the list of authors for the project.
* [src](https://github.com/official-stockfish/Stockfish/tree/master/src), a subdirectory containing the full source code, including a Makefile
that can be used to compile Stockfish on Unix-like systems.
* [src][src-link], a subdirectory containing the full source code, including a
Makefile that can be used to compile Stockfish on Unix-like systems.
* a file with the .nnue extension, storing the neural network for the NNUE
evaluation. Binary distributions will have this file embedded.
## The UCI protocol and available options
## Contributing
The Universal Chess Interface (UCI) is a standard protocol used to communicate with
a chess engine, and is the recommended way to do so for typical graphical user interfaces
(GUI) or chess tools. Stockfish implements the majority of it options as described
in [the UCI protocol](https://www.shredderchess.com/download/div/uci.zip).
Developers can see the default values for UCI options available in Stockfish by typing
`./stockfish uci` in a terminal, but the majority of users will typically see them and
change them via a chess GUI. This is a list of available UCI options in Stockfish:
* #### Threads
The number of CPU threads used for searching a position. For best performance, set
this equal to the number of CPU cores available.
* #### Hash
The size of the hash table in MB. It is recommended to set Hash after setting Threads.
* #### Clear Hash
Clear the hash table.
* #### Ponder
Let Stockfish ponder its next move while the opponent is thinking.
* #### MultiPV
Output the N best lines (principal variations, PVs) when searching.
Leave at 1 for best performance.
* #### Use NNUE
Toggle between the NNUE and classical evaluation functions. If set to "true",
the network parameters must be available to load from file (see also EvalFile),
if they are not embedded in the binary.
* #### EvalFile
The name of the file of the NNUE evaluation parameters. Depending on the GUI the
filename might have to include the full path to the folder/directory that contains the file.
Other locations, such as the directory that contains the binary and the working directory,
are also searched.
* #### UCI_AnalyseMode
An option handled by your GUI.
* #### UCI_Chess960
An option handled by your GUI. If true, Stockfish will play Chess960.
* #### UCI_ShowWDL
If enabled, show approximate WDL statistics as part of the engine output.
These WDL numbers model expected game outcomes for a given evaluation and
game ply for engine self-play at fishtest LTC conditions (60+0.6s per game).
* #### UCI_LimitStrength
Enable weaker play aiming for an Elo rating as set by UCI_Elo. This option overrides Skill Level.
* #### UCI_Elo
If enabled by UCI_LimitStrength, aim for an engine strength of the given Elo.
This Elo rating has been calibrated at a time control of 60s+0.6s and anchored to CCRL 40/4.
* #### Skill Level
Lower the Skill Level in order to make Stockfish play weaker (see also UCI_LimitStrength).
Internally, MultiPV is enabled, and with a certain probability depending on the Skill Level a
weaker move will be played.
* #### SyzygyPath
Path to the folders/directories storing the Syzygy tablebase files. Multiple
directories are to be separated by ";" on Windows and by ":" on Unix-based
operating systems. Do not use spaces around the ";" or ":".
Example: `C:\tablebases\wdl345;C:\tablebases\wdl6;D:\tablebases\dtz345;D:\tablebases\dtz6`
It is recommended to store .rtbw files on an SSD. There is no loss in storing
the .rtbz files on a regular HD. It is recommended to verify all md5 checksums
of the downloaded tablebase files (`md5sum -c checksum.md5`) as corruption will
lead to engine crashes.
* #### SyzygyProbeDepth
Minimum remaining search depth for which a position is probed. Set this option
to a higher value to probe less aggressively if you experience too much slowdown
(in terms of nps) due to tablebase probing.
* #### Syzygy50MoveRule
Disable to let fifty-move rule draws detected by Syzygy tablebase probes count
as wins or losses. This is useful for ICCF correspondence games.
* #### SyzygyProbeLimit
Limit Syzygy tablebase probing to positions with at most this many pieces left
(including kings and pawns).
* #### Move Overhead
Assume a time delay of x ms due to network and GUI overheads. This is useful to
avoid losses on time in those cases.
* #### Slow Mover
Lower values will make Stockfish take less time in games, higher values will
make it think longer.
* #### nodestime
Tells the engine to use nodes searched instead of wall time to account for
elapsed time. Useful for engine testing.
* #### Debug Log File
Write all communication to and from the engine into a text file.
For developers the following non-standard commands might be of interest, mainly useful for debugging:
* #### bench *ttSize threads limit fenFile limitType evalType*
Performs a standard benchmark using various options. The signature of a version (standard node
count) is obtained using all defaults. `bench` is currently `bench 16 1 13 default depth mixed`.
* #### compiler
Give information about the compiler and environment used for building a binary.
* #### d
Display the current position, with ascii art and fen.
* #### eval
Return the evaluation of the current position.
* #### export_net [filename]
Exports the currently loaded network to a file.
If the currently loaded network is the embedded network and the filename
is not specified then the network is saved to the file matching the name
of the embedded network, as defined in evaluate.h.
If the currently loaded network is not the embedded network (some net set
through the UCI setoption) then the filename parameter is required and the
network is saved into that file.
* #### flip
Flips the side to move.
## A note on classical evaluation versus NNUE evaluation
Both approaches assign a value to a position that is used in alpha-beta (PVS) search
to find the best move. The classical evaluation computes this value as a function
of various chess concepts, handcrafted by experts, tested and tuned using fishtest.
The NNUE evaluation computes this value with a neural network based on basic
inputs (e.g. piece positions only). The network is optimized and trained
on the evaluations of millions of positions at moderate search depth.
The NNUE evaluation was first introduced in shogi, and ported to Stockfish afterward.
It can be evaluated efficiently on CPUs, and exploits the fact that only parts
of the neural network need to be updated after a typical chess move.
[The nodchip repository](https://github.com/nodchip/Stockfish) provided the first version of
the needed tools to train and develop the NNUE networks. Today, more advanced training tools are available
in [the nnue-pytorch repository](https://github.com/glinscott/nnue-pytorch/), while data generation tools
are available in [a dedicated branch](https://github.com/official-stockfish/Stockfish/tree/tools).
On CPUs supporting modern vector instructions
(avx2 and similar), the NNUE evaluation results in much stronger playing strength, even
if the nodes per second computed by the engine is somewhat lower (roughly 80% of nps
is typical).
Notes:
1) the NNUE evaluation depends on the Stockfish binary and the network parameter
file (see the EvalFile UCI option). Not every parameter file is compatible with a given
Stockfish binary, but the default value of the EvalFile UCI option is the name of a network
that is guaranteed to be compatible with that binary.
2) to use the NNUE evaluation, the additional data file with neural network parameters
needs to be available. Normally, this file is already embedded in the binary or it
can be downloaded. The filename for the default (recommended) net can be found as the default
value of the `EvalFile` UCI option, with the format `nn-[SHA256 first 12 digits].nnue`
(for instance, `nn-c157e0a5755b.nnue`). This file can be downloaded from
```
https://tests.stockfishchess.org/api/nn/[filename]
```
replacing `[filename]` as needed.
## What to expect from the Syzygy tablebases?
If the engine is searching a position that is not in the tablebases (e.g.
a position with 8 pieces), it will access the tablebases during the search.
If the engine reports a very large score (typically 153.xx), this means
it has found a winning line into a tablebase position.
If the engine is given a position to search that is in the tablebases, it
will use the tablebases at the beginning of the search to preselect all
good moves, i.e. all moves that preserve the win or preserve the draw while
taking into account the 50-move rule.
It will then perform a search only on those moves. **The engine will not move
immediately**, unless there is only a single good move. **The engine likely
will not report a mate score, even if the position is known to be won.**
It is therefore clear that this behaviour is not identical to what one might
be used to with Nalimov tablebases. There are technical reasons for this
difference, the main technical reason being that Nalimov tablebases use the
DTM metric (distance-to-mate), while the Syzygy tablebases use a variation of the
DTZ metric (distance-to-zero, zero meaning any move that resets the 50-move
counter). This special metric is one of the reasons that the Syzygy tablebases are
more compact than Nalimov tablebases, while still storing all information
needed for optimal play and in addition being able to take into account
the 50-move rule.
## Large Pages
Stockfish supports large pages on Linux and Windows. Large pages make
the hash access more efficient, improving the engine speed, especially
on large hash sizes. Typical increases are 5..10% in terms of nodes per
second, but speed increases up to 30% have been measured. The support is
automatic. Stockfish attempts to use large pages when available and
will fall back to regular memory allocation when this is not the case.
### Support on Linux
Large page support on Linux is obtained by the Linux kernel
transparent huge pages functionality. Typically, transparent huge pages
are already enabled, and no configuration is needed.
### Support on Windows
The use of large pages requires "Lock Pages in Memory" privilege. See
[Enable the Lock Pages in Memory Option (Windows)](https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/enable-the-lock-pages-in-memory-option-windows)
on how to enable this privilege, then run [RAMMap](https://docs.microsoft.com/en-us/sysinternals/downloads/rammap)
to double-check that large pages are used. We suggest that you reboot
your computer after you have enabled large pages, because long Windows
sessions suffer from memory fragmentation, which may prevent Stockfish
from getting large pages: a fresh session is better in this regard.
## Compiling Stockfish yourself from the sources
Stockfish has support for 32 or 64-bit CPUs, certain hardware
instructions, big-endian machines such as Power PC, and other platforms.
On Unix-like systems, it should be easy to compile Stockfish
directly from the source code with the included Makefile in the folder
`src`. In general it is recommended to run `make help` to see a list of make
targets with corresponding descriptions.
```
cd src
make help
make net
make build ARCH=x86-64-modern
```
When not using the Makefile to compile (for instance, with Microsoft MSVC) you
need to manually set/unset some switches in the compiler command line; see
file *types.h* for a quick reference.
When reporting an issue or a bug, please tell us which Stockfish version
and which compiler you used to create your executable. This information
can be found by typing the following command in a console:
```
./stockfish compiler
```
## Understanding the code base and participating in the project
Stockfish's improvement over the last decade has been a great community
effort. There are a few ways to help contribute to its growth.
__See [Contributing Guide](CONTRIBUTING.md).__
### Donating hardware
Improving Stockfish requires a massive amount of testing. You can donate
your hardware resources by installing the [Fishtest Worker](https://github.com/glinscott/fishtest/wiki/Running-the-worker:-overview)
and view the current tests on [Fishtest](https://tests.stockfishchess.org/tests).
Improving Stockfish requires a massive amount of testing. You can donate your
hardware resources by installing the [Fishtest Worker][worker-link] and viewing
the current tests on [Fishtest][fishtest-link].
### Improving the code
If you want to help improve the code, there are several valuable resources:
* [In this wiki,](https://www.chessprogramming.org) many techniques used in
In the [chessprogramming wiki][programming-link], many techniques used in
Stockfish are explained with a lot of background information.
The [section on Stockfish][programmingsf-link] describes many features
and techniques used by Stockfish. However, it is generic rather than
focused on Stockfish's precise implementation.
* [The section on Stockfish](https://www.chessprogramming.org/Stockfish)
describes many features and techniques used by Stockfish. However, it is
generic rather than being focused on Stockfish's precise implementation.
Nevertheless, a helpful resource.
* The latest source can always be found on [GitHub](https://github.com/official-stockfish/Stockfish).
Discussions about Stockfish take place these days mainly in the [FishCooking](https://groups.google.com/forum/#!forum/fishcooking)
group and on the [Stockfish Discord channel](https://discord.gg/nv8gDtt).
The engine testing is done on [Fishtest](https://tests.stockfishchess.org/tests).
If you want to help improve Stockfish, please read this [guideline](https://github.com/glinscott/fishtest/wiki/Creating-my-first-test)
The engine testing is done on [Fishtest][fishtest-link].
If you want to help improve Stockfish, please read this [guideline][guideline-link]
first, where the basics of Stockfish development are explained.
Discussions about Stockfish take place these days mainly in the Stockfish
[Discord server][discord-link]. This is also the best place to ask questions
about the codebase and how to improve it.
## Compiling Stockfish
Stockfish has support for 32 or 64-bit CPUs, certain hardware instructions,
big-endian machines such as Power PC, and other platforms.
On Unix-like systems, it should be easy to compile Stockfish directly from the
source code with the included Makefile in the folder `src`. In general, it is
recommended to run `make help` to see a list of make targets with corresponding
descriptions. An example suitable for most Intel and AMD chips:
```
cd src
make -j profile-build ARCH=x86-64-avx2
```
Detailed compilation instructions for all platforms can be found in our
[documentation][wiki-compile-link]. Our wiki also has information about
the [UCI commands][wiki-uci-link] supported by Stockfish.
## Terms of use
Stockfish is free, and distributed under the **GNU General Public License version 3**
(GPL v3). Essentially, this means you are free to do almost exactly
what you want with the program, including distributing it among your
friends, making it available for download from your website, selling
it (either by itself or as part of some bigger software package), or
using it as the starting point for a software project of your own.
Stockfish is free and distributed under the
[**GNU General Public License version 3**][license-link] (GPL v3). Essentially,
this means you are free to do almost exactly what you want with the program,
including distributing it among your friends, making it available for download
from your website, selling it (either by itself or as part of some bigger
software package), or using it as the starting point for a software project of
your own.
The only real limitation is that whenever you distribute Stockfish in
some way, you MUST always include the full source code, or a pointer
to where the source code can be found, to generate the exact binary
you are distributing. If you make any changes to the source code,
these changes must also be made available under the GPL.
The only real limitation is that whenever you distribute Stockfish in some way,
you MUST always include the license and the full source code (or a pointer to
where the source code can be found) to generate the exact binary you are
distributing. If you make any changes to the source code, these changes must
also be made available under GPL v3.
For full details, read the copy of the GPL v3 found in the file named
[*Copying.txt*](https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt).
[authors-link]: https://github.com/official-stockfish/Stockfish/blob/master/AUTHORS
[build-link]: https://github.com/official-stockfish/Stockfish/actions/workflows/stockfish.yml
[commits-link]: https://github.com/official-stockfish/Stockfish/commits/master
[discord-link]: https://discord.gg/GWDRS3kU6R
[issue-link]: https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml
[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new
[fishtest-link]: https://tests.stockfishchess.org/tests
[guideline-link]: https://github.com/official-stockfish/fishtest/wiki/Creating-my-first-test
[license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt
[programming-link]: https://www.chessprogramming.org/Main_Page
[programmingsf-link]: https://www.chessprogramming.org/Stockfish
[readme-link]: https://github.com/official-stockfish/Stockfish/blob/master/README.md
[release-link]: https://github.com/official-stockfish/Stockfish/releases/latest
[src-link]: https://github.com/official-stockfish/Stockfish/tree/master/src
[stockfish128-logo]: https://stockfishchess.org/images/logo/icon_128x128.png
[uci-link]: https://backscattering.de/chess/uci/
[website-link]: https://stockfishchess.org
[website-blog-link]: https://stockfishchess.org/blog/
[wiki-link]: https://github.com/official-stockfish/Stockfish/wiki
[wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source
[wiki-uci-link]: https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands
[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage
[worker-link]: https://github.com/official-stockfish/fishtest/wiki/Running-the-worker
[build-badge]: https://img.shields.io/github/actions/workflow/status/official-stockfish/Stockfish/stockfish.yml?branch=master&style=for-the-badge&label=stockfish&logo=github
[commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge
[discord-badge]: https://img.shields.io/discord/435943710472011776?style=for-the-badge&label=discord&logo=Discord
[fishtest-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=Fishtest&up_color=success&up_message=Online&url=https%3A%2F%2Ftests.stockfishchess.org%2Ftests%2Ffinished
[license-badge]: https://img.shields.io/github/license/official-stockfish/Stockfish?style=for-the-badge&label=license&color=success
[release-badge]: https://img.shields.io/github/v/release/official-stockfish/Stockfish?style=for-the-badge&label=official%20release
[website-badge]: https://img.shields.io/website?style=for-the-badge&down_color=red&down_message=Offline&label=website&up_color=success&up_message=Online&url=https%3A%2F%2Fstockfishchess.org

View file

@ -1,100 +1,126 @@
Contributors to Fishtest with >10,000 CPU hours, as of 2022-02-05.
Contributors to Fishtest with >10,000 CPU hours, as of 2024-02-24.
Thank you!
Username CPU Hours Games played
------------------------------------------------------------------
noobpwnftw 30730952 2158431735
mlang 2729669 187335452
technologov 1696847 74478658
dew 1635640 97483012
grandphish2 1062754 64955639
tvijlbrief 795993 51894442
okrout 773704 63465204
TueRens 766198 47770388
noobpwnftw 39302472 3055513453
technologov 20845762 994893444
linrock 8616428 560281417
mlang 3026000 200065824
okrout 2332151 222639518
pemo 1800019 60274069
dew 1689162 100033738
TueRens 1474943 75121774
grandphish2 1463002 91616949
JojoM 1109702 72927902
olafm 978631 71037944
sebastronomy 939955 44920556
tvijlbrief 796125 51897690
gvreuls 711320 49142318
mibere 703840 46867607
JojoM 703005 42689868
pemo 634102 29868807
linrock 626939 17408017
gvreuls 517442 33605006
cw 503905 33850487
fastgm 482847 29004732
crunchy 427035 27344275
CSU_Dynasty 415864 28116776
ctoks 403102 26737127
oz 357710 26490208
bcross 331095 23165889
oz 646268 46293638
rpngn 572571 38928563
leszek 531858 39316505
cw 518116 34894291
fastgm 503862 30260818
CSU_Dynasty 468784 31385034
ctoks 434591 28520597
maximmasiutin 429983 27066286
crunchy 427414 27371625
bcross 415724 29061187
velislav 342588 22140902
mgrabiak 338763 23999170
Fisherman 327231 21829379
velislav 321708 20729264
leszek 303654 19063973
Dantist 251015 15843226
mgrabiak 231973 15162494
glinscott 217799 13780820
robal 213960 13665726
nordlandia 211692 13484886
drabel 200914 13755384
robal 299836 20213182
Dantist 296386 18031762
ncfish1 267604 17881149
nordlandia 249322 16420192
marrco 234581 17714473
tolkki963 233490 19773930
glinscott 208125 13277240
drabel 204167 13930674
mhoram 202894 12601997
bking_US 198894 11876016
mhoram 180229 11610075
Calis007 188631 12795784
Thanar 179852 12365359
Fifis 176209 10638245
vdv 175544 9904472
spams 157128 10319326
marrco 150300 9402229
DesolatedDodo 156659 10210328
armo9494 155355 10566898
sqrt2 147963 9724586
vdbergh 137429 8955089
jcAEie 140086 10603658
vdbergh 139746 9172061
CoffeeOne 137100 5024116
malala 136182 8002293
xoto 133759 9159372
rpngn 131285 8657757
davar 122661 7996937
davar 129023 8376525
DMBK 122960 8980062
dsmith 122059 7570238
amicic 119659 7937885
javran 121564 10144656
amicic 119661 7938029
sschnee 118107 7389266
Wolfgang 114616 8070494
Data 113305 8220352
BrunoBanani 112960 7436849
CypressChess 108321 7759588
Wencey 111502 5991676
cuistot 108503 7006992
CypressChess 108331 7759788
skiminki 107583 7218170
MaZePallas 102823 6633619
sterni1971 100532 5880772
sunu 100167 7040199
DesolatedDodo 99038 6414626
zeryl 99331 6221261
thirdlife 99156 2245320
ElbertoOne 99028 7023771
skiminki 98123 6478402
Dubslow 98600 6903242
markkulix 97010 7643900
bigpen0r 94809 6529203
brabos 92118 6186135
cuistot 90358 5351004
Maxim 90818 3283364
psk 89957 5984901
racerschmacer 85712 6119648
megaman7de 88822 6052132
racerschmacer 85805 6122790
maposora 85710 7778146
Vizvezdenec 83761 5344740
sschnee 83003 4840890
0x3C33 82614 5271253
BRAVONE 81239 5054681
nssy 76497 5259388
jromang 76106 5236025
teddybaer 75125 5407666
Pking_cda 73776 5293873
zeryl 73335 4774257
jromang 72192 5057715
yurikvelo 73516 5036928
MarcusTullius 71053 4803477
Bobo1239 70579 4794999
solarlight 70517 5028306
dv8silencer 70287 3883992
Bobo1239 68515 4652287
Spprtr 69646 4806763
Mineta 66325 4537742
manap 66273 4121774
szupaw 65468 5669742
tinker 64333 4268790
yurikvelo 63371 4335060
qurashee 61208 3429862
woutboat 59496 4906352
AGI 58195 4329580
robnjr 57262 4053117
Wolfgang 57014 3561352
Freja 56938 3733019
MaxKlaxxMiner 56879 3423958
ttruscott 56010 3680085
rkl 55132 4164467
jmdana 54697 4012593
renouve 53811 3501516
notchris 52433 4044590
finfish 51360 3370515
eva42 51272 3599691
Calis007 51182 3131552
eastorwest 51058 3451555
eastorwest 51117 3454811
Goatminola 51004 4432492
rap 49985 3219146
pb00067 49727 3298270
Spprtr 48260 3141959
bigpen0r 47667 3336927
pb00067 49733 3298934
GPUex 48686 3684998
OuaisBla 48626 3445134
ronaldjerum 47654 3240695
MaxKlaxxMiner 47584 2972142
biffhero 46564 3111352
megaman7de 45992 2952006
Fifis 45843 3088497
oryx 45533 3539290
VoyagerOne 45476 3452465
speedycpu 43842 3003273
jbwiebe 43305 2805433
@ -102,129 +128,159 @@ Antihistamine 41788 2761312
mhunt 41735 2691355
homyur 39893 2850481
gri 39871 2515779
oryx 38867 2976992
Garf 37741 2999686
SC 37299 2731694
Garf 37213 2986270
Sylvain27 36520 1467082
csnodgrass 36207 2688994
jmdana 36157 2210661
Gaster319 35655 3149442
strelock 34716 2074055
EthanOConnor 33370 2090311
slakovv 32915 2021889
armo9494 32129 2551682
tolkki963 32114 1932256
gopeto 31884 2076712
Gelma 31771 1551204
kdave 31157 2198362
manapbk 30987 1810399
DMBK 30675 2383552
ZacHFX 30551 2238078
Prcuvu 30377 2170122
anst 30301 2190091
jkiiski 30136 1904470
gopeto 29886 1979118
spcc 29925 1901692
hyperbolic.tom 29840 2017394
chuckstablers 29659 2093438
Pyafue 29650 1902349
OuaisBla 27636 1578800
belzedar94 28846 1811530
votoanthuan 27978 2285818
shawnxu 27438 2465810
chriswk 26902 1868317
xwziegtm 26897 2124586
achambord 26582 1767323
Patrick_G 26276 1801617
yorkman 26193 1992080
Ulysses 25397 1701264
Jopo12321 25227 1652482
SFTUser 25182 1675689
nabildanial 24942 1519409
nabildanial 25068 1531665
Sharaf_DG 24765 1786697
ncfish1 24411 1520927
rodneyc 24275 1410450
rodneyc 24376 1416402
jsys14 24297 1721230
agg177 23890 1395014
belzedar94 23707 1593860
srowen 23842 1342508
Ente 23752 1678188
jojo2357 23479 2061238
JanErik 23408 1703875
Isidor 23388 1680691
Norabor 23339 1602636
Ente 23093 1642458
cisco2015 22897 1762669
Norabor 23371 1603244
cisco2015 22920 1763301
Zirie 22542 1472937
Nullvalue 22490 1970374
AndreasKrug 22485 1769491
team-oh 22272 1636708
Roady 22220 1465606
MazeOfGalious 21978 1629593
sg4032 21947 1643265
sg4032 21947 1643353
ianh2105 21725 1632562
xor12 21628 1680365
dex 21612 1467203
nesoneg 21494 1463031
user213718 21454 1404128
sphinx 21211 1384728
qoo_charly_cai 21135 1514907
jjoshua2 21001 1423089
Zake9298 20938 1565848
horst.prack 20878 1465656
user213718 20783 1379584
0xB00B1ES 20590 1208666
Serpensin 20487 1729674
Dinde 20440 1292390
j3corre 20405 941444
Adrian.Schmidt123 20316 1281436
wei 19973 1745989
Roady 19848 1335928
fishtester 19617 1257388
rstoesser 19569 1293588
eudhan 19274 1283717
vulcan 18871 1729392
Karpovbot 18766 1053178
WoodMan777 18556 1628264
jundery 18445 1115855
iisiraider 18247 1101015
ville 17883 1384026
chris 17698 1487385
purplefishies 17595 1092533
dju 17353 978595
kdave 17183 1242754
dju 17414 981289
ols 17291 1042003
iisiraider 17275 1049015
Skiff84 17111 950248
DragonLord 17014 1162790
thirdlife 16996 447356
spcc 16932 1130940
fishtester 16644 1123000
Ulysses 16490 1184400
redstone59 16842 1461780
Karby 16839 1010124
Alb11747 16787 1213990
pirt 16493 1237199
Naven94 16414 951718
wizardassassin 16392 1148672
IgorLeMasson 16064 1147232
scuzzi 15757 968735
ako027ako 15671 1173203
Nikolay.IT 15154 1068349
Andrew Grant 15114 895539
OssumOpossum 14857 1007129
Karby 14808 867120
AndreasKrug 14608 1152093
LunaticBFF57 14525 1190310
enedene 14476 905279
jsys14 14340 844792
bpfliegel 14298 884523
IslandLambda 14393 958196
bpfliegel 14233 882523
YELNAMRON 14230 1128094
mpx86 14019 759568
jpulman 13982 870599
getraideBFF 13871 1172846
Nesa92 13806 1116101
crocogoat 13803 1117422
joster 13794 950160
Nesa92 13786 1114691
joster 13710 946160
mbeier 13650 1044928
Hjax 13535 915487
Dark_wizzie 13422 1007152
Rudolphous 13244 883140
MarcusTullius 13221 843169
Machariel 13010 863104
infinigon 12991 943216
mabichito 12903 749391
thijsk 12886 722107
AdrianSA 12860 804972
infinigon 12807 937332
Flopzee 12698 894821
mschmidt 12644 863193
korposzczur 12606 838168
tsim67 12570 890180
Jackfish 12553 836958
fatmurphy 12547 853210
scuzzi 12511 845761
Oakwen 12503 853105
SapphireBrand 12416 969604
deflectooor 12386 579392
modolief 12386 896470
TataneSan 12358 609332
Farseer 12249 694108
pgontarz 12151 848794
dbernier 12103 860824
FormazChar 11989 907809
stocky 11954 699440
mschmidt 11941 803401
dbernier 11609 818636
Maxim 11543 836024
pirt 11516 894513
somethingintheshadows 11940 989472
MooTheCow 11892 776126
3cho 11842 1036786
whelanh 11557 245188
infinity 11470 727027
aga 11409 695071
aga 11412 695127
torbjo 11395 729145
Thomas A. Anderson 11372 732094
savage84 11358 670860
markkulix 11331 739098
FormazChar 11308 847735
d64 11263 789184
MooTheCow 11237 720174
ali-al-zhrani 11245 779246
ckaz 11170 680866
snicolet 11106 869170
ali-al-zhrani 11098 768494
whelanh 11067 235676
dapper 11032 771402
Ethnikoi 10993 945906
Snuuka 10938 435504
Karmatron 10859 678058
basepi 10637 744851
jibarbosa 10628 857100
Cubox 10621 826448
mecevdimitar 10609 787318
michaelrpg 10509 739239
Def9Infinity 10427 686978
OIVAS7572 10420 995586
wxt9861 10412 1013864
Garruk 10365 706465
dzjp 10343 732529
Garruk 10332 703905
ols 10259 570669
lbraesch 10252 647825
Jackfish 10098 682338

120
scripts/get_native_properties.sh Executable file
View file

@ -0,0 +1,120 @@
#!/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
#
# 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
}
# 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
get_flags() {
flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo)
}
# 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
}
# Set the file CPU x86_64 architecture
set_arch_x86_64() {
if check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then
true_arch='x86-64-vnni256'
elif check_flags 'avx512f' 'avx512bw'; then
true_arch='x86-64-avx512'
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
}
# Check the system type
uname_s=$(uname -s)
uname_m=$(uname -m)
case $uname_s in
'Darwin') # Mac OSX system
case $uname_m in
'arm64')
true_arch='apple-silicon'
file_arch='x86-64-sse41-popcnt' # Supported by Rosetta 2
;;
'x86_64')
flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.')
set_arch_x86_64
if [ "$true_arch" = 'x86-64-vnni256' ] || [ "$true_arch" = 'x86-64-avx512' ]; then
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'
;;
'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
;;
*) # Unsupported machine type, exit with error
printf 'Unsupported machine type: %s\n' "$uname_m"
exit 1
;;
esac
file_ext='tar'
;;
'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows system 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
;;
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"

View file

@ -1,5 +1,5 @@
# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
# Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
# Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
#
# Stockfish is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -20,9 +20,9 @@
### ==========================================================================
### Establish the operating system name
KERNEL = $(shell uname -s)
KERNEL := $(shell uname -s)
ifeq ($(KERNEL),Linux)
OS = $(shell uname -o)
OS := $(shell uname -o)
endif
### Target Windows OS
@ -33,7 +33,7 @@ ifeq ($(OS),Windows_NT)
else ifeq ($(COMP),mingw)
target_windows = yes
ifeq ($(WINE_PATH),)
WINE_PATH = $(shell which wine)
WINE_PATH := $(shell which wine)
endif
endif
@ -49,17 +49,21 @@ PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
### Built-in benchmark for pgo-builds
ifeq ($(SDE_PATH),)
PGOBENCH = $(WINE_PATH) ./$(EXE) bench
else
PGOBENCH = $(SDE_PATH) -- $(WINE_PATH) ./$(EXE) bench
endif
PGOBENCH = $(WINE_PATH) ./$(EXE) bench
### Source and object files
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
misc.cpp movegen.cpp movepick.cpp position.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp
nnue/nnue_misc.cpp nnue/features/half_ka_v2_hm.cpp nnue/network.cpp engine.cpp score.cpp
HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \
nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \
nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \
nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \
nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \
search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h
OBJS = $(notdir $(SRCS:.cpp=.o))
@ -69,32 +73,33 @@ VPATH = syzygy:nnue:nnue/features
### Section 2. High-level Configuration
### ==========================================================================
#
# flag --- Comp switch --- Description
# flag --- Comp switch --- Description
# ----------------------------------------------------------------------------
#
# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
# sanitize = none/<sanitizer> ... (-fsanitize )
# --- ( undefined ) --- enable undefined behavior checks
# --- ( thread ) --- enable threading error checks
# --- ( address ) --- enable memory access checks
# --- ...etc... --- see compiler documentation for supported sanitizers
# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations
# arch = (name) --- (-arch) --- Target architecture
# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system
# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction
# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction
# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction
# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions
# mmx = yes/no --- -mmmx --- Use Intel MMX instructions
# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2
# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3
# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1
# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2
# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX
# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512
# vnni256 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 256
# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
# --- ( undefined ) --- enable undefined behavior checks
# --- ( thread ) --- enable threading error checks
# --- ( address ) --- enable memory access checks
# --- ...etc... --- see compiler documentation for supported sanitizers
# optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations
# arch = (name) --- (-arch) --- Target architecture
# bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system
# prefetch = yes/no --- -DUSE_PREFETCH --- Use prefetch asm-instruction
# popcnt = yes/no --- -DUSE_POPCNT --- Use popcnt asm-instruction
# pext = yes/no --- -DUSE_PEXT --- Use pext x86_64 asm-instruction
# sse = yes/no --- -msse --- Use Intel Streaming SIMD Extensions
# mmx = yes/no --- -mmmx --- Use Intel MMX instructions
# sse2 = yes/no --- -msse2 --- Use Intel Streaming SIMD Extensions 2
# ssse3 = yes/no --- -mssse3 --- Use Intel Supplemental Streaming SIMD Extensions 3
# sse41 = yes/no --- -msse4.1 --- Use Intel Streaming SIMD Extensions 4.1
# avx2 = yes/no --- -mavx2 --- Use Intel Advanced Vector Extensions 2
# avxvnni = yes/no --- -mavxvnni --- Use Intel Vector Neural Network Instructions AVX
# avx512 = yes/no --- -mavx512bw --- Use Intel Advanced Vector Extensions 512
# vnni256 = yes/no --- -mavx256vnni --- Use Intel Vector Neural Network Instructions 512 with 256bit operands
# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions
#
# 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
@ -107,16 +112,20 @@ VPATH = syzygy:nnue:nnue/features
### 2.1. General and architecture defaults
ifeq ($(ARCH),)
ARCH = x86-64-modern
help_skip_sanity = yes
ARCH = native
endif
ifeq ($(ARCH), native)
override ARCH := $(shell $(SHELL) ../scripts/get_native_properties.sh | cut -d " " -f 1)
endif
# explicitly check for the list of supported architectures (as listed with make help),
# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true`
ifeq ($(ARCH), $(filter $(ARCH), \
x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \
x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \
armv7 armv7-neon armv8 apple-silicon general-64 general-32))
armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 loongarch64))
SUPPORTED_ARCH=true
else
SUPPORTED_ARCH=false
@ -140,9 +149,16 @@ avx512 = no
vnni256 = no
vnni512 = no
neon = no
dotprod = no
arm_version = 0
STRIP = strip
ifneq ($(shell which clang-format-17 2> /dev/null),)
CLANG-FORMAT = clang-format-17
else
CLANG-FORMAT = clang-format
endif
### 2.2 Architecture specific
ifeq ($(findstring x86,$(ARCH)),x86)
@ -191,6 +207,8 @@ ifeq ($(findstring -sse41,$(ARCH)),-sse41)
endif
ifeq ($(findstring -modern,$(ARCH)),-modern)
$(warning *** ARCH=$(ARCH) is deprecated, defaulting to ARCH=x86-64-sse41-popcnt. Execute `make help` for a list of available architectures. ***)
$(shell sleep 5)
popcnt = yes
sse = yes
sse2 = yes
@ -308,11 +326,21 @@ ifeq ($(ARCH),armv8)
arm_version = 8
endif
ifeq ($(ARCH),armv8-dotprod)
arch = armv8
prefetch = yes
popcnt = yes
neon = yes
dotprod = yes
arm_version = 8
endif
ifeq ($(ARCH),apple-silicon)
arch = arm64
prefetch = yes
popcnt = yes
neon = yes
dotprod = yes
arm_version = 8
endif
@ -338,16 +366,30 @@ ifeq ($(findstring e2k,$(ARCH)),e2k)
popcnt = yes
endif
ifeq ($(ARCH),riscv64)
arch = riscv64
endif
ifeq ($(ARCH),loongarch64)
arch = loongarch64
endif
endif
### ==========================================================================
### Section 3. Low-level Configuration
### ==========================================================================
### 3.1 Selecting compiler (default = gcc)
CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
DEPENDFLAGS += -std=c++17
LDFLAGS += $(EXTRALDFLAGS)
ifeq ($(MAKELEVEL),0)
export ENV_CXXFLAGS := $(CXXFLAGS)
export ENV_DEPENDFLAGS := $(DEPENDFLAGS)
export ENV_LDFLAGS := $(LDFLAGS)
endif
CXXFLAGS = $(ENV_CXXFLAGS) -Wall -Wcast-qual -fno-exceptions -std=c++17 $(EXTRACXXFLAGS)
DEPENDFLAGS = $(ENV_DEPENDFLAGS) -std=c++17
LDFLAGS = $(ENV_LDFLAGS) $(EXTRALDFLAGS)
ifeq ($(COMP),)
COMP=gcc
@ -356,13 +398,18 @@ endif
ifeq ($(COMP),gcc)
comp=gcc
CXX=g++
CXXFLAGS += -pedantic -Wextra -Wshadow
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations
ifeq ($(arch),$(filter $(arch),armv7 armv8))
ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))
ifeq ($(OS),Android)
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
endif
ifeq ($(ARCH),riscv64)
CXXFLAGS += -latomic
endif
else ifeq ($(ARCH),loongarch64)
CXXFLAGS += -latomic
else
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
@ -397,13 +444,14 @@ ifeq ($(COMP),mingw)
CXX=i686-w64-mingw32-c++-posix
endif
endif
CXXFLAGS += -pedantic -Wextra -Wshadow
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations
endif
ifeq ($(COMP),icc)
comp=icc
CXX=icpc
CXXFLAGS += -diag-disable 1476,10120 -Wcheck -Wabi -Wdeprecated -strict-ansi
ifeq ($(COMP),icx)
comp=icx
CXX=icpx
CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \
-Wconditional-uninitialized -Wabi -Wdeprecated
endif
ifeq ($(COMP),clang)
@ -413,7 +461,8 @@ ifeq ($(COMP),clang)
CXX=x86_64-w64-mingw32-clang++
endif
CXXFLAGS += -pedantic -Wextra -Wshadow
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \
-Wconditional-uninitialized
ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),)
ifeq ($(target_windows),)
@ -423,11 +472,16 @@ ifeq ($(COMP),clang)
endif
endif
ifeq ($(arch),$(filter $(arch),armv7 armv8))
ifeq ($(arch),$(filter $(arch),armv7 armv8 riscv64))
ifeq ($(OS),Android)
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
endif
ifeq ($(ARCH),riscv64)
CXXFLAGS += -latomic
endif
else ifeq ($(ARCH),loongarch64)
CXXFLAGS += -latomic
else
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
@ -467,12 +521,20 @@ ifeq ($(COMP),ndk)
STRIP=llvm-strip
endif
endif
ifeq ($(arch),x86_64)
CXX=x86_64-linux-android21-clang++
ifneq ($(shell which x86_64-linux-android-strip 2>/dev/null),)
STRIP=x86_64-linux-android-strip
else
STRIP=llvm-strip
endif
endif
LDFLAGS += -static-libstdc++ -pie -lm -latomic
endif
ifeq ($(comp),icc)
profile_make = icc-profile-make
profile_use = icc-profile-use
ifeq ($(comp),icx)
profile_make = icx-profile-make
profile_use = icx-profile-use
else ifeq ($(comp),clang)
profile_make = clang-profile-make
profile_use = clang-profile-use
@ -484,11 +546,6 @@ else
endif
endif
### Travis CI script uses COMPILER to overwrite CXX
ifdef COMPILER
COMPCXX=$(COMPILER)
endif
### Allow overwriting CXX from command line
ifdef COMPCXX
CXX=$(COMPCXX)
@ -496,8 +553,8 @@ endif
### Sometimes gcc is really clang
ifeq ($(COMP),gcc)
gccversion = $(shell $(CXX) --version)
gccisclang = $(findstring clang,$(gccversion))
gccversion := $(shell $(CXX) --version 2>/dev/null)
gccisclang := $(findstring clang,$(gccversion))
ifneq ($(gccisclang),)
profile_make = clang-profile-make
profile_use = clang-profile-use
@ -534,7 +591,7 @@ endif
### 3.3 Optimization
ifeq ($(optimize),yes)
CXXFLAGS += -O3
CXXFLAGS += -O3 -funroll-loops
ifeq ($(comp),gcc)
ifeq ($(OS), Android)
@ -542,20 +599,23 @@ ifeq ($(optimize),yes)
endif
endif
ifeq ($(KERNEL),Darwin)
ifeq ($(comp),$(filter $(comp),clang icc))
CXXFLAGS += -mdynamic-no-pic
endif
ifeq ($(KERNEL),Darwin)
ifeq ($(comp),$(filter $(comp),clang icx))
CXXFLAGS += -mdynamic-no-pic
endif
ifeq ($(comp),gcc)
ifneq ($(arch),arm64)
CXXFLAGS += -mdynamic-no-pic
endif
endif
endif
ifeq ($(comp),gcc)
ifneq ($(arch),arm64)
CXXFLAGS += -mdynamic-no-pic
endif
endif
endif
ifeq ($(comp),clang)
CXXFLAGS += -fexperimental-new-pass-manager
clangmajorversion := $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.)
ifeq ($(shell expr $(clangmajorversion) \< 16),1)
CXXFLAGS += -fexperimental-new-pass-manager
endif
endif
endif
@ -576,8 +636,6 @@ endif
ifeq ($(popcnt),yes)
ifeq ($(arch),$(filter $(arch),ppc64 armv7 armv8 arm64))
CXXFLAGS += -DUSE_POPCNT
else ifeq ($(comp),icc)
CXXFLAGS += -msse3 -DUSE_POPCNT
else
CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT
endif
@ -586,63 +644,62 @@ endif
### 3.6 SIMD architectures
ifeq ($(avx2),yes)
CXXFLAGS += -DUSE_AVX2
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
CXXFLAGS += -mavx2
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavx2 -mbmi
endif
endif
ifeq ($(avxvnni),yes)
CXXFLAGS += -DUSE_VNNI -DUSE_AVXVNNI
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavxvnni
endif
endif
ifeq ($(avx512),yes)
CXXFLAGS += -DUSE_AVX512
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavx512f -mavx512bw
endif
endif
ifeq ($(vnni256),yes)
CXXFLAGS += -DUSE_VNNI
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=256
endif
endif
ifeq ($(vnni512),yes)
CXXFLAGS += -DUSE_VNNI
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
CXXFLAGS += -mavx512vnni -mavx512dq -mavx512vl
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mavx512f -mavx512bw -mavx512vnni -mavx512dq -mavx512vl -mprefer-vector-width=512
endif
endif
ifeq ($(sse41),yes)
CXXFLAGS += -DUSE_SSE41
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -msse4.1
endif
endif
ifeq ($(ssse3),yes)
CXXFLAGS += -DUSE_SSSE3
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mssse3
endif
endif
ifeq ($(sse2),yes)
CXXFLAGS += -DUSE_SSE2
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -msse2
endif
endif
ifeq ($(mmx),yes)
CXXFLAGS += -DUSE_MMX
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mmmx
endif
endif
@ -658,21 +715,45 @@ ifeq ($(neon),yes)
endif
endif
ifeq ($(dotprod),yes)
CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD
endif
### 3.7 pext
ifeq ($(pext),yes)
CXXFLAGS += -DUSE_PEXT
ifeq ($(comp),$(filter $(comp),gcc clang mingw))
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mbmi2
endif
endif
### 3.8 Link Time Optimization
### 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)
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)
endif
### 3.8.3 Try to include architecture
ifneq ($(ARCH), )
CXXFLAGS += -DARCH=$(ARCH)
endif
### 3.9 Link Time Optimization
### 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 ($(comp),clang)
CXXFLAGS += -flto
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
@ -682,25 +763,23 @@ ifeq ($(debug), no)
# GCC on some systems.
else ifeq ($(comp),gcc)
ifeq ($(gccisclang),)
CXXFLAGS += -flto
CXXFLAGS += -flto -flto-partition=one
LDFLAGS += $(CXXFLAGS) -flto=jobserver
else
CXXFLAGS += -flto
CXXFLAGS += -flto=full
LDFLAGS += $(CXXFLAGS)
endif
# To use LTO and static linking on Windows,
# the tool chain requires gcc version 10.1 or later.
else ifeq ($(comp),mingw)
ifneq ($(arch),i386)
CXXFLAGS += -flto
CXXFLAGS += -flto -flto-partition=one
LDFLAGS += $(CXXFLAGS) -save-temps
endif
endif
endif
endif
### 3.9 Android 5 can only run position independent executables. Note that this
### 3.10 Android 5 can only run position independent executables. Note that this
### breaks Android 4.0 and earlier.
ifeq ($(OS), Android)
CXXFLAGS += -fPIE
@ -711,81 +790,87 @@ endif
### Section 4. Public Targets
### ==========================================================================
help:
@echo ""
@echo "To compile stockfish, type: "
@echo ""
@echo "make target ARCH=arch [COMP=compiler] [COMPCXX=cxx]"
@echo "make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]"
@echo ""
@echo "Supported targets:"
@echo ""
@echo "help > Display architecture details"
@echo "build > Standard build"
@echo "net > Download the default nnue net"
@echo "profile-build > Faster build (with profile-guided optimization)"
@echo "profile-build > standard build with profile-guided optimization"
@echo "build > skip profile-guided optimization"
@echo "net > Download the default nnue nets"
@echo "strip > Strip executable"
@echo "install > Install executable"
@echo "clean > Clean up"
@echo ""
@echo "Supported archs:"
@echo ""
@echo "x86-64-vnni512 > x86 64-bit with vnni support 512bit wide"
@echo "x86-64-vnni256 > x86 64-bit with vnni support 256bit wide"
@echo "native > select the best architecture for the host processor (default)"
@echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support"
@echo "x86-64-vnni256 > x86 64-bit with vnni 512bit support, limit operands to 256bit wide"
@echo "x86-64-avx512 > x86 64-bit with avx512 support"
@echo "x86-64-avxvnni > x86 64-bit with avxvnni support"
@echo "x86-64-avxvnni > x86 64-bit with vnni 256bit support"
@echo "x86-64-bmi2 > x86 64-bit with bmi2 support"
@echo "x86-64-avx2 > x86 64-bit with avx2 support"
@echo "x86-64-sse41-popcnt > x86 64-bit with sse41 and popcnt support"
@echo "x86-64-modern > common modern CPU, currently x86-64-sse41-popcnt"
@echo "x86-64-modern > deprecated, currently x86-64-sse41-popcnt"
@echo "x86-64-ssse3 > x86 64-bit with ssse3 support"
@echo "x86-64-sse3-popcnt > x86 64-bit with sse3 and popcnt support"
@echo "x86-64-sse3-popcnt > x86 64-bit with sse3 compile and popcnt support"
@echo "x86-64 > x86 64-bit generic (with sse2 support)"
@echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt support"
@echo "x86-32-sse2 > x86 32-bit with sse2 support"
@echo "x86-32 > x86 32-bit generic (with mmx and sse support)"
@echo "x86-32 > x86 32-bit generic (with mmx compile support)"
@echo "ppc-64 > PPC 64-bit"
@echo "ppc-32 > PPC 32-bit"
@echo "armv7 > ARMv7 32-bit"
@echo "armv7-neon > ARMv7 32-bit with popcnt and neon"
@echo "armv8 > ARMv8 64-bit with popcnt and neon"
@echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support"
@echo "e2k > Elbrus 2000"
@echo "apple-silicon > Apple silicon ARM64"
@echo "general-64 > unspecified 64-bit"
@echo "general-32 > unspecified 32-bit"
@echo "riscv64 > RISC-V 64-bit"
@echo "loongarch64 > LoongArch 64-bit"
@echo ""
@echo "Supported compilers:"
@echo ""
@echo "gcc > Gnu compiler (default)"
@echo "mingw > Gnu compiler with MinGW under Windows"
@echo "gcc > GNU compiler (default)"
@echo "mingw > GNU compiler with MinGW under Windows"
@echo "clang > LLVM Clang compiler"
@echo "icc > Intel compiler"
@echo "icx > Intel oneAPI DPC++/C++ Compiler"
@echo "ndk > Google NDK to cross-compile for Android"
@echo ""
@echo "Simple examples. If you don't know what to do, you likely want to run: "
@echo "Simple examples. If you don't know what to do, you likely want to run one of: "
@echo ""
@echo "make -j build ARCH=x86-64 (A portable, slow compile for 64-bit systems)"
@echo "make -j build ARCH=x86-32 (A portable, slow compile for 32-bit systems)"
@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 ""
@echo "Advanced examples, for experienced users looking for performance: "
@echo "Advanced examples, for experienced users: "
@echo ""
@echo "make help ARCH=x86-64-bmi2"
@echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0"
@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 build ARCH=x86-64-ssse3 COMP=clang"
@echo ""
@echo "-------------------------------"
ifeq ($(SUPPORTED_ARCH)$(help_skip_sanity), true)
@echo "The selected architecture $(ARCH) will enable the following configuration: "
@$(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity
else
ifneq ($(SUPPORTED_ARCH), true)
@echo "Specify a supported architecture with the ARCH option for more details"
@echo ""
endif
.PHONY: help build profile-build strip install clean net objclean profileclean \
config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
clang-profile-use clang-profile-make
.PHONY: help analyze build profile-build strip install clean net \
objclean profileclean config-sanity \
icx-profile-use icx-profile-make \
gcc-profile-use gcc-profile-make \
clang-profile-use clang-profile-make FORCE \
format analyze
analyze: net config-sanity objclean
$(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS)
build: net config-sanity
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
@ -796,7 +881,8 @@ profile-build: net config-sanity objclean profileclean
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
@echo ""
@echo "Step 2/4. Running benchmark for pgo-build ..."
$(PGOBENCH) 2>&1 | tail -n 4
$(PGOBENCH) > PGOBENCH.out 2>&1
tail -n 4 PGOBENCH.out
@echo ""
@echo "Step 3/4. Building optimized executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
@ -817,30 +903,6 @@ install:
clean: objclean profileclean
@rm -f .depend *~ core
# evaluation network (nnue)
net:
$(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
@echo "Default net: $(nnuenet)"
$(eval nnuedownloadurl := https://tests.stockfishchess.org/api/nn/$(nnuenet))
$(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi))
@if test -f "$(nnuenet)"; then \
echo "Already available."; \
else \
if [ "x$(curl_or_wget)" = "x" ]; then \
echo "Automatic download failed: neither curl nor wget is installed. Install one of these tools or download the net manually"; exit 1; \
else \
echo "Downloading $(nnuedownloadurl)"; $(curl_or_wget) $(nnuedownloadurl) > $(nnuenet);\
fi; \
fi;
$(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi))
@if [ "x$(shasum_command)" != "x" ]; then \
if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
echo "Failed download or $(nnuenet) corrupted, please delete!"; exit 1; \
fi \
else \
echo "shasum / sha256sum not found, skipping net validation"; \
fi
# clean binaries and objects
objclean:
@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
@ -848,13 +910,71 @@ objclean:
# clean auxiliary profiling files
profileclean:
@rm -rf profdir
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s PGOBENCH.out
@rm -f stockfish.profdata *.profraw
@rm -f stockfish.*args*
@rm -f stockfish.*lt*
@rm -f stockfish.res
@rm -f ./-lstdc++.res
define fetch_network
@echo "Default net: $(nnuenet)"
@if [ "x$(curl_or_wget)" = "x" ]; then \
echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \
fi
@if [ "x$(shasum_command)" = "x" ]; then \
echo "shasum / sha256sum not found, skipping net validation"; \
elif test -f "$(nnuenet)"; then \
if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
echo "Removing invalid network"; rm -f $(nnuenet); \
fi; \
fi;
@for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \
if test -f "$(nnuenet)"; then \
echo "$(nnuenet) available : OK"; break; \
else \
if [ "x$(curl_or_wget)" != "x" ]; then \
echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\
else \
echo "No net found and download not possible"; exit 1;\
fi; \
fi; \
if [ "x$(shasum_command)" != "x" ]; then \
if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
echo "Removing failed download"; rm -f $(nnuenet); \
fi; \
fi; \
done
@if ! test -f "$(nnuenet)"; then \
echo "Failed to download $(nnuenet)."; \
fi;
@if [ "x$(shasum_command)" != "x" ]; then \
if [ "$(nnuenet)" = "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
echo "Network validated"; break; \
fi; \
fi;
endef
# set up shell variables for the net stuff
define netvariables
$(eval nnuenet := $(shell grep $(1) evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
$(eval nnuedownloadurl1 := https://tests.stockfishchess.org/api/nn/$(nnuenet))
$(eval nnuedownloadurl2 := https://github.com/official-stockfish/networks/raw/master/$(nnuenet))
$(eval curl_or_wget := $(shell if hash curl 2>/dev/null; then echo "curl -skL"; elif hash wget 2>/dev/null; then echo "wget -qO-"; fi))
$(eval shasum_command := $(shell if hash shasum 2>/dev/null; then echo "shasum -a 256 "; elif hash sha256sum 2>/dev/null; then echo "sha256sum "; fi))
endef
# evaluation network (nnue)
net:
$(call netvariables, EvalFileDefaultNameBig)
$(call fetch_network)
$(call netvariables, EvalFileDefaultNameSmall)
$(call fetch_network)
format:
$(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file
# default target
default:
help
@ -888,7 +1008,9 @@ config-sanity: net
@echo "vnni256: '$(vnni256)'"
@echo "vnni512: '$(vnni512)'"
@echo "neon: '$(neon)'"
@echo "dotprod: '$(dotprod)'"
@echo "arm_version: '$(arm_version)'"
@echo "target_windows: '$(target_windows)'"
@echo ""
@echo "Flags:"
@echo "CXX: $(CXX)"
@ -902,7 +1024,7 @@ config-sanity: net
@test "$(SUPPORTED_ARCH)" = "true"
@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)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64"
@test "$(bits)" = "32" || test "$(bits)" = "64"
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
@test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
@ -917,12 +1039,16 @@ config-sanity: net
@test "$(vnni256)" = "yes" || test "$(vnni256)" = "no"
@test "$(vnni512)" = "yes" || test "$(vnni512)" = "no"
@test "$(neon)" = "yes" || test "$(neon)" = "no"
@test "$(comp)" = "gcc" || test "$(comp)" = "icc" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \
@test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || test "$(comp)" = "clang" \
|| test "$(comp)" = "armv7a-linux-androideabi16-clang" || test "$(comp)" = "aarch64-linux-android21-clang"
$(EXE): $(OBJS)
+$(CXX) -o $@ $(OBJS) $(LDFLAGS)
# Force recompilation to ensure version info is up-to-date
misc.o: FORCE
FORCE:
clang-profile-make:
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
EXTRACXXFLAGS='-fprofile-instr-generate ' \
@ -951,18 +1077,22 @@ gcc-profile-use:
EXTRALDFLAGS='-lgcov' \
all
icc-profile-make:
@mkdir -p profdir
icx-profile-make:
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \
EXTRACXXFLAGS='-fprofile-instr-generate ' \
EXTRALDFLAGS=' -fprofile-instr-generate' \
all
icc-profile-use:
icx-profile-use:
$(XCRUN) llvm-profdata merge -output=stockfish.profdata *.profraw
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \
EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \
EXTRALDFLAGS='-fprofile-use ' \
all
.depend: $(SRCS)
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean config-sanity))
-include .depend
endif

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,18 +16,17 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "benchmark.h"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <istream>
#include <vector>
#include "position.h"
using namespace std;
namespace {
const vector<string> Defaults = {
// clang-format off
const std::vector<std::string> Defaults = {
"setoption name UCI_Chess960 value false",
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10",
@ -90,86 +89,75 @@ const vector<string> Defaults = {
"nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1",
"setoption name UCI_Chess960 value false"
};
// clang-format on
} // namespace
} // namespace
namespace Stockfish {
namespace Stockfish::Benchmark {
/// setup_bench() builds a list of UCI commands to be run by bench. There
/// are five parameters: TT size in MB, number of search threads that
/// should be used, the limit value spent for each position, a file name
/// where to look for positions in FEN format, the type of the limit:
/// depth, perft, nodes and movetime (in millisecs), and evaluation type
/// mixed (default), classical, NNUE.
///
/// bench -> search default positions up to depth 13
/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB)
/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec
/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each
/// bench 16 1 5 default perft -> run a perft 5 on default positions
// Builds a list of UCI commands to be run by bench. There
// are five parameters: TT size in MB, number of search threads that
// should be used, the limit value spent for each position, a file name
// where to look for positions in FEN format, and the type of the limit:
// depth, perft, nodes and movetime (in milliseconds). Examples:
//
// bench : search default positions up to depth 13
// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB)
// bench 64 1 100000 default nodes : search default positions for 100K nodes each
// bench 64 4 5000 current movetime : search current position with 4 threads for 5 sec
// bench 16 1 5 blah perft : run a perft 5 on positions in file "blah"
std::vector<std::string> setup_bench(const std::string& currentFen, std::istream& is) {
vector<string> setup_bench(const Position& current, istream& is) {
std::vector<std::string> fens, list;
std::string go, token;
vector<string> fens, list;
string go, token;
// Assign default values to missing arguments
std::string ttSize = (is >> token) ? token : "16";
std::string threads = (is >> token) ? token : "1";
std::string limit = (is >> token) ? token : "13";
std::string fenFile = (is >> token) ? token : "default";
std::string limitType = (is >> token) ? token : "depth";
// Assign default values to missing arguments
string ttSize = (is >> token) ? token : "16";
string threads = (is >> token) ? token : "1";
string limit = (is >> token) ? token : "13";
string fenFile = (is >> token) ? token : "default";
string limitType = (is >> token) ? token : "depth";
string evalType = (is >> token) ? token : "mixed";
go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
go = limitType == "eval" ? "eval" : "go " + limitType + " " + limit;
if (fenFile == "default")
fens = Defaults;
if (fenFile == "default")
fens = Defaults;
else if (fenFile == "current")
fens.push_back(currentFen);
else if (fenFile == "current")
fens.push_back(current.fen());
else
{
std::string fen;
std::ifstream file(fenFile);
else
{
string fen;
ifstream file(fenFile);
if (!file.is_open())
{
std::cerr << "Unable to open file " << fenFile << std::endl;
exit(EXIT_FAILURE);
}
if (!file.is_open())
{
cerr << "Unable to open file " << fenFile << endl;
exit(EXIT_FAILURE);
}
while (getline(file, fen))
if (!fen.empty())
fens.push_back(fen);
while (getline(file, fen))
if (!fen.empty())
fens.push_back(fen);
file.close();
}
file.close();
}
list.emplace_back("setoption name Threads value " + threads);
list.emplace_back("setoption name Hash value " + ttSize);
list.emplace_back("ucinewgame");
list.emplace_back("setoption name Threads value " + threads);
list.emplace_back("setoption name Hash value " + ttSize);
list.emplace_back("ucinewgame");
for (const std::string& fen : fens)
if (fen.find("setoption") != std::string::npos)
list.emplace_back(fen);
else
{
list.emplace_back("position fen " + fen);
list.emplace_back(go);
}
size_t posCounter = 0;
for (const string& fen : fens)
if (fen.find("setoption") != string::npos)
list.emplace_back(fen);
else
{
if (evalType == "classical" || (evalType == "mixed" && posCounter % 2 == 0))
list.emplace_back("setoption name Use NNUE value false");
else if (evalType == "NNUE" || (evalType == "mixed" && posCounter % 2 != 0))
list.emplace_back("setoption name Use NNUE value true");
list.emplace_back("position fen " + fen);
list.emplace_back(go);
++posCounter;
}
list.emplace_back("setoption name Use NNUE value true");
return list;
return list;
}
} // namespace Stockfish
} // namespace Stockfish

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,23 +16,17 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef BENCHMARK_H_INCLUDED
#define BENCHMARK_H_INCLUDED
#ifndef PSQT_H_INCLUDED
#define PSQT_H_INCLUDED
#include <iosfwd>
#include <string>
#include <vector>
namespace Stockfish::Benchmark {
#include "types.h"
std::vector<std::string> setup_bench(const std::string&, std::istream&);
} // namespace Stockfish
namespace Stockfish::PSQT
{
extern Score psq[PIECE_NB][SQUARE_NB];
// Fill psqt array from a set of internally linked parameters
extern void init();
} // namespace Stockfish::PSQT
#endif // PSQT_H_INCLUDED
#endif // #ifndef BENCHMARK_H_INCLUDED

View file

@ -1,172 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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 <cassert>
#include <vector>
#include <bitset>
#include "bitboard.h"
#include "types.h"
namespace Stockfish {
namespace {
// There are 24 possible pawn squares: files A to D and ranks from 2 to 7.
// Positions with the pawn on files E to H will be mirrored before probing.
constexpr unsigned MAX_INDEX = 2*24*64*64; // stm * psq * wksq * bksq = 196608
std::bitset<MAX_INDEX> KPKBitbase;
// A KPK bitbase index is an integer in [0, IndexMax] range
//
// Information is mapped in a way that minimizes the number of iterations:
//
// bit 0- 5: white king square (from SQ_A1 to SQ_H8)
// bit 6-11: black king square (from SQ_A1 to SQ_H8)
// bit 12: side to move (WHITE or BLACK)
// bit 13-14: white pawn file (from FILE_A to FILE_D)
// bit 15-17: white pawn RANK_7 - rank (from RANK_7 - RANK_7 to RANK_7 - RANK_2)
unsigned index(Color stm, Square bksq, Square wksq, Square psq) {
return int(wksq) | (bksq << 6) | (stm << 12) | (file_of(psq) << 13) | ((RANK_7 - rank_of(psq)) << 15);
}
enum Result {
INVALID = 0,
UNKNOWN = 1,
DRAW = 2,
WIN = 4
};
Result& operator|=(Result& r, Result v) { return r = Result(r | v); }
struct KPKPosition {
KPKPosition() = default;
explicit KPKPosition(unsigned idx);
operator Result() const { return result; }
Result classify(const std::vector<KPKPosition>& db);
Color stm;
Square ksq[COLOR_NB], psq;
Result result;
};
} // namespace
bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color stm) {
assert(file_of(wpsq) <= FILE_D);
return KPKBitbase[index(stm, bksq, wksq, wpsq)];
}
void Bitbases::init() {
std::vector<KPKPosition> db(MAX_INDEX);
unsigned idx, repeat = 1;
// Initialize db with known win / draw positions
for (idx = 0; idx < MAX_INDEX; ++idx)
db[idx] = KPKPosition(idx);
// Iterate through the positions until none of the unknown positions can be
// changed to either wins or draws (15 cycles needed).
while (repeat)
for (repeat = idx = 0; idx < MAX_INDEX; ++idx)
repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN);
// Fill the bitbase with the decisive results
for (idx = 0; idx < MAX_INDEX; ++idx)
if (db[idx] == WIN)
KPKBitbase.set(idx);
}
namespace {
KPKPosition::KPKPosition(unsigned idx) {
ksq[WHITE] = Square((idx >> 0) & 0x3F);
ksq[BLACK] = Square((idx >> 6) & 0x3F);
stm = Color ((idx >> 12) & 0x01);
psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7)));
// Invalid if two pieces are on the same square or if a king can be captured
if ( distance(ksq[WHITE], ksq[BLACK]) <= 1
|| ksq[WHITE] == psq
|| ksq[BLACK] == psq
|| (stm == WHITE && (pawn_attacks_bb(WHITE, psq) & ksq[BLACK])))
result = INVALID;
// Win if the pawn can be promoted without getting captured
else if ( stm == WHITE
&& rank_of(psq) == RANK_7
&& ksq[WHITE] != psq + NORTH
&& ( distance(ksq[BLACK], psq + NORTH) > 1
|| (distance(ksq[WHITE], psq + NORTH) == 1)))
result = WIN;
// Draw if it is stalemate or the black king can capture the pawn
else if ( stm == BLACK
&& ( !(attacks_bb<KING>(ksq[BLACK]) & ~(attacks_bb<KING>(ksq[WHITE]) | pawn_attacks_bb(WHITE, psq)))
|| (attacks_bb<KING>(ksq[BLACK]) & ~attacks_bb<KING>(ksq[WHITE]) & psq)))
result = DRAW;
// Position will be classified later
else
result = UNKNOWN;
}
Result KPKPosition::classify(const std::vector<KPKPosition>& db) {
// White to move: If one move leads to a position classified as WIN, the result
// of the current position is WIN. If all moves lead to positions classified
// as DRAW, the current position is classified as DRAW, otherwise the current
// position is classified as UNKNOWN.
//
// Black to move: If one move leads to a position classified as DRAW, the result
// of the current position is DRAW. If all moves lead to positions classified
// as WIN, the position is classified as WIN, otherwise the current position is
// classified as UNKNOWN.
const Result Good = (stm == WHITE ? WIN : DRAW);
const Result Bad = (stm == WHITE ? DRAW : WIN);
Result r = INVALID;
Bitboard b = attacks_bb<KING>(ksq[stm]);
while (b)
r |= stm == WHITE ? db[index(BLACK, ksq[BLACK], pop_lsb(b), psq)]
: db[index(WHITE, pop_lsb(b), ksq[WHITE], psq)];
if (stm == WHITE)
{
if (rank_of(psq) < RANK_7) // Single push
r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH)];
if ( rank_of(psq) == RANK_2 // Double push
&& psq + NORTH != ksq[WHITE]
&& psq + NORTH != ksq[BLACK])
r |= db[index(BLACK, ksq[BLACK], ksq[WHITE], psq + NORTH + NORTH)];
}
return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad;
}
} // namespace
} // namespace Stockfish

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,10 +16,12 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "bitboard.h"
#include <algorithm>
#include <bitset>
#include <initializer_list>
#include "bitboard.h"
#include "misc.h"
namespace Stockfish {
@ -27,7 +29,6 @@ namespace Stockfish {
uint8_t PopCnt16[1 << 16];
uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
Bitboard SquareBB[SQUARE_NB];
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
@ -38,93 +39,86 @@ Magic BishopMagics[SQUARE_NB];
namespace {
Bitboard RookTable[0x19000]; // To store rook attacks
Bitboard BishopTable[0x1480]; // To store bishop attacks
Bitboard RookTable[0x19000]; // To store rook attacks
Bitboard BishopTable[0x1480]; // To store bishop attacks
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
}
/// safe_destination() returns the bitboard of target square for the given step
/// from the given square. If the step is off the board, returns empty bitboard.
inline Bitboard safe_destination(Square s, int step) {
// Returns the bitboard of target square for the given step
// from the given square. If the step is off the board, returns empty bitboard.
Bitboard safe_destination(Square s, int step) {
Square to = Square(s + step);
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
}
}
/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable
/// to be printed to standard output. Useful for debugging.
// Returns an ASCII representation of a bitboard suitable
// to be printed to standard output. Useful for debugging.
std::string Bitboards::pretty(Bitboard b) {
std::string s = "+---+---+---+---+---+---+---+---+\n";
std::string s = "+---+---+---+---+---+---+---+---+\n";
for (Rank r = RANK_8; r >= RANK_1; --r)
{
for (File f = FILE_A; f <= FILE_H; ++f)
s += b & make_square(f, r) ? "| X " : "| ";
for (Rank r = RANK_8; r >= RANK_1; --r)
{
for (File f = FILE_A; f <= FILE_H; ++f)
s += b & make_square(f, r) ? "| X " : "| ";
s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
}
s += " a b c d e f g h\n";
s += "| " + std::to_string(1 + r) + "\n+---+---+---+---+---+---+---+---+\n";
}
s += " a b c d e f g h\n";
return s;
return s;
}
/// Bitboards::init() initializes various bitboard tables. It is called at
/// startup and relies on global objects to be already zero-initialized.
// 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 (unsigned i = 0; i < (1 << 16); ++i)
PopCnt16[i] = uint8_t(std::bitset<16>(i).count());
for (Square s = SQ_A1; s <= SQ_H8; ++s)
SquareBB[s] = (1ULL << s);
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));
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, RookMagics);
init_magics(BISHOP, BishopTable, BishopMagics);
init_magics(ROOK, RookTable, RookMagics);
init_magics(BISHOP, BishopTable, BishopMagics);
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{
PawnAttacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
PawnAttacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{
PawnAttacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
PawnAttacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9})
PseudoAttacks[KING][s1] |= safe_destination(s1, step);
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} )
PseudoAttacks[KING][s1] |= safe_destination(s1, step);
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17})
PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step);
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} )
PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step);
PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1] = attacks_bb<ROOK>(s1, 0);
PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0);
for (PieceType pt : { BISHOP, ROOK })
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
{
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)));
}
BetweenBB[s1][s2] |= s2;
}
}
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)));
}
BetweenBB[s1][s2] |= s2;
}
}
}
namespace {
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
Bitboard attacks = 0;
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
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))
@ -135,22 +129,21 @@ namespace {
}
return attacks;
}
}
// init_magics() computes all rook and bishop attacks at startup. Magic
// bitboards are used to look up attacks of sliding pieces. As a reference see
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
// called "fancy" approach.
void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
// Computes all rook and bishop attacks at startup. Magic
// bitboards are used to look up attacks of sliding pieces. As a reference see
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
// called "fancy" approach.
void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
// 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 } };
int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
{728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
Bitboard occupancy[4096], reference[4096], edges, b;
int epoch[4096] = {}, cnt = 0, size = 0;
int epoch[4096] = {}, cnt = 0, size = 0;
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
@ -163,8 +156,8 @@ namespace {
// 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];
m.mask = sliding_attack(pt, s, 0) & ~edges;
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
m.mask = sliding_attack(pt, s, 0) & ~edges;
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
// Set the offset for the attacks table of the square. We have individual
// table sizes for each square with "Fancy Magic Bitboards".
@ -173,7 +166,8 @@ namespace {
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and
// store the corresponding sliding attack bitboard in reference[].
b = size = 0;
do {
do
{
occupancy[size] = b;
reference[size] = sliding_attack(pt, s, b);
@ -191,9 +185,9 @@ namespace {
// 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 (int i = 0; i < size;)
{
for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; )
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
@ -208,7 +202,7 @@ namespace {
if (epoch[idx] < cnt)
{
epoch[idx] = cnt;
epoch[idx] = cnt;
m.attacks[idx] = reference[i];
}
else if (m.attacks[idx] != reference[i])
@ -216,7 +210,7 @@ namespace {
}
}
}
}
}
}
} // namespace Stockfish
} // namespace Stockfish

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,28 +19,23 @@
#ifndef BITBOARD_H_INCLUDED
#define BITBOARD_H_INCLUDED
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <string>
#include "types.h"
namespace Stockfish {
namespace Bitbases {
void init();
bool probe(Square wksq, Square wpsq, Square bksq, Color us);
} // namespace Stockfish::Bitbases
namespace Bitboards {
void init();
void init();
std::string pretty(Bitboard b);
} // namespace Stockfish::Bitboards
constexpr Bitboard AllSquares = ~Bitboard(0);
constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL;
} // namespace Stockfish::Bitboards
constexpr Bitboard FileABB = 0x0101010101010101ULL;
constexpr Bitboard FileBBB = FileABB << 1;
@ -60,392 +55,317 @@ constexpr Bitboard Rank6BB = Rank1BB << (8 * 5);
constexpr Bitboard Rank7BB = Rank1BB << (8 * 6);
constexpr Bitboard Rank8BB = Rank1BB << (8 * 7);
constexpr Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB;
constexpr Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB;
constexpr Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB;
constexpr Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB);
constexpr Bitboard KingFlank[FILE_NB] = {
QueenSide ^ FileDBB, QueenSide, QueenSide,
CenterFiles, CenterFiles,
KingSide, KingSide, KingSide ^ FileEBB
};
extern uint8_t PopCnt16[1 << 16];
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
extern Bitboard SquareBB[SQUARE_NB];
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB];
/// Magic holds all magic bitboards relevant data for a single square
// Magic holds all magic bitboards relevant data for a single square
struct Magic {
Bitboard mask;
Bitboard magic;
Bitboard* attacks;
unsigned shift;
Bitboard mask;
Bitboard magic;
Bitboard* attacks;
unsigned shift;
// Compute the attack's index using the 'magic bitboards' approach
unsigned index(Bitboard occupied) const {
// Compute the attack's index using the 'magic bitboards' approach
unsigned index(Bitboard occupied) const {
if (HasPext)
return unsigned(pext(occupied, mask));
if (HasPext)
return unsigned(pext(occupied, mask));
if (Is64Bit)
return unsigned(((occupied & mask) * magic) >> shift);
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;
}
unsigned lo = unsigned(occupied) & unsigned(mask);
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
}
};
extern Magic RookMagics[SQUARE_NB];
extern Magic BishopMagics[SQUARE_NB];
inline Bitboard square_bb(Square s) {
assert(is_ok(s));
return SquareBB[s];
constexpr Bitboard square_bb(Square s) {
assert(is_ok(s));
return (1ULL << s);
}
/// Overloads of bitwise operators between a Bitboard and a Square for testing
/// whether a given bit is set in a bitboard, and for setting and clearing bits.
// Overloads of bitwise operators between a Bitboard and a Square for testing
// whether a given bit is set in a bitboard, and for setting and clearing bits.
inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); }
inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); }
inline Bitboard operator^( Bitboard b, Square s) { return b ^ square_bb(s); }
inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); }
inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); }
inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); }
inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); }
inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); }
inline Bitboard operator&(Square s, Bitboard b) { return b & s; }
inline Bitboard operator|(Square s, Bitboard b) { return b | s; }
inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; }
inline Bitboard operator&(Square s, Bitboard b) { return b & s; }
inline Bitboard operator|(Square s, Bitboard b) { return b | s; }
inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; }
inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; }
inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; }
constexpr bool more_than_one(Bitboard b) {
return b & (b - 1);
}
constexpr bool more_than_one(Bitboard b) { return b & (b - 1); }
constexpr bool opposite_colors(Square s1, Square s2) {
return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1;
}
// rank_bb() and file_bb() return a bitboard representing all the squares on
// the given file or rank.
constexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); }
constexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); }
constexpr Bitboard file_bb(File f) { return FileABB << f; }
constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); }
/// rank_bb() and file_bb() return a bitboard representing all the squares on
/// the given file or rank.
constexpr Bitboard rank_bb(Rank r) {
return Rank1BB << (8 * r);
}
constexpr Bitboard rank_bb(Square s) {
return rank_bb(rank_of(s));
}
constexpr Bitboard file_bb(File f) {
return FileABB << f;
}
constexpr Bitboard file_bb(Square s) {
return file_bb(file_of(s));
}
/// shift() moves a bitboard one or two steps as specified by the direction D
// Moves a bitboard one or two steps as specified by the direction D
template<Direction D>
constexpr Bitboard shift(Bitboard b) {
return D == NORTH ? b << 8 : D == SOUTH ? b >> 8
: D == NORTH+NORTH? b <<16 : D == SOUTH+SOUTH? b >>16
: D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1
: D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7
: D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9
: 0;
return D == NORTH ? b << 8
: D == SOUTH ? b >> 8
: D == NORTH + NORTH ? b << 16
: D == SOUTH + SOUTH ? b >> 16
: D == EAST ? (b & ~FileHBB) << 1
: D == WEST ? (b & ~FileABB) >> 1
: D == NORTH_EAST ? (b & ~FileHBB) << 9
: D == NORTH_WEST ? (b & ~FileABB) << 7
: D == SOUTH_EAST ? (b & ~FileHBB) >> 7
: D == SOUTH_WEST ? (b & ~FileABB) >> 9
: 0;
}
/// pawn_attacks_bb() returns the squares attacked by pawns of the given color
/// from the squares in the given bitboard.
// Returns the squares attacked by pawns of the given color
// from the squares in the given bitboard.
template<Color C>
constexpr Bitboard pawn_attacks_bb(Bitboard b) {
return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
: shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
: shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
}
inline Bitboard pawn_attacks_bb(Color c, Square s) {
assert(is_ok(s));
return PawnAttacks[c][s];
assert(is_ok(s));
return PawnAttacks[c][s];
}
/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the
/// given color from the squares in the given bitboard.
template<Color C>
constexpr Bitboard pawn_double_attacks_bb(Bitboard b) {
return C == WHITE ? shift<NORTH_WEST>(b) & shift<NORTH_EAST>(b)
: shift<SOUTH_WEST>(b) & shift<SOUTH_EAST>(b);
}
/// adjacent_files_bb() returns a bitboard representing all the squares on the
/// adjacent files of a given square.
constexpr Bitboard adjacent_files_bb(Square s) {
return shift<EAST>(file_bb(s)) | shift<WEST>(file_bb(s));
}
/// line_bb() 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.
// 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];
assert(is_ok(s1) && is_ok(s2));
return LineBB[s1][s2];
}
/// between_bb(s1, s2) 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.
// 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) {
assert(is_ok(s1) && is_ok(s2));
return BetweenBB[s1][s2];
assert(is_ok(s1) && is_ok(s2));
return BetweenBB[s1][s2];
}
// Returns true if the squares s1, s2 and s3 are aligned either on a
// straight or on a diagonal line.
inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; }
/// forward_ranks_bb() returns a bitboard representing the squares on the ranks in
/// front of the given one, from the point of view of the given color. For instance,
/// forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2.
constexpr Bitboard forward_ranks_bb(Color c, Square s) {
return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s)
: ~Rank8BB >> 8 * relative_rank(BLACK, s);
// 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 T1 = Square>
inline int distance(Square x, Square y);
template<>
inline int distance<File>(Square x, Square y) {
return std::abs(file_of(x) - file_of(y));
}
/// forward_file_bb() returns a bitboard representing all the squares along the
/// line in front of the given one, from the point of view of the given color.
constexpr Bitboard forward_file_bb(Color c, Square s) {
return forward_ranks_bb(c, s) & file_bb(s);
template<>
inline int distance<Rank>(Square x, Square y) {
return std::abs(rank_of(x) - rank_of(y));
}
/// pawn_attack_span() returns a bitboard representing all the squares that can
/// be attacked by a pawn of the given color when it moves along its file, starting
/// from the given square.
constexpr Bitboard pawn_attack_span(Color c, Square s) {
return forward_ranks_bb(c, s) & adjacent_files_bb(s);
template<>
inline int distance<Square>(Square x, Square y) {
return SquareDistance[x][y];
}
/// passed_pawn_span() returns a bitboard which can be used to test if a pawn of
/// the given color and on the given square is a passed pawn.
constexpr Bitboard passed_pawn_span(Color c, Square s) {
return pawn_attack_span(c, s) | forward_file_bb(c, s);
}
/// aligned() returns true if the squares s1, s2 and s3 are aligned either on a
/// straight or on a diagonal line.
inline bool aligned(Square s1, Square s2, Square s3) {
return line_bb(s1, s2) & s3;
}
/// distance() functions return the distance between x and y, defined as the
/// number of steps for a king in x to reach y.
template<typename T1 = Square> inline int distance(Square x, Square y);
template<> inline int distance<File>(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); }
template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
template<> inline int distance<Square>(Square x, Square y) { return SquareDistance[x][y]; }
inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
inline int edge_distance(Rank r) { return std::min(r, Rank(RANK_8 - r)); }
/// attacks_bb(Square) returns the pseudo attacks of the give piece type
/// assuming an empty board.
// Returns the pseudo attacks of the given piece type
// assuming an empty board.
template<PieceType Pt>
inline Bitboard attacks_bb(Square s) {
assert((Pt != PAWN) && (is_ok(s)));
return PseudoAttacks[Pt][s];
assert((Pt != PAWN) && (is_ok(s)));
return PseudoAttacks[Pt][s];
}
/// attacks_bb(Square, Bitboard) 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.
// 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)));
assert((Pt != PAWN) && (is_ok(s)));
switch (Pt)
{
case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
case ROOK : return RookMagics[s].attacks[ RookMagics[s].index(occupied)];
case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
default : return PseudoAttacks[Pt][s];
}
switch (Pt)
{
case BISHOP :
return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
case ROOK :
return RookMagics[s].attacks[RookMagics[s].index(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)));
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];
}
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];
}
}
/// popcount() counts the number of non-zero bits in a bitboard
// Counts the number of non-zero bits in a bitboard.
inline int popcount(Bitboard b) {
#ifndef USE_POPCNT
union { Bitboard bb; uint16_t u[4]; } v = { b };
return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]];
union {
Bitboard bb;
uint16_t u[4];
} v = {b};
return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]];
#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)
#elif defined(_MSC_VER)
return (int)_mm_popcnt_u64(b);
return int(_mm_popcnt_u64(b));
#else // Assumed gcc or compatible compiler
#else // Assumed gcc or compatible compiler
return __builtin_popcountll(b);
return __builtin_popcountll(b);
#endif
}
/// lsb() and msb() return the least/most significant bit in a non-zero bitboard
#if defined(__GNUC__) // GCC, Clang, ICC
// Returns the least significant bit in a non-zero bitboard.
inline Square lsb(Bitboard b) {
assert(b);
return Square(__builtin_ctzll(b));
}
assert(b);
inline Square msb(Bitboard b) {
assert(b);
return Square(63 ^ __builtin_clzll(b));
}
#if defined(__GNUC__) // GCC, Clang, ICX
#elif defined(_MSC_VER) // MSVC
return Square(__builtin_ctzll(b));
#ifdef _WIN64 // MSVC, WIN64
#elif defined(_MSC_VER)
#ifdef _WIN64 // MSVC, WIN64
inline Square lsb(Bitboard b) {
assert(b);
unsigned long idx;
_BitScanForward64(&idx, b);
return (Square) idx;
}
unsigned long idx;
_BitScanForward64(&idx, b);
return Square(idx);
inline Square msb(Bitboard b) {
assert(b);
unsigned long idx;
_BitScanReverse64(&idx, b);
return (Square) idx;
}
#else // MSVC, WIN32
inline Square lsb(Bitboard b) {
assert(b);
unsigned long idx;
if (b & 0xffffffff) {
_BitScanForward(&idx, int32_t(b));
return Square(idx);
} else {
_BitScanForward(&idx, int32_t(b >> 32));
return Square(idx + 32);
}
}
inline Square msb(Bitboard b) {
assert(b);
unsigned long idx;
if (b >> 32) {
_BitScanReverse(&idx, int32_t(b >> 32));
return Square(idx + 32);
} else {
_BitScanReverse(&idx, int32_t(b));
return Square(idx);
}
}
#endif
#else // MSVC, WIN32
unsigned long idx;
if (b & 0xffffffff)
{
_BitScanForward(&idx, int32_t(b));
return Square(idx);
}
else
{
_BitScanForward(&idx, int32_t(b >> 32));
return Square(idx + 32);
}
#endif
#else // Compiler is neither GCC nor MSVC compatible
#error "Compiler not supported."
#error "Compiler not supported."
#endif
}
/// least_significant_square_bb() returns the bitboard of the least significant
/// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)).
// Returns the most significant bit in a non-zero bitboard.
inline Square msb(Bitboard b) {
assert(b);
#if defined(__GNUC__) // GCC, Clang, ICX
return Square(63 ^ __builtin_clzll(b));
#elif defined(_MSC_VER)
#ifdef _WIN64 // MSVC, WIN64
unsigned long idx;
_BitScanReverse64(&idx, b);
return Square(idx);
#else // MSVC, WIN32
unsigned long idx;
if (b >> 32)
{
_BitScanReverse(&idx, int32_t(b >> 32));
return Square(idx + 32);
}
else
{
_BitScanReverse(&idx, int32_t(b));
return Square(idx);
}
#endif
#else // Compiler is neither GCC nor MSVC compatible
#error "Compiler not supported."
#endif
}
// Returns the bitboard of the least significant
// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)).
inline Bitboard least_significant_square_bb(Bitboard b) {
assert(b);
return b & -b;
assert(b);
return b & -b;
}
/// pop_lsb() finds and clears the least significant bit in a non-zero bitboard
// Finds and clears the least significant bit in a non-zero bitboard.
inline Square pop_lsb(Bitboard& b) {
assert(b);
const Square s = lsb(b);
b &= b - 1;
return s;
assert(b);
const Square s = lsb(b);
b &= b - 1;
return s;
}
} // namespace Stockfish
/// frontmost_sq() returns the most advanced square for the given color,
/// requires a non-zero bitboard.
inline Square frontmost_sq(Color c, Bitboard b) {
assert(b);
return c == WHITE ? msb(b) : lsb(b);
}
} // namespace Stockfish
#endif // #ifndef BITBOARD_H_INCLUDED
#endif // #ifndef BITBOARD_H_INCLUDED

View file

@ -1,747 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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 <cassert>
#include "bitboard.h"
#include "endgame.h"
#include "movegen.h"
namespace Stockfish {
namespace {
// Used to drive the king towards the edge of the board
// in KX vs K and KQ vs KR endgames.
// Values range from 27 (center squares) to 90 (in the corners)
inline int push_to_edge(Square s) {
int rd = edge_distance(rank_of(s)), fd = edge_distance(file_of(s));
return 90 - (7 * fd * fd / 2 + 7 * rd * rd / 2);
}
// Used to drive the king towards A1H8 corners in KBN vs K endgames.
// Values range from 0 on A8H1 diagonal to 7 in A1H8 corners
inline int push_to_corner(Square s) {
return abs(7 - rank_of(s) - file_of(s));
}
// Drive a piece close to or away from another piece
inline int push_close(Square s1, Square s2) { return 140 - 20 * distance(s1, s2); }
inline int push_away(Square s1, Square s2) { return 120 - push_close(s1, s2); }
#ifndef NDEBUG
bool verify_material(const Position& pos, Color c, Value npm, int pawnsCnt) {
return pos.non_pawn_material(c) == npm && pos.count<PAWN>(c) == pawnsCnt;
}
#endif
// Map the square as if strongSide is white and strongSide's only pawn
// is on the left half of the board.
Square normalize(const Position& pos, Color strongSide, Square sq) {
assert(pos.count<PAWN>(strongSide) == 1);
if (file_of(pos.square<PAWN>(strongSide)) >= FILE_E)
sq = flip_file(sq);
return strongSide == WHITE ? sq : flip_rank(sq);
}
} // namespace
namespace Endgames {
std::pair<Map<Value>, Map<ScaleFactor>> maps;
void init() {
add<KPK>("KPK");
add<KNNK>("KNNK");
add<KBNK>("KBNK");
add<KRKP>("KRKP");
add<KRKB>("KRKB");
add<KRKN>("KRKN");
add<KQKP>("KQKP");
add<KQKR>("KQKR");
add<KNNKP>("KNNKP");
add<KRPKR>("KRPKR");
add<KRPKB>("KRPKB");
add<KBPKB>("KBPKB");
add<KBPKN>("KBPKN");
add<KBPPKB>("KBPPKB");
add<KRPPKRP>("KRPPKRP");
}
}
/// Mate with KX vs K. This function is used to evaluate positions with
/// king and plenty of material vs a lone king. It simply gives the
/// attacking side a bonus for driving the defending king towards the edge
/// of the board, and for keeping the distance between the two kings small.
template<>
Value Endgame<KXK>::operator()(const Position& pos) const {
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
assert(!pos.checkers()); // Eval is never called when in check
// Stalemate detection with lone king
if (pos.side_to_move() == weakSide && !MoveList<LEGAL>(pos).size())
return VALUE_DRAW;
Square strongKing = pos.square<KING>(strongSide);
Square weakKing = pos.square<KING>(weakSide);
Value result = pos.non_pawn_material(strongSide)
+ pos.count<PAWN>(strongSide) * PawnValueEg
+ push_to_edge(weakKing)
+ push_close(strongKing, weakKing);
if ( pos.count<QUEEN>(strongSide)
|| pos.count<ROOK>(strongSide)
||(pos.count<BISHOP>(strongSide) && pos.count<KNIGHT>(strongSide))
|| ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares)
&& (pos.pieces(strongSide, BISHOP) & DarkSquares)))
result = std::min(result + VALUE_KNOWN_WIN, VALUE_TB_WIN_IN_MAX_PLY - 1);
return strongSide == pos.side_to_move() ? result : -result;
}
/// Mate with KBN vs K. This is similar to KX vs K, but we have to drive the
/// defending king towards a corner square that our bishop attacks.
template<>
Value Endgame<KBNK>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, KnightValueMg + BishopValueMg, 0));
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
Square strongKing = pos.square<KING>(strongSide);
Square strongBishop = pos.square<BISHOP>(strongSide);
Square weakKing = pos.square<KING>(weakSide);
// If our bishop does not attack A1/H8, we flip the enemy king square
// to drive to opposite corners (A8/H1).
Value result = (VALUE_KNOWN_WIN + 3520)
+ push_close(strongKing, weakKing)
+ 420 * push_to_corner(opposite_colors(strongBishop, SQ_A1) ? flip_file(weakKing) : weakKing);
assert(abs(result) < VALUE_TB_WIN_IN_MAX_PLY);
return strongSide == pos.side_to_move() ? result : -result;
}
/// KP vs K. This endgame is evaluated with the help of a bitbase
template<>
Value Endgame<KPK>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, VALUE_ZERO, 1));
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
// Assume strongSide is white and the pawn is on files A-D
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
if (!Bitbases::probe(strongKing, strongPawn, weakKing, us))
return VALUE_DRAW;
Value result = VALUE_KNOWN_WIN + PawnValueEg + Value(rank_of(strongPawn));
return strongSide == pos.side_to_move() ? result : -result;
}
/// KR vs KP. This is a somewhat tricky endgame to evaluate precisely without
/// a bitbase. The function below returns drawish scores when the pawn is
/// far advanced with support of the king, while the attacking king is far
/// away.
template<>
Value Endgame<KRKP>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, RookValueMg, 0));
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
Square strongKing = pos.square<KING>(strongSide);
Square weakKing = pos.square<KING>(weakSide);
Square strongRook = pos.square<ROOK>(strongSide);
Square weakPawn = pos.square<PAWN>(weakSide);
Square queeningSquare = make_square(file_of(weakPawn), relative_rank(weakSide, RANK_8));
Value result;
// If the stronger side's king is in front of the pawn, it's a win
if (forward_file_bb(strongSide, strongKing) & weakPawn)
result = RookValueEg - distance(strongKing, weakPawn);
// If the weaker side's king is too far from the pawn and the rook,
// it's a win.
else if ( distance(weakKing, weakPawn) >= 3 + (pos.side_to_move() == weakSide)
&& distance(weakKing, strongRook) >= 3)
result = RookValueEg - distance(strongKing, weakPawn);
// If the pawn is far advanced and supported by the defending king,
// the position is drawish
else if ( relative_rank(strongSide, weakKing) <= RANK_3
&& distance(weakKing, weakPawn) == 1
&& relative_rank(strongSide, strongKing) >= RANK_4
&& distance(strongKing, weakPawn) > 2 + (pos.side_to_move() == strongSide))
result = Value(80) - 8 * distance(strongKing, weakPawn);
else
result = Value(200) - 8 * ( distance(strongKing, weakPawn + pawn_push(weakSide))
- distance(weakKing, weakPawn + pawn_push(weakSide))
- distance(weakPawn, queeningSquare));
return strongSide == pos.side_to_move() ? result : -result;
}
/// KR vs KB. This is very simple, and always returns drawish scores. The
/// score is slightly bigger when the defending king is close to the edge.
template<>
Value Endgame<KRKB>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, RookValueMg, 0));
assert(verify_material(pos, weakSide, BishopValueMg, 0));
Value result = Value(push_to_edge(pos.square<KING>(weakSide)));
return strongSide == pos.side_to_move() ? result : -result;
}
/// KR vs KN. The attacking side has slightly better winning chances than
/// in KR vs KB, particularly if the king and the knight are far apart.
template<>
Value Endgame<KRKN>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, RookValueMg, 0));
assert(verify_material(pos, weakSide, KnightValueMg, 0));
Square weakKing = pos.square<KING>(weakSide);
Square weakKnight = pos.square<KNIGHT>(weakSide);
Value result = Value(push_to_edge(weakKing) + push_away(weakKing, weakKnight));
return strongSide == pos.side_to_move() ? result : -result;
}
/// KQ vs KP. In general, this is a win for the stronger side, but there are a
/// few important exceptions. A pawn on 7th rank and on the A,C,F or H files
/// with a king positioned next to it can be a draw, so in that case, we only
/// use the distance between the kings.
template<>
Value Endgame<KQKP>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, QueenValueMg, 0));
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
Square strongKing = pos.square<KING>(strongSide);
Square weakKing = pos.square<KING>(weakSide);
Square weakPawn = pos.square<PAWN>(weakSide);
Value result = Value(push_close(strongKing, weakKing));
if ( relative_rank(weakSide, weakPawn) != RANK_7
|| distance(weakKing, weakPawn) != 1
|| ((FileBBB | FileDBB | FileEBB | FileGBB) & weakPawn))
result += QueenValueEg - PawnValueEg;
return strongSide == pos.side_to_move() ? result : -result;
}
/// KQ vs KR. This is almost identical to KX vs K: we give the attacking
/// king a bonus for having the kings close together, and for forcing the
/// defending king towards the edge. If we also take care to avoid null move for
/// the defending side in the search, this is usually sufficient to win KQ vs KR.
template<>
Value Endgame<KQKR>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, QueenValueMg, 0));
assert(verify_material(pos, weakSide, RookValueMg, 0));
Square strongKing = pos.square<KING>(strongSide);
Square weakKing = pos.square<KING>(weakSide);
Value result = QueenValueEg
- RookValueEg
+ push_to_edge(weakKing)
+ push_close(strongKing, weakKing);
return strongSide == pos.side_to_move() ? result : -result;
}
/// KNN vs KP. Very drawish, but there are some mate opportunities if we can
/// press the weakSide King to a corner before the pawn advances too much.
template<>
Value Endgame<KNNKP>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, 2 * KnightValueMg, 0));
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
Square weakKing = pos.square<KING>(weakSide);
Square weakPawn = pos.square<PAWN>(weakSide);
Value result = PawnValueEg
+ 2 * push_to_edge(weakKing)
- 10 * relative_rank(weakSide, weakPawn);
return strongSide == pos.side_to_move() ? result : -result;
}
/// Some cases of trivial draws
template<> Value Endgame<KNNK>::operator()(const Position&) const { return VALUE_DRAW; }
/// KB and one or more pawns vs K. It checks for draws with rook pawns and
/// a bishop of the wrong color. If such a draw is detected, SCALE_FACTOR_DRAW
/// is returned. If not, the return value is SCALE_FACTOR_NONE, i.e. no scaling
/// will be used.
template<>
ScaleFactor Endgame<KBPsK>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongSide) == BishopValueMg);
assert(pos.count<PAWN>(strongSide) >= 1);
// No assertions about the material of weakSide, because we want draws to
// be detected even when the weaker side has some pawns.
Bitboard strongPawns = pos.pieces(strongSide, PAWN);
Bitboard allPawns = pos.pieces(PAWN);
Square strongBishop = pos.square<BISHOP>(strongSide);
Square weakKing = pos.square<KING>(weakSide);
Square strongKing = pos.square<KING>(strongSide);
// All strongSide pawns are on a single rook file?
if (!(strongPawns & ~FileABB) || !(strongPawns & ~FileHBB))
{
Square queeningSquare = relative_square(strongSide, make_square(file_of(lsb(strongPawns)), RANK_8));
if ( opposite_colors(queeningSquare, strongBishop)
&& distance(queeningSquare, weakKing) <= 1)
return SCALE_FACTOR_DRAW;
}
// If all the pawns are on the same B or G file, then it's potentially a draw
if ((!(allPawns & ~FileBBB) || !(allPawns & ~FileGBB))
&& pos.non_pawn_material(weakSide) == 0
&& pos.count<PAWN>(weakSide) >= 1)
{
// Get the least advanced weakSide pawn
Square weakPawn = frontmost_sq(strongSide, pos.pieces(weakSide, PAWN));
// There's potential for a draw if our pawn is blocked on the 7th rank,
// the bishop cannot attack it or they only have one pawn left.
if ( relative_rank(strongSide, weakPawn) == RANK_7
&& (strongPawns & (weakPawn + pawn_push(weakSide)))
&& (opposite_colors(strongBishop, weakPawn) || !more_than_one(strongPawns)))
{
int strongKingDist = distance(weakPawn, strongKing);
int weakKingDist = distance(weakPawn, weakKing);
// It's a draw if the weak king is on its back two ranks, within 2
// squares of the blocking pawn and the strong king is not
// closer. (I think this rule only fails in practically
// unreachable positions such as 5k1K/6p1/6P1/8/8/3B4/8/8 w
// and positions where qsearch will immediately correct the
// problem such as 8/4k1p1/6P1/1K6/3B4/8/8/8 w).
if ( relative_rank(strongSide, weakKing) >= RANK_7
&& weakKingDist <= 2
&& weakKingDist <= strongKingDist)
return SCALE_FACTOR_DRAW;
}
}
return SCALE_FACTOR_NONE;
}
/// KQ vs KR and one or more pawns. It tests for fortress draws with a rook on
/// the third rank defended by a pawn.
template<>
ScaleFactor Endgame<KQKRPs>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, QueenValueMg, 0));
assert(pos.count<ROOK>(weakSide) == 1);
assert(pos.count<PAWN>(weakSide) >= 1);
Square strongKing = pos.square<KING>(strongSide);
Square weakKing = pos.square<KING>(weakSide);
Square weakRook = pos.square<ROOK>(weakSide);
if ( relative_rank(weakSide, weakKing) <= RANK_2
&& relative_rank(weakSide, strongKing) >= RANK_4
&& relative_rank(weakSide, weakRook) == RANK_3
&& ( pos.pieces(weakSide, PAWN)
& attacks_bb<KING>(weakKing)
& pawn_attacks_bb(strongSide, weakRook)))
return SCALE_FACTOR_DRAW;
return SCALE_FACTOR_NONE;
}
/// KRP vs KR. This function knows a handful of the most important classes of
/// drawn positions, but is far from perfect. It would probably be a good idea
/// to add more knowledge in the future.
///
/// It would also be nice to rewrite the actual code for this function,
/// which is mostly copied from Glaurung 1.x, and isn't very pretty.
template<>
ScaleFactor Endgame<KRPKR>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, RookValueMg, 1));
assert(verify_material(pos, weakSide, RookValueMg, 0));
// Assume strongSide is white and the pawn is on files A-D
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
Square strongRook = normalize(pos, strongSide, pos.square<ROOK>(strongSide));
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
Square weakRook = normalize(pos, strongSide, pos.square<ROOK>(weakSide));
File pawnFile = file_of(strongPawn);
Rank pawnRank = rank_of(strongPawn);
Square queeningSquare = make_square(pawnFile, RANK_8);
int tempo = (pos.side_to_move() == strongSide);
// If the pawn is not too far advanced and the defending king defends the
// queening square, use the third-rank defence.
if ( pawnRank <= RANK_5
&& distance(weakKing, queeningSquare) <= 1
&& strongKing <= SQ_H5
&& (rank_of(weakRook) == RANK_6 || (pawnRank <= RANK_3 && rank_of(strongRook) != RANK_6)))
return SCALE_FACTOR_DRAW;
// The defending side saves a draw by checking from behind in case the pawn
// has advanced to the 6th rank with the king behind.
if ( pawnRank == RANK_6
&& distance(weakKing, queeningSquare) <= 1
&& rank_of(strongKing) + tempo <= RANK_6
&& (rank_of(weakRook) == RANK_1 || (!tempo && distance<File>(weakRook, strongPawn) >= 3)))
return SCALE_FACTOR_DRAW;
if ( pawnRank >= RANK_6
&& weakKing == queeningSquare
&& rank_of(weakRook) == RANK_1
&& (!tempo || distance(strongKing, strongPawn) >= 2))
return SCALE_FACTOR_DRAW;
// White pawn on a7 and rook on a8 is a draw if black's king is on g7 or h7
// and the black rook is behind the pawn.
if ( strongPawn == SQ_A7
&& strongRook == SQ_A8
&& (weakKing == SQ_H7 || weakKing == SQ_G7)
&& file_of(weakRook) == FILE_A
&& (rank_of(weakRook) <= RANK_3 || file_of(strongKing) >= FILE_D || rank_of(strongKing) <= RANK_5))
return SCALE_FACTOR_DRAW;
// If the defending king blocks the pawn and the attacking king is too far
// away, it's a draw.
if ( pawnRank <= RANK_5
&& weakKing == strongPawn + NORTH
&& distance(strongKing, strongPawn) - tempo >= 2
&& distance(strongKing, weakRook) - tempo >= 2)
return SCALE_FACTOR_DRAW;
// Pawn on the 7th rank supported by the rook from behind usually wins if the
// attacking king is closer to the queening square than the defending king,
// and the defending king cannot gain tempi by threatening the attacking rook.
if ( pawnRank == RANK_7
&& pawnFile != FILE_A
&& file_of(strongRook) == pawnFile
&& strongRook != queeningSquare
&& (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
&& (distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo))
return ScaleFactor(SCALE_FACTOR_MAX - 2 * distance(strongKing, queeningSquare));
// Similar to the above, but with the pawn further back
if ( pawnFile != FILE_A
&& file_of(strongRook) == pawnFile
&& strongRook < strongPawn
&& (distance(strongKing, queeningSquare) < distance(weakKing, queeningSquare) - 2 + tempo)
&& (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn + NORTH) - 2 + tempo)
&& ( distance(weakKing, strongRook) + tempo >= 3
|| ( distance(strongKing, queeningSquare) < distance(weakKing, strongRook) + tempo
&& (distance(strongKing, strongPawn + NORTH) < distance(weakKing, strongPawn) + tempo))))
return ScaleFactor( SCALE_FACTOR_MAX
- 8 * distance(strongPawn, queeningSquare)
- 2 * distance(strongKing, queeningSquare));
// If the pawn is not far advanced and the defending king is somewhere in
// the pawn's path, it's probably a draw.
if (pawnRank <= RANK_4 && weakKing > strongPawn)
{
if (file_of(weakKing) == file_of(strongPawn))
return ScaleFactor(10);
if ( distance<File>(weakKing, strongPawn) == 1
&& distance(strongKing, weakKing) > 2)
return ScaleFactor(24 - 2 * distance(strongKing, weakKing));
}
return SCALE_FACTOR_NONE;
}
template<>
ScaleFactor Endgame<KRPKB>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, RookValueMg, 1));
assert(verify_material(pos, weakSide, BishopValueMg, 0));
// Test for a rook pawn
if (pos.pieces(PAWN) & (FileABB | FileHBB))
{
Square weakKing = pos.square<KING>(weakSide);
Square weakBishop = pos.square<BISHOP>(weakSide);
Square strongKing = pos.square<KING>(strongSide);
Square strongPawn = pos.square<PAWN>(strongSide);
Rank pawnRank = relative_rank(strongSide, strongPawn);
Direction push = pawn_push(strongSide);
// If the pawn is on the 5th rank and the pawn (currently) is on
// the same color square as the bishop then there is a chance of
// a fortress. Depending on the king position give a moderate
// reduction or a stronger one if the defending king is near the
// corner but not trapped there.
if (pawnRank == RANK_5 && !opposite_colors(weakBishop, strongPawn))
{
int d = distance(strongPawn + 3 * push, weakKing);
if (d <= 2 && !(d == 0 && weakKing == strongKing + 2 * push))
return ScaleFactor(24);
else
return ScaleFactor(48);
}
// When the pawn has moved to the 6th rank we can be fairly sure
// it's drawn if the bishop attacks the square in front of the
// pawn from a reasonable distance and the defending king is near
// the corner
if ( pawnRank == RANK_6
&& distance(strongPawn + 2 * push, weakKing) <= 1
&& (attacks_bb<BISHOP>(weakBishop) & (strongPawn + push))
&& distance<File>(weakBishop, strongPawn) >= 2)
return ScaleFactor(8);
}
return SCALE_FACTOR_NONE;
}
/// KRPP vs KRP. There is just a single rule: if the stronger side has no passed
/// pawns and the defending king is actively placed, the position is drawish.
template<>
ScaleFactor Endgame<KRPPKRP>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, RookValueMg, 2));
assert(verify_material(pos, weakSide, RookValueMg, 1));
Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN));
Square strongPawn2 = msb(pos.pieces(strongSide, PAWN));
Square weakKing = pos.square<KING>(weakSide);
// Does the stronger side have a passed pawn?
if (pos.pawn_passed(strongSide, strongPawn1) || pos.pawn_passed(strongSide, strongPawn2))
return SCALE_FACTOR_NONE;
Rank pawnRank = std::max(relative_rank(strongSide, strongPawn1), relative_rank(strongSide, strongPawn2));
if ( distance<File>(weakKing, strongPawn1) <= 1
&& distance<File>(weakKing, strongPawn2) <= 1
&& relative_rank(strongSide, weakKing) > pawnRank)
{
assert(pawnRank > RANK_1 && pawnRank < RANK_7);
return ScaleFactor(7 * pawnRank);
}
return SCALE_FACTOR_NONE;
}
/// K and two or more pawns vs K. There is just a single rule here: if all pawns
/// are on the same rook file and are blocked by the defending king, it's a draw.
template<>
ScaleFactor Endgame<KPsK>::operator()(const Position& pos) const {
assert(pos.non_pawn_material(strongSide) == VALUE_ZERO);
assert(pos.count<PAWN>(strongSide) >= 2);
assert(verify_material(pos, weakSide, VALUE_ZERO, 0));
Square weakKing = pos.square<KING>(weakSide);
Bitboard strongPawns = pos.pieces(strongSide, PAWN);
// If all pawns are ahead of the king on a single rook file, it's a draw.
if ( !(strongPawns & ~(FileABB | FileHBB))
&& !(strongPawns & ~passed_pawn_span(weakSide, weakKing)))
return SCALE_FACTOR_DRAW;
return SCALE_FACTOR_NONE;
}
/// KBP vs KB. There are two rules: if the defending king is somewhere along the
/// path of the pawn, and the square of the king is not of the same color as the
/// stronger side's bishop, it's a draw. If the two bishops have opposite color,
/// it's almost always a draw.
template<>
ScaleFactor Endgame<KBPKB>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, BishopValueMg, 1));
assert(verify_material(pos, weakSide, BishopValueMg, 0));
Square strongPawn = pos.square<PAWN>(strongSide);
Square strongBishop = pos.square<BISHOP>(strongSide);
Square weakBishop = pos.square<BISHOP>(weakSide);
Square weakKing = pos.square<KING>(weakSide);
// Case 1: Defending king blocks the pawn, and cannot be driven away
if ( (forward_file_bb(strongSide, strongPawn) & weakKing)
&& ( opposite_colors(weakKing, strongBishop)
|| relative_rank(strongSide, weakKing) <= RANK_6))
return SCALE_FACTOR_DRAW;
// Case 2: Opposite colored bishops
if (opposite_colors(strongBishop, weakBishop))
return SCALE_FACTOR_DRAW;
return SCALE_FACTOR_NONE;
}
/// KBPP vs KB. It detects a few basic draws with opposite-colored bishops
template<>
ScaleFactor Endgame<KBPPKB>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, BishopValueMg, 2));
assert(verify_material(pos, weakSide, BishopValueMg, 0));
Square strongBishop = pos.square<BISHOP>(strongSide);
Square weakBishop = pos.square<BISHOP>(weakSide);
if (!opposite_colors(strongBishop, weakBishop))
return SCALE_FACTOR_NONE;
Square weakKing = pos.square<KING>(weakSide);
Square strongPawn1 = lsb(pos.pieces(strongSide, PAWN));
Square strongPawn2 = msb(pos.pieces(strongSide, PAWN));
Square blockSq1, blockSq2;
if (relative_rank(strongSide, strongPawn1) > relative_rank(strongSide, strongPawn2))
{
blockSq1 = strongPawn1 + pawn_push(strongSide);
blockSq2 = make_square(file_of(strongPawn2), rank_of(strongPawn1));
}
else
{
blockSq1 = strongPawn2 + pawn_push(strongSide);
blockSq2 = make_square(file_of(strongPawn1), rank_of(strongPawn2));
}
switch (distance<File>(strongPawn1, strongPawn2))
{
case 0:
// Both pawns are on the same file. It's an easy draw if the defender firmly
// controls some square in the frontmost pawn's path.
if ( file_of(weakKing) == file_of(blockSq1)
&& relative_rank(strongSide, weakKing) >= relative_rank(strongSide, blockSq1)
&& opposite_colors(weakKing, strongBishop))
return SCALE_FACTOR_DRAW;
else
return SCALE_FACTOR_NONE;
case 1:
// Pawns on adjacent files. It's a draw if the defender firmly controls the
// square in front of the frontmost pawn's path, and the square diagonally
// behind this square on the file of the other pawn.
if ( weakKing == blockSq1
&& opposite_colors(weakKing, strongBishop)
&& ( weakBishop == blockSq2
|| (attacks_bb<BISHOP>(blockSq2, pos.pieces()) & pos.pieces(weakSide, BISHOP))
|| distance<Rank>(strongPawn1, strongPawn2) >= 2))
return SCALE_FACTOR_DRAW;
else if ( weakKing == blockSq2
&& opposite_colors(weakKing, strongBishop)
&& ( weakBishop == blockSq1
|| (attacks_bb<BISHOP>(blockSq1, pos.pieces()) & pos.pieces(weakSide, BISHOP))))
return SCALE_FACTOR_DRAW;
else
return SCALE_FACTOR_NONE;
default:
// The pawns are not on the same file or adjacent files. No scaling.
return SCALE_FACTOR_NONE;
}
}
/// KBP vs KN. There is a single rule: if the defending king is somewhere along
/// the path of the pawn, and the square of the king is not of the same color as
/// the stronger side's bishop, it's a draw.
template<>
ScaleFactor Endgame<KBPKN>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, BishopValueMg, 1));
assert(verify_material(pos, weakSide, KnightValueMg, 0));
Square strongPawn = pos.square<PAWN>(strongSide);
Square strongBishop = pos.square<BISHOP>(strongSide);
Square weakKing = pos.square<KING>(weakSide);
if ( file_of(weakKing) == file_of(strongPawn)
&& relative_rank(strongSide, strongPawn) < relative_rank(strongSide, weakKing)
&& ( opposite_colors(weakKing, strongBishop)
|| relative_rank(strongSide, weakKing) <= RANK_6))
return SCALE_FACTOR_DRAW;
return SCALE_FACTOR_NONE;
}
/// KP vs KP. This is done by removing the weakest side's pawn and probing the
/// KP vs K bitbase: if the weakest side has a draw without the pawn, it probably
/// has at least a draw with the pawn as well. The exception is when the stronger
/// side's pawn is far advanced and not on a rook file; in this case it is often
/// possible to win (e.g. 8/4k3/3p4/3P4/6K1/8/8/8 w - - 0 1).
template<>
ScaleFactor Endgame<KPKP>::operator()(const Position& pos) const {
assert(verify_material(pos, strongSide, VALUE_ZERO, 1));
assert(verify_material(pos, weakSide, VALUE_ZERO, 1));
// Assume strongSide is white and the pawn is on files A-D
Square strongKing = normalize(pos, strongSide, pos.square<KING>(strongSide));
Square weakKing = normalize(pos, strongSide, pos.square<KING>(weakSide));
Square strongPawn = normalize(pos, strongSide, pos.square<PAWN>(strongSide));
Color us = strongSide == pos.side_to_move() ? WHITE : BLACK;
// If the pawn has advanced to the fifth rank or further, and is not a
// rook pawn, it's too dangerous to assume that it's at least a draw.
if (rank_of(strongPawn) >= RANK_5 && file_of(strongPawn) != FILE_A)
return SCALE_FACTOR_NONE;
// Probe the KPK bitbase with the weakest side's pawn removed. If it's a draw,
// it's probably at least a draw even with the pawn.
return Bitbases::probe(strongKing, strongPawn, weakKing, us) ? SCALE_FACTOR_NONE : SCALE_FACTOR_DRAW;
}
} // namespace Stockfish

View file

@ -1,126 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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 ENDGAME_H_INCLUDED
#define ENDGAME_H_INCLUDED
#include <memory>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include "position.h"
#include "types.h"
namespace Stockfish {
/// EndgameCode lists all supported endgame functions by corresponding codes
enum EndgameCode {
EVALUATION_FUNCTIONS,
KNNK, // KNN vs K
KNNKP, // KNN vs KP
KXK, // Generic "mate lone king" eval
KBNK, // KBN vs K
KPK, // KP vs K
KRKP, // KR vs KP
KRKB, // KR vs KB
KRKN, // KR vs KN
KQKP, // KQ vs KP
KQKR, // KQ vs KR
SCALING_FUNCTIONS,
KBPsK, // KB and pawns vs K
KQKRPs, // KQ vs KR and pawns
KRPKR, // KRP vs KR
KRPKB, // KRP vs KB
KRPPKRP, // KRPP vs KRP
KPsK, // K and pawns vs K
KBPKB, // KBP vs KB
KBPPKB, // KBPP vs KB
KBPKN, // KBP vs KN
KPKP // KP vs KP
};
/// Endgame functions can be of two types depending on whether they return a
/// Value or a ScaleFactor.
template<EndgameCode E> using
eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type;
/// Base and derived functors for endgame evaluation and scaling functions
template<typename T>
struct EndgameBase {
explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {}
virtual ~EndgameBase() = default;
virtual T operator()(const Position&) const = 0;
const Color strongSide, weakSide;
};
template<EndgameCode E, typename T = eg_type<E>>
struct Endgame : public EndgameBase<T> {
explicit Endgame(Color c) : EndgameBase<T>(c) {}
T operator()(const Position&) const override;
};
/// The Endgames namespace handles the pointers to endgame evaluation and scaling
/// base objects in two std::map. We use polymorphism to invoke the actual
/// endgame function by calling its virtual operator().
namespace Endgames {
template<typename T> using Ptr = std::unique_ptr<EndgameBase<T>>;
template<typename T> using Map = std::unordered_map<Key, Ptr<T>>;
extern std::pair<Map<Value>, Map<ScaleFactor>> maps;
void init();
template<typename T>
Map<T>& map() {
return std::get<std::is_same<T, ScaleFactor>::value>(maps);
}
template<EndgameCode E, typename T = eg_type<E>>
void add(const std::string& code) {
StateInfo st;
map<T>()[Position().set(code, WHITE, &st).material_key()] = Ptr<T>(new Endgame<E>(WHITE));
map<T>()[Position().set(code, BLACK, &st).material_key()] = Ptr<T>(new Endgame<E>(BLACK));
}
template<typename T>
const EndgameBase<T>* probe(Key key) {
auto it = map<T>().find(key);
return it != map<T>().end() ? it->second.get() : nullptr;
}
}
} // namespace Stockfish
#endif // #ifndef ENDGAME_H_INCLUDED

187
src/engine.cpp Normal file
View file

@ -0,0 +1,187 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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 "engine.h"
#include <deque>
#include <memory>
#include <ostream>
#include <string_view>
#include <utility>
#include <vector>
#include <sstream>
#include <iosfwd>
#include <cassert>
#include "evaluate.h"
#include "misc.h"
#include "nnue/network.h"
#include "nnue/nnue_common.h"
#include "perft.h"
#include "position.h"
#include "search.h"
#include "syzygy/tbprobe.h"
#include "types.h"
#include "uci.h"
#include "ucioption.h"
namespace Stockfish {
namespace NN = Eval::NNUE;
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
Engine::Engine(std::string path) :
binaryDirectory(CommandLine::get_binary_directory(path)),
states(new std::deque<StateInfo>(1)),
networks(NN::Networks(
NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG),
NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))) {
pos.set(StartFEN, false, &states->back());
capSq = SQ_NONE;
}
std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) {
verify_networks();
return Benchmark::perft(fen, depth, isChess960);
}
void Engine::go(Search::LimitsType& limits) {
assert(limits.perft == 0);
verify_networks();
limits.capSq = capSq;
threads.start_thinking(options, pos, states, limits);
}
void Engine::stop() { threads.stop = true; }
void Engine::search_clear() {
wait_for_search_finished();
tt.clear(options["Threads"]);
threads.clear();
// @TODO wont work with multiple instances
Tablebases::init(options["SyzygyPath"]); // Free mapped files
}
void Engine::set_on_update_no_moves(std::function<void(const Engine::InfoShort&)>&& f) {
updateContext.onUpdateNoMoves = std::move(f);
}
void Engine::set_on_update_full(std::function<void(const Engine::InfoFull&)>&& f) {
updateContext.onUpdateFull = std::move(f);
}
void Engine::set_on_iter(std::function<void(const Engine::InfoIter&)>&& f) {
updateContext.onIter = std::move(f);
}
void Engine::set_on_bestmove(std::function<void(std::string_view, std::string_view)>&& f) {
updateContext.onBestmove = 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) {
// Drop the old state and create a new one
states = StateListPtr(new std::deque<StateInfo>(1));
pos.set(fen, options["UCI_Chess960"], &states->back());
capSq = SQ_NONE;
for (const auto& move : moves)
{
auto m = UCIEngine::to_move(pos, move);
if (m == Move::none())
break;
states->emplace_back();
pos.do_move(m, states->back());
capSq = SQ_NONE;
DirtyPiece& dp = states->back().dirtyPiece;
if (dp.dirty_num > 1 && dp.to[1] == SQ_NONE)
capSq = m.to_sq();
}
}
// modifiers
void Engine::resize_threads() { threads.set({options, threads, tt, networks}, updateContext); }
void Engine::set_tt_size(size_t mb) {
wait_for_search_finished();
tt.resize(mb, options["Threads"]);
}
void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; }
// network related
void Engine::verify_networks() const {
networks.big.verify(options["EvalFile"]);
networks.small.verify(options["EvalFileSmall"]);
}
void Engine::load_networks() {
load_big_network(options["EvalFile"]);
load_small_network(options["EvalFileSmall"]);
}
void Engine::load_big_network(const std::string& file) {
networks.big.load(binaryDirectory, file);
threads.clear();
}
void Engine::load_small_network(const std::string& file) {
networks.small.load(binaryDirectory, file);
threads.clear();
}
void Engine::save_network(const std::pair<std::optional<std::string>, std::string> files[2]) {
networks.big.save(files[0].first);
networks.small.save(files[1].first);
}
// utility functions
void Engine::trace_eval() const {
StateListPtr trace_states(new std::deque<StateInfo>(1));
Position p;
p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back());
verify_networks();
sync_cout << "\n" << Eval::trace(p, networks) << sync_endl;
}
OptionsMap& Engine::get_options() { return options; }
std::string Engine::fen() const { return pos.fen(); }
void Engine::flip() { pos.flip(); }
std::string Engine::visualize() const {
std::stringstream ss;
ss << pos;
return ss.str();
}
}

110
src/engine.h Normal file
View file

@ -0,0 +1,110 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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 ENGINE_H_INCLUDED
#define ENGINE_H_INCLUDED
#include <cstddef>
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "nnue/network.h"
#include "position.h"
#include "search.h"
#include "syzygy/tbprobe.h" // for Stockfish::Depth
#include "thread.h"
#include "tt.h"
#include "ucioption.h"
namespace Stockfish {
enum Square : int;
class Engine {
public:
using InfoShort = Search::InfoShort;
using InfoFull = Search::InfoFull;
using InfoIter = Search::InfoIteration;
Engine(std::string path = "");
~Engine() { wait_for_search_finished(); }
std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960);
// non blocking call to start searching
void go(Search::LimitsType&);
// non blocking call to stop searching
void stop();
// 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);
// modifiers
void resize_threads();
void set_tt_size(size_t mb);
void set_ponderhit(bool);
void search_clear();
void set_on_update_no_moves(std::function<void(const InfoShort&)>&&);
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)>&&);
// 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]);
// utility functions
void trace_eval() const;
OptionsMap& get_options();
std::string fen() const;
void flip();
std::string visualize() const;
private:
const std::string binaryDirectory;
Position pos;
StateListPtr states;
Square capSq;
OptionsMap options;
ThreadPool threads;
TranspositionTable tt;
Eval::NNUE::Networks networks;
Search::SearchManager::UpdateContext updateContext;
};
} // namespace Stockfish
#endif // #ifndef ENGINE_H_INCLUDED

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -20,7 +20,6 @@
#define EVALUATE_H_INCLUDED
#include <string>
#include <optional>
#include "types.h"
@ -30,33 +29,29 @@ class Position;
namespace Eval {
std::string trace(Position& pos);
Value evaluate(const Position& pos);
constexpr inline int SmallNetThreshold = 1274;
extern bool useNNUE;
extern std::string currentEvalFileName;
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// 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-ae6a388e4a1a.nnue"
#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue"
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-6877cd24400e.nnue"
namespace NNUE {
struct Networks;
struct AccumulatorCaches;
}
namespace NNUE {
std::string trace(Position& pos, const Eval::NNUE::Networks& networks);
std::string trace(Position& pos);
Value evaluate(const Position& pos, bool adjusted = false);
int simple_eval(const Position& pos, Color c);
Value evaluate(const NNUE::Networks& networks,
const Position& pos,
Eval::NNUE::AccumulatorCaches& caches,
int optimism);
} // namespace Eval
void init();
void verify();
} // namespace Stockfish
bool load_eval(std::string name, std::istream& stream);
bool save_eval(std::ostream& stream);
bool save_eval(const std::optional<std::string>& filename);
} // namespace NNUE
} // namespace Eval
} // namespace Stockfish
#endif // #ifndef EVALUATE_H_INCLUDED
#endif // #ifndef EVALUATE_H_INCLUDED

10
src/incbin/incbin.h Executable file → Normal file
View file

@ -3,8 +3,8 @@
* @author Dale Weiler
* @brief Utility for including binary files
*
* Facilities for including binary files into the current translation unit and
* making use from them externally in other translation units.
* Facilities for including binary files into the current translation unit
* and making use of them externally in other translation units.
*/
#ifndef INCBIN_HDR
#define INCBIN_HDR
@ -139,7 +139,7 @@
#endif
#if defined(__APPLE__)
/* The directives are different for Apple branded compilers */
/* The directives are different for Apple-branded compilers */
# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n"
# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
# define INCBIN_INT ".long "
@ -261,8 +261,8 @@
INCBIN_STRINGIZE( \
INCBIN_STYLE_IDENT(TYPE)) \
/* Generate the global labels by indirectly invoking the macro with our style
* type and concatenating the name against them. */
/* Generate the global labels by indirectly invoking the macro
* with our style type and concatenate the name against them. */
#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
INCBIN_INVOKE( \
INCBIN_GLOBAL, \

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,35 +19,26 @@
#include <iostream>
#include "bitboard.h"
#include "endgame.h"
#include "misc.h"
#include "position.h"
#include "psqt.h"
#include "search.h"
#include "syzygy/tbprobe.h"
#include "thread.h"
#include "tt.h"
#include "types.h"
#include "uci.h"
#include "tune.h"
using namespace Stockfish;
int main(int argc, char* argv[]) {
std::cout << engine_info() << std::endl;
std::cout << engine_info() << std::endl;
CommandLine::init(argc, argv);
UCI::init(Options);
Tune::init();
PSQT::init();
Bitboards::init();
Position::init();
Bitbases::init();
Endgames::init();
Threads.set(size_t(Options["Threads"]));
Search::clear(); // After threads are up
Eval::NNUE::init();
Bitboards::init();
Position::init();
UCI::loop(argc, argv);
UCIEngine uci(argc, argv);
Threads.set(0);
return 0;
Tune::init(uci.engine_options());
uci.loop();
return 0;
}

View file

@ -1,229 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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 <cassert>
#include <cstring> // For std::memset
#include "material.h"
#include "thread.h"
using namespace std;
namespace Stockfish {
namespace {
#define S(mg, eg) make_score(mg, eg)
// Polynomial material imbalance parameters
// One Score parameter for each pair (our piece, another of our pieces)
constexpr Score QuadraticOurs[][PIECE_TYPE_NB] = {
// OUR PIECE 2
// bishop pair pawn knight bishop rook queen
{S(1419, 1455) }, // Bishop pair
{S( 101, 28), S( 37, 39) }, // Pawn
{S( 57, 64), S(249, 187), S(-49, -62) }, // Knight OUR PIECE 1
{S( 0, 0), S(118, 137), S( 10, 27), S( 0, 0) }, // Bishop
{S( -63, -68), S( -5, 3), S(100, 81), S(132, 118), S(-246, -244) }, // Rook
{S(-210, -211), S( 37, 14), S(147, 141), S(161, 105), S(-158, -174), S(-9,-31) } // Queen
};
// One Score parameter for each pair (our piece, their piece)
constexpr Score QuadraticTheirs[][PIECE_TYPE_NB] = {
// THEIR PIECE
// bishop pair pawn knight bishop rook queen
{ }, // Bishop pair
{S( 33, 30) }, // Pawn
{S( 46, 18), S(106, 84) }, // Knight OUR PIECE
{S( 75, 35), S( 59, 44), S( 60, 15) }, // Bishop
{S( 26, 35), S( 6, 22), S( 38, 39), S(-12, -2) }, // Rook
{S( 97, 93), S(100, 163), S(-58, -91), S(112, 192), S(276, 225) } // Queen
};
#undef S
// Endgame evaluation and scaling functions are accessed directly and not through
// the function maps because they correspond to more than one material hash key.
Endgame<KXK> EvaluateKXK[] = { Endgame<KXK>(WHITE), Endgame<KXK>(BLACK) };
Endgame<KBPsK> ScaleKBPsK[] = { Endgame<KBPsK>(WHITE), Endgame<KBPsK>(BLACK) };
Endgame<KQKRPs> ScaleKQKRPs[] = { Endgame<KQKRPs>(WHITE), Endgame<KQKRPs>(BLACK) };
Endgame<KPsK> ScaleKPsK[] = { Endgame<KPsK>(WHITE), Endgame<KPsK>(BLACK) };
Endgame<KPKP> ScaleKPKP[] = { Endgame<KPKP>(WHITE), Endgame<KPKP>(BLACK) };
// Helper used to detect a given material distribution
bool is_KXK(const Position& pos, Color us) {
return !more_than_one(pos.pieces(~us))
&& pos.non_pawn_material(us) >= RookValueMg;
}
bool is_KBPsK(const Position& pos, Color us) {
return pos.non_pawn_material(us) == BishopValueMg
&& pos.count<PAWN>(us) >= 1;
}
bool is_KQKRPs(const Position& pos, Color us) {
return !pos.count<PAWN>(us)
&& pos.non_pawn_material(us) == QueenValueMg
&& pos.count<ROOK>(~us) == 1
&& pos.count<PAWN>(~us) >= 1;
}
/// imbalance() calculates the imbalance by comparing the piece count of each
/// piece type for both colors.
template<Color Us>
Score imbalance(const int pieceCount[][PIECE_TYPE_NB]) {
constexpr Color Them = ~Us;
Score bonus = SCORE_ZERO;
// Second-degree polynomial material imbalance, by Tord Romstad
for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1)
{
if (!pieceCount[Us][pt1])
continue;
int v = QuadraticOurs[pt1][pt1] * pieceCount[Us][pt1];
for (int pt2 = NO_PIECE_TYPE; pt2 < pt1; ++pt2)
v += QuadraticOurs[pt1][pt2] * pieceCount[Us][pt2]
+ QuadraticTheirs[pt1][pt2] * pieceCount[Them][pt2];
bonus += pieceCount[Us][pt1] * v;
}
return bonus;
}
} // namespace
namespace Material {
/// Material::probe() looks up the current position's material configuration in
/// the material hash table. It returns a pointer to the Entry if the position
/// is found. Otherwise a new Entry is computed and stored there, so we don't
/// have to recompute all when the same material configuration occurs again.
Entry* probe(const Position& pos) {
Key key = pos.material_key();
Entry* e = pos.this_thread()->materialTable[key];
if (e->key == key)
return e;
std::memset(e, 0, sizeof(Entry));
e->key = key;
e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL;
Value npm_w = pos.non_pawn_material(WHITE);
Value npm_b = pos.non_pawn_material(BLACK);
Value npm = std::clamp(npm_w + npm_b, EndgameLimit, MidgameLimit);
// Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME]
e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit));
// Let's look if we have a specialized evaluation function for this particular
// material configuration. Firstly we look for a fixed configuration one, then
// for a generic one if the previous search failed.
if ((e->evaluationFunction = Endgames::probe<Value>(key)) != nullptr)
return e;
for (Color c : { WHITE, BLACK })
if (is_KXK(pos, c))
{
e->evaluationFunction = &EvaluateKXK[c];
return e;
}
// OK, we didn't find any special evaluation function for the current material
// configuration. Is there a suitable specialized scaling function?
const auto* sf = Endgames::probe<ScaleFactor>(key);
if (sf)
{
e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned
return e;
}
// We didn't find any specialized scaling function, so fall back on generic
// ones that refer to more than one material distribution. Note that in this
// case we don't return after setting the function.
for (Color c : { WHITE, BLACK })
{
if (is_KBPsK(pos, c))
e->scalingFunction[c] = &ScaleKBPsK[c];
else if (is_KQKRPs(pos, c))
e->scalingFunction[c] = &ScaleKQKRPs[c];
}
if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board
{
if (!pos.count<PAWN>(BLACK))
{
assert(pos.count<PAWN>(WHITE) >= 2);
e->scalingFunction[WHITE] = &ScaleKPsK[WHITE];
}
else if (!pos.count<PAWN>(WHITE))
{
assert(pos.count<PAWN>(BLACK) >= 2);
e->scalingFunction[BLACK] = &ScaleKPsK[BLACK];
}
else if (pos.count<PAWN>(WHITE) == 1 && pos.count<PAWN>(BLACK) == 1)
{
// This is a special case because we set scaling functions
// for both colors instead of only one.
e->scalingFunction[WHITE] = &ScaleKPKP[WHITE];
e->scalingFunction[BLACK] = &ScaleKPKP[BLACK];
}
}
// Zero or just one pawn makes it difficult to win, even with a small material
// advantage. This catches some trivial draws like KK, KBK and KNK and gives a
// drawish scale factor for cases such as KRKBP and KmmKm (except for KBBKN).
if (!pos.count<PAWN>(WHITE) && npm_w - npm_b <= BishopValueMg)
e->factor[WHITE] = uint8_t(npm_w < RookValueMg ? SCALE_FACTOR_DRAW :
npm_b <= BishopValueMg ? 4 : 14);
if (!pos.count<PAWN>(BLACK) && npm_b - npm_w <= BishopValueMg)
e->factor[BLACK] = uint8_t(npm_b < RookValueMg ? SCALE_FACTOR_DRAW :
npm_w <= BishopValueMg ? 4 : 14);
// Evaluate the material imbalance. We use PIECE_TYPE_NONE as a place holder
// for the bishop pair "extended piece", which allows us to be more flexible
// in defining bishop pair bonuses.
const int pieceCount[COLOR_NB][PIECE_TYPE_NB] = {
{ pos.count<BISHOP>(WHITE) > 1, pos.count<PAWN>(WHITE), pos.count<KNIGHT>(WHITE),
pos.count<BISHOP>(WHITE) , pos.count<ROOK>(WHITE), pos.count<QUEEN >(WHITE) },
{ pos.count<BISHOP>(BLACK) > 1, pos.count<PAWN>(BLACK), pos.count<KNIGHT>(BLACK),
pos.count<BISHOP>(BLACK) , pos.count<ROOK>(BLACK), pos.count<QUEEN >(BLACK) } };
e->score = (imbalance<WHITE>(pieceCount) - imbalance<BLACK>(pieceCount)) / 16;
return e;
}
} // namespace Material
} // namespace Stockfish

View file

@ -1,71 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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 MATERIAL_H_INCLUDED
#define MATERIAL_H_INCLUDED
#include "endgame.h"
#include "misc.h"
#include "position.h"
#include "types.h"
namespace Stockfish::Material {
/// Material::Entry contains various information about a material configuration.
/// It contains a material imbalance evaluation, a function pointer to a special
/// endgame evaluation function (which in most cases is NULL, meaning that the
/// standard evaluation function will be used), and scale factors.
///
/// The scale factors are used to scale the evaluation score up or down. For
/// instance, in KRB vs KR endgames, the score is scaled down by a factor of 4,
/// which will result in scores of absolute value less than one pawn.
struct Entry {
Score imbalance() const { return score; }
Phase game_phase() const { return (Phase)gamePhase; }
bool specialized_eval_exists() const { return evaluationFunction != nullptr; }
Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); }
// scale_factor() takes a position and a color as input and returns a scale factor
// for the given color. We have to provide the position in addition to the color
// because the scale factor may also be a function which should be applied to
// the position. For instance, in KBP vs K endgames, the scaling function looks
// for rook pawns and wrong-colored bishops.
ScaleFactor scale_factor(const Position& pos, Color c) const {
ScaleFactor sf = scalingFunction[c] ? (*scalingFunction[c])(pos)
: SCALE_FACTOR_NONE;
return sf != SCALE_FACTOR_NONE ? sf : ScaleFactor(factor[c]);
}
Key key;
const EndgameBase<Value>* evaluationFunction;
const EndgameBase<ScaleFactor>* scalingFunction[COLOR_NB]; // Could be one for each
// side (e.g. KPKP, KBPsK)
Score score;
int16_t gamePhase;
uint8_t factor[COLOR_NB];
};
typedef HashTable<Entry, 8192> Table;
Entry* probe(const Position& pos);
} // namespace Stockfish::Material
#endif // #ifndef MATERIAL_H_INCLUDED

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,222 +19,217 @@
#ifndef MISC_H_INCLUDED
#define MISC_H_INCLUDED
#include <algorithm>
#include <cassert>
#include <chrono>
#include <ostream>
#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <memory>
#include <string>
#include <vector>
#include <cstdint>
#include "types.h"
#define stringify2(x) #x
#define stringify(x) stringify2(x)
namespace Stockfish {
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(void* addr);
void start_logger(const std::string& fname);
void start_logger(const std::string& fname);
void* std_aligned_alloc(size_t alignment, size_t size);
void std_aligned_free(void* ptr);
void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes
void aligned_large_pages_free(void* mem); // nop if mem == nullptr
void std_aligned_free(void* ptr);
// memory aligned by page size, min alignment: 4096 bytes
void* aligned_large_pages_alloc(size_t size);
// nop if mem == nullptr
void aligned_large_pages_free(void* mem);
void dbg_hit_on(bool b);
void dbg_hit_on(bool c, bool b);
void dbg_mean_of(int v);
void dbg_print();
typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
inline TimePoint now() {
return std::chrono::duration_cast<std::chrono::milliseconds>
(std::chrono::steady_clock::now().time_since_epoch()).count();
}
template<class Entry, int Size>
struct HashTable {
Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; }
private:
std::vector<Entry> table = std::vector<Entry>(Size); // Allocate on the heap
// Deleter for automating release of memory area
template<typename T>
struct AlignedDeleter {
void operator()(T* ptr) const {
ptr->~T();
std_aligned_free(ptr);
}
};
template<typename T>
struct LargePageDeleter {
void operator()(T* ptr) const {
ptr->~T();
aligned_large_pages_free(ptr);
}
};
enum SyncCout { IO_LOCK, IO_UNLOCK };
template<typename T>
using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
template<typename T>
using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
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_correl_of(int64_t value1, int64_t value2, int slot = 0);
void dbg_print();
using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
inline TimePoint now() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
}
enum SyncCout {
IO_LOCK,
IO_UNLOCK
};
std::ostream& operator<<(std::ostream&, SyncCout);
#define sync_cout std::cout << IO_LOCK
#define sync_endl std::endl << IO_UNLOCK
// align_ptr_up() : get the first aligned element of an array.
// Get the first aligned element of an array.
// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes,
// where N is the number of elements in the array.
template <uintptr_t Alignment, typename T>
T* align_ptr_up(T* ptr)
{
static_assert(alignof(T) < Alignment);
template<uintptr_t Alignment, typename T>
T* align_ptr_up(T* ptr) {
static_assert(alignof(T) < Alignment);
const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));
return reinterpret_cast<T*>(reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));
return reinterpret_cast<T*>(
reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
}
// IsLittleEndian : true if and only if the binary is compiled on a little endian machine
static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
// True if and only if the binary is compiled on a little-endian machine
static inline const union {
uint32_t i;
char c[4];
} Le = {0x01020304};
static inline const bool IsLittleEndian = (Le.c[0] == 4);
// RunningAverage : a class to calculate a running average of a series of values.
// For efficiency, all computations are done with integers.
class RunningAverage {
public:
// Constructor
RunningAverage() {}
// Reset the running average to rational value p / q
void set(int64_t p, int64_t q)
{ average = p * PERIOD * RESOLUTION / q; }
// Update average with value v
void update(int64_t v)
{ average = RESOLUTION * v + (PERIOD - 1) * average / PERIOD; }
// Test if average is strictly greater than rational a / b
bool is_greater(int64_t a, int64_t b)
{ return b * average > a * PERIOD * RESOLUTION ; }
int64_t value()
{ return average / (PERIOD * RESOLUTION); }
private :
static constexpr int64_t PERIOD = 4096;
static constexpr int64_t RESOLUTION = 1024;
int64_t average;
};
template <typename T, std::size_t MaxSize>
template<typename T, std::size_t MaxSize>
class ValueList {
public:
std::size_t size() const { return size_; }
void resize(std::size_t newSize) { size_ = newSize; }
void push_back(const T& value) { values_[size_++] = value; }
T& operator[](std::size_t index) { return values_[index]; }
T* begin() { return values_; }
T* end() { return values_ + size_; }
const T& operator[](std::size_t index) const { return values_[index]; }
const T* begin() const { return values_; }
const T* end() const { return values_ + size_; }
public:
std::size_t size() const { return size_; }
void push_back(const T& value) { values_[size_++] = value; }
const T* begin() const { return values_; }
const T* end() const { return values_ + size_; }
const T& operator[](int index) const { return values_[index]; }
void swap(ValueList& other) {
const std::size_t maxSize = std::max(size_, other.size_);
for (std::size_t i = 0; i < maxSize; ++i) {
std::swap(values_[i], other.values_[i]);
}
std::swap(size_, other.size_);
}
private:
T values_[MaxSize];
std::size_t size_ = 0;
private:
T values_[MaxSize];
std::size_t size_ = 0;
};
/// sigmoid(t, x0, y0, C, P, Q) implements a sigmoid-like function using only integers,
/// with the following properties:
///
/// - sigmoid is centered in (x0, y0)
/// - sigmoid has amplitude [-P/Q , P/Q] instead of [-1 , +1]
/// - limit is (y0 - P/Q) when t tends to -infinity
/// - limit is (y0 + P/Q) when t tends to +infinity
/// - the slope can be adjusted using C > 0, smaller C giving a steeper sigmoid
/// - the slope of the sigmoid when t = x0 is P/(Q*C)
/// - sigmoid is increasing with t when P > 0 and Q > 0
/// - to get a decreasing sigmoid, call with -t, or change sign of P
/// - mean value of the sigmoid is y0
///
/// Use <https://www.desmos.com/calculator/jhh83sqq92> to draw the sigmoid
inline int64_t sigmoid(int64_t t, int64_t x0,
int64_t y0,
int64_t C,
int64_t P,
int64_t Q)
{
assert(C > 0);
return y0 + P * (t-x0) / (Q * (std::abs(t-x0) + C)) ;
}
/// xorshift64star Pseudo-Random Number Generator
/// This class is based on original code written and dedicated
/// to the public domain by Sebastiano Vigna (2014).
/// It has the following characteristics:
///
/// - Outputs 64-bit numbers
/// - Passes Dieharder and SmallCrush test batteries
/// - Does not require warm-up, no zeroland to escape
/// - Internal state is a single 64-bit integer
/// - Period is 2^64 - 1
/// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
///
/// For further analysis see
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
// xorshift64star Pseudo-Random Number Generator
// This class is based on original code written and dedicated
// to the public domain by Sebastiano Vigna (2014).
// It has the following characteristics:
//
// - Outputs 64-bit numbers
// - Passes Dieharder and SmallCrush test batteries
// - Does not require warm-up, no zeroland to escape
// - Internal state is a single 64-bit integer
// - Period is 2^64 - 1
// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
//
// For further analysis see
// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
class PRNG {
uint64_t s;
uint64_t s;
uint64_t rand64() {
uint64_t rand64() {
s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
return s * 2685821657736338717LL;
}
s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
return s * 2685821657736338717LL;
}
public:
PRNG(uint64_t seed) : s(seed) { assert(seed); }
public:
PRNG(uint64_t seed) :
s(seed) {
assert(seed);
}
template<typename T> T rand() { return T(rand64()); }
template<typename T>
T rand() {
return T(rand64());
}
/// Special generator used to fast init magic numbers.
/// Output values only have 1/8th of their bits set on average.
template<typename T> T sparse_rand()
{ return T(rand64() & rand64() & rand64()); }
// Special generator used to fast init magic numbers.
// Output values only have 1/8th of their bits set on average.
template<typename T>
T sparse_rand() {
return T(rand64() & rand64() & rand64());
}
};
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
#if defined(__GNUC__) && defined(IS_64BIT)
__extension__ typedef unsigned __int128 uint128;
return ((uint128)a * (uint128)b) >> 64;
__extension__ using uint128 = unsigned __int128;
return (uint128(a) * uint128(b)) >> 64;
#else
uint64_t aL = (uint32_t)a, aH = a >> 32;
uint64_t bL = (uint32_t)b, bH = b >> 32;
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;
uint64_t c3 = aL * bH + uint32_t(c2);
return aH * bH + (c2 >> 32) + (c3 >> 32);
#endif
}
/// Under Windows it is not possible for a process to run on more than one
/// logical processor group. This usually means to be limited to use max 64
/// cores. To overcome this, some special platform specific API should be
/// called to set group affinity for each thread. Original code from Texel by
/// Peter Österlund.
// Under Windows it is not possible for a process to run on more than one
// logical processor group. This usually means being limited to using max 64
// cores. To overcome this, some special platform-specific API should be
// called to set group affinity for each thread. Original code from Texel by
// Peter Österlund.
namespace WinProcGroup {
void bindThisThread(size_t idx);
void bind_this_thread(size_t idx);
}
namespace CommandLine {
void init(int argc, char* argv[]);
extern std::string binaryDirectory; // path of the executable directory
extern std::string workingDirectory; // path of the working directory
struct CommandLine {
public:
CommandLine(int _argc, char** _argv) :
argc(_argc),
argv(_argv) {}
static std::string get_binary_directory(std::string argv0);
static std::string get_working_directory();
int argc;
char** argv;
};
namespace Utility {
template<typename T, typename Predicate>
void move_to_front(std::vector<T>& vec, Predicate pred) {
auto it = std::find_if(vec.begin(), vec.end(), pred);
if (it != vec.end())
{
std::rotate(vec.begin(), it, it + 1);
}
}
}
} // namespace Stockfish
} // namespace Stockfish
#endif // #ifndef MISC_H_INCLUDED
#endif // #ifndef MISC_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,82 +16,86 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#include "movegen.h"
#include <cassert>
#include <initializer_list>
#include "bitboard.h"
#include "position.h"
namespace Stockfish {
namespace {
template<GenType Type, Direction D>
ExtMove* make_promotions(ExtMove* moveList, Square to) {
template<GenType Type, Direction D, bool Enemy>
ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS;
if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS)
if constexpr (Type == CAPTURES || all)
*moveList++ = Move::make<PROMOTION>(to - D, to, QUEEN);
if constexpr ((Type == CAPTURES && Enemy) || (Type == QUIETS && !Enemy) || all)
{
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
*moveList++ = Move::make<PROMOTION>(to - D, to, ROOK);
*moveList++ = Move::make<PROMOTION>(to - D, to, BISHOP);
*moveList++ = Move::make<PROMOTION>(to - D, to, KNIGHT);
}
return moveList;
}
}
template<Color Us, GenType Type>
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
template<Color Us, GenType Type>
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
constexpr Color Them = ~Us;
constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
constexpr Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB);
constexpr Direction Up = pawn_push(Us);
constexpr Direction UpRight = (Us == WHITE ? NORTH_EAST : SOUTH_WEST);
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
const Bitboard emptySquares = ~pos.pieces();
const Bitboard enemies = Type == EVASIONS ? pos.checkers()
: pos.pieces(Them);
const Bitboard enemies = Type == EVASIONS ? pos.checkers() : pos.pieces(Them);
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
// Single and double pawn pushes, no promotions
if (Type != CAPTURES)
if constexpr (Type != CAPTURES)
{
Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
Bitboard b1 = shift<Up>(pawnsNotOn7) & emptySquares;
Bitboard b2 = shift<Up>(b1 & TRank3BB) & emptySquares;
if (Type == EVASIONS) // Consider only blocking squares
if constexpr (Type == EVASIONS) // Consider only blocking squares
{
b1 &= target;
b2 &= target;
}
if (Type == QUIET_CHECKS)
if constexpr (Type == QUIET_CHECKS)
{
// To make a quiet check, you either make a direct check by pushing a pawn
// or push a blocker pawn that is not on the same file as the enemy king.
// Discovered check promotion has been already generated amongst the captures.
Square ksq = pos.square<KING>(Them);
Square ksq = pos.square<KING>(Them);
Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq);
b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns);
b2 &= pawn_attacks_bb(Them, ksq) | shift<Up+Up>(dcCandidatePawns);
b1 &= pawn_attacks_bb(Them, ksq) | shift<Up>(dcCandidatePawns);
b2 &= pawn_attacks_bb(Them, ksq) | shift<Up + Up>(dcCandidatePawns);
}
while (b1)
{
Square to = pop_lsb(b1);
*moveList++ = make_move(to - Up, to);
Square to = pop_lsb(b1);
*moveList++ = Move(to - Up, to);
}
while (b2)
{
Square to = pop_lsb(b2);
*moveList++ = make_move(to - Up - Up, to);
Square to = pop_lsb(b2);
*moveList++ = Move(to - Up - Up, to);
}
}
@ -99,38 +103,38 @@ namespace {
if (pawnsOn7)
{
Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
Bitboard b2 = shift<UpLeft>(pawnsOn7) & enemies;
Bitboard b3 = shift<Up>(pawnsOn7) & emptySquares;
if (Type == EVASIONS)
if constexpr (Type == EVASIONS)
b3 &= target;
while (b1)
moveList = make_promotions<Type, UpRight>(moveList, pop_lsb(b1));
moveList = make_promotions<Type, UpRight, true>(moveList, pop_lsb(b1));
while (b2)
moveList = make_promotions<Type, UpLeft >(moveList, pop_lsb(b2));
moveList = make_promotions<Type, UpLeft, true>(moveList, pop_lsb(b2));
while (b3)
moveList = make_promotions<Type, Up >(moveList, pop_lsb(b3));
moveList = make_promotions<Type, Up, false>(moveList, pop_lsb(b3));
}
// Standard and en passant captures
if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
{
Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
Bitboard b2 = shift<UpLeft >(pawnsNotOn7) & enemies;
Bitboard b2 = shift<UpLeft>(pawnsNotOn7) & enemies;
while (b1)
{
Square to = pop_lsb(b1);
*moveList++ = make_move(to - UpRight, to);
Square to = pop_lsb(b1);
*moveList++ = Move(to - UpRight, to);
}
while (b2)
{
Square to = pop_lsb(b2);
*moveList++ = make_move(to - UpLeft, to);
Square to = pop_lsb(b2);
*moveList++ = Move(to - UpLeft, to);
}
if (pos.ep_square() != SQ_NONE)
@ -146,16 +150,16 @@ namespace {
assert(b1);
while (b1)
*moveList++ = make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
*moveList++ = Move::make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
}
}
return moveList;
}
}
template<Color Us, PieceType Pt, bool Checks>
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
template<Color Us, PieceType Pt, bool Checks>
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
@ -163,43 +167,43 @@ namespace {
while (bb)
{
Square from = pop_lsb(bb);
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
Square from = pop_lsb(bb);
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
// To check, you either move freely a blocker or make a direct check.
if (Checks && (Pt == QUEEN || !(pos.blockers_for_king(~Us) & from)))
b &= pos.check_squares(Pt);
while (b)
*moveList++ = make_move(from, pop_lsb(b));
*moveList++ = Move(from, pop_lsb(b));
}
return moveList;
}
}
template<Color Us, GenType Type>
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
template<Color Us, GenType Type>
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
static_assert(Type != LEGAL, "Unsupported type in generate_all()");
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations
const Square ksq = pos.square<KING>(Us);
Bitboard target;
constexpr bool Checks = Type == QUIET_CHECKS; // Reduce template instantiations
const Square ksq = pos.square<KING>(Us);
Bitboard target;
// 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()))
: Type == NON_EVASIONS ? ~pos.pieces( Us)
: Type == CAPTURES ? pos.pieces(~Us)
: ~pos.pieces( ); // QUIETS || QUIET_CHECKS
target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers()))
: Type == NON_EVASIONS ? ~pos.pieces(Us)
: Type == CAPTURES ? pos.pieces(~Us)
: ~pos.pieces(); // QUIETS || QUIET_CHECKS
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
moveList = generate_moves<Us, BISHOP, Checks>(pos, moveList, target);
moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
moveList = generate_moves<Us, ROOK, Checks>(pos, moveList, target);
moveList = generate_moves<Us, QUEEN, Checks>(pos, moveList, target);
}
if (!Checks || pos.blockers_for_king(~Us) & ksq)
@ -209,38 +213,38 @@ namespace {
b &= ~attacks_bb<QUEEN>(pos.square<KING>(~Us));
while (b)
*moveList++ = make_move(ksq, pop_lsb(b));
*moveList++ = Move(ksq, pop_lsb(b));
if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING))
for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE})
if (!pos.castling_impeded(cr) && pos.can_castle(cr))
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
*moveList++ = Move::make<CASTLING>(ksq, pos.castling_rook_square(cr));
}
return moveList;
}
}
} // namespace
} // namespace
/// <CAPTURES> Generates all pseudo-legal captures plus queen promotions
/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
/// <EVASIONS> Generates all pseudo-legal check evasions when the side to move is in check
/// <QUIET_CHECKS> Generates all pseudo-legal non-captures giving check, except castling and promotions
/// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
///
/// Returns a pointer to the end of the move list.
// <CAPTURES> Generates all pseudo-legal captures plus queen promotions
// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
// <EVASIONS> Generates all pseudo-legal check evasions
// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
// <QUIET_CHECKS> Generates all pseudo-legal non-captures giving check,
// except castling and promotions
//
// Returns a pointer to the end of the move list.
template<GenType Type>
ExtMove* generate(const Position& pos, ExtMove* moveList) {
static_assert(Type != LEGAL, "Unsupported type in generate()");
assert((Type == EVASIONS) == (bool)pos.checkers());
static_assert(Type != LEGAL, "Unsupported type in generate()");
assert((Type == EVASIONS) == bool(pos.checkers()));
Color us = pos.side_to_move();
Color us = pos.side_to_move();
return us == WHITE ? generate_all<WHITE, Type>(pos, moveList)
: generate_all<BLACK, Type>(pos, moveList);
return us == WHITE ? generate_all<WHITE, Type>(pos, moveList)
: generate_all<BLACK, Type>(pos, moveList);
}
// Explicit template instantiations
@ -251,26 +255,26 @@ template ExtMove* generate<QUIET_CHECKS>(const Position&, ExtMove*);
template ExtMove* generate<NON_EVASIONS>(const Position&, ExtMove*);
/// generate<LEGAL> generates all the legal moves in the given position
// generate<LEGAL> generates all the legal moves in the given position
template<>
ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
Color us = pos.side_to_move();
Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
Square ksq = pos.square<KING>(us);
ExtMove* cur = moveList;
Color us = pos.side_to_move();
Bitboard pinned = pos.blockers_for_king(us) & pos.pieces(us);
Square ksq = pos.square<KING>(us);
ExtMove* cur = moveList;
moveList = pos.checkers() ? generate<EVASIONS >(pos, moveList)
: generate<NON_EVASIONS>(pos, moveList);
while (cur != moveList)
if ( ((pinned && pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT)
&& !pos.legal(*cur))
*cur = (--moveList)->move;
else
++cur;
moveList =
pos.checkers() ? generate<EVASIONS>(pos, moveList) : generate<NON_EVASIONS>(pos, moveList);
while (cur != moveList)
if (((pinned & cur->from_sq()) || cur->from_sq() == ksq || cur->type_of() == EN_PASSANT)
&& !pos.legal(*cur))
*cur = *(--moveList);
else
++cur;
return moveList;
return moveList;
}
} // namespace Stockfish
} // namespace Stockfish

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,7 +19,8 @@
#ifndef MOVEGEN_H_INCLUDED
#define MOVEGEN_H_INCLUDED
#include <algorithm>
#include <algorithm> // IWYU pragma: keep
#include <cstddef>
#include "types.h"
@ -28,50 +29,46 @@ namespace Stockfish {
class Position;
enum GenType {
CAPTURES,
QUIETS,
QUIET_CHECKS,
EVASIONS,
NON_EVASIONS,
LEGAL
CAPTURES,
QUIETS,
QUIET_CHECKS,
EVASIONS,
NON_EVASIONS,
LEGAL
};
struct ExtMove {
Move move;
int value;
struct ExtMove: public Move {
int value;
operator Move() const { return move; }
void operator=(Move m) { move = m; }
void operator=(Move m) { data = m.raw(); }
// Inhibit unwanted implicit conversions to Move
// with an ambiguity that yields to a compile error.
operator float() const = delete;
// Inhibit unwanted implicit conversions to Move
// with an ambiguity that yields to a compile error.
operator float() const = delete;
};
inline bool operator<(const ExtMove& f, const ExtMove& s) {
return f.value < s.value;
}
inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; }
template<GenType>
ExtMove* generate(const Position& pos, ExtMove* moveList);
/// The MoveList struct is a simple wrapper around generate(). It sometimes comes
/// in handy to use this class instead of the low level generate() function.
// The MoveList struct wraps the generate() function and returns a convenient
// list of moves. Using MoveList is sometimes preferable to directly calling
// the lower level generate() function.
template<GenType T>
struct MoveList {
explicit MoveList(const Position& pos) : last(generate<T>(pos, moveList)) {}
const ExtMove* begin() const { return moveList; }
const ExtMove* end() const { return last; }
size_t size() const { return last - moveList; }
bool contains(Move move) const {
return std::find(begin(), end(), move) != end();
}
explicit MoveList(const Position& pos) :
last(generate<T>(pos, moveList)) {}
const ExtMove* begin() const { return moveList; }
const ExtMove* end() const { return last; }
size_t size() const { return last - moveList; }
bool contains(Move move) const { return std::find(begin(), end(), move) != end(); }
private:
ExtMove moveList[MAX_MOVES], *last;
private:
ExtMove moveList[MAX_MOVES], *last;
};
} // namespace Stockfish
} // namespace Stockfish
#endif // #ifndef MOVEGEN_H_INCLUDED
#endif // #ifndef MOVEGEN_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,258 +16,371 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#include "movepick.h"
#include <algorithm>
#include <cassert>
#include <iterator>
#include <utility>
#include "bitboard.h"
#include "position.h"
namespace Stockfish {
namespace {
enum Stages {
MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE,
EVASION_TT, EVASION_INIT, EVASION,
PROBCUT_TT, PROBCUT_INIT, PROBCUT,
QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK
};
enum Stages {
// generate main search moves
MAIN_TT,
CAPTURE_INIT,
GOOD_CAPTURE,
REFUTATION,
QUIET_INIT,
GOOD_QUIET,
BAD_CAPTURE,
BAD_QUIET,
// partial_insertion_sort() sorts 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) {
// generate evasion moves
EVASION_TT,
EVASION_INIT,
EVASION,
// generate probcut moves
PROBCUT_TT,
PROBCUT_INIT,
PROBCUT,
// generate qsearch moves
QSEARCH_TT,
QCAPTURE_INIT,
QCAPTURE,
QCHECK_INIT,
QCHECK
};
// 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) {
for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p)
if (p->value >= limit)
{
ExtMove tmp = *p, *q;
*p = *++sortedEnd;
*p = *++sortedEnd;
for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q)
*q = *(q - 1);
*q = tmp;
}
}
} // namespace
/// Constructors of the MovePicker class. As arguments we pass information
/// to help it to return the (presumably) good moves first, to decide which
/// moves to return (in the quiescence search, for instance, we only want to
/// search captures, promotions, and some checks) and how important good move
/// ordering is at the current node.
/// MovePicker constructor for the main search
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
const CapturePieceToHistory* cph,
const PieceToHistory** ch,
Move cm,
const Move* killers)
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch),
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d)
{
assert(d > 0);
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) +
!(ttm && pos.pseudo_legal(ttm));
}
/// MovePicker constructor for quiescence search
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
const CapturePieceToHistory* cph,
const PieceToHistory** ch,
Square rs)
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d)
{
assert(d <= 0);
} // namespace
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) +
!( ttm
&& (pos.checkers() || depth > DEPTH_QS_RECAPTURES || to_sq(ttm) == recaptureSquare)
&& pos.pseudo_legal(ttm));
// Constructors of the MovePicker class. As arguments, we pass information
// to help it return the (presumably) good moves first, to decide which
// moves to return (in the quiescence search, for instance, we only want to
// search captures, promotions, and some checks) and how important a good
// move ordering is at the current node.
// MovePicker constructor for the main search
MovePicker::MovePicker(const Position& p,
Move ttm,
Depth d,
const ButterflyHistory* mh,
const CapturePieceToHistory* cph,
const PieceToHistory** ch,
const PawnHistory* ph,
Move cm,
const Move* killers) :
pos(p),
mainHistory(mh),
captureHistory(cph),
continuationHistory(ch),
pawnHistory(ph),
ttMove(ttm),
refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}},
depth(d) {
assert(d > 0);
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm));
}
/// MovePicker constructor for ProbCut: we generate captures with SEE greater
/// than or equal to the given threshold.
MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
: pos(p), captureHistory(cph), ttMove(ttm), threshold(th)
{
assert(!pos.checkers());
// Constructor for quiescence search
MovePicker::MovePicker(const Position& p,
Move ttm,
Depth d,
const ButterflyHistory* mh,
const CapturePieceToHistory* cph,
const PieceToHistory** ch,
const PawnHistory* ph) :
pos(p),
mainHistory(mh),
captureHistory(cph),
continuationHistory(ch),
pawnHistory(ph),
ttMove(ttm),
depth(d) {
assert(d <= 0);
stage = PROBCUT_TT + !(ttm && pos.capture(ttm)
&& pos.pseudo_legal(ttm)
&& pos.see_ge(ttm, threshold));
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm));
}
/// MovePicker::score() 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 histories.
// Constructor for ProbCut: we generate captures with SEE greater
// than or equal to the given threshold.
MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) :
pos(p),
captureHistory(cph),
ttMove(ttm),
threshold(th) {
assert(!pos.checkers());
stage = PROBCUT_TT
+ !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold));
}
// Assigns a numerical value to each move in a list, used
// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring
// captures with a good history. Quiets moves are ordered using the history tables.
template<GenType Type>
void MovePicker::score() {
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
for (auto& m : *this)
if constexpr (Type == CAPTURES)
m.value = int(PieceValue[MG][pos.piece_on(to_sq(m))]) * 6
+ (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))];
[[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook,
threatenedPieces;
if constexpr (Type == QUIETS)
{
Color us = pos.side_to_move();
else if constexpr (Type == QUIETS)
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
+ 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
+ (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
+ (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
+ (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)];
threatenedByPawn = pos.attacks_by<PAWN>(~us);
threatenedByMinor =
pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
threatenedByRook = pos.attacks_by<ROOK>(~us) | threatenedByMinor;
else // Type == EVASIONS
{
if (pos.capture(m))
m.value = PieceValue[MG][pos.piece_on(to_sq(m))]
- Value(type_of(pos.moved_piece(m)));
else
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
+ 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
- (1 << 28);
}
// Pieces threatened by pieces of lesser material value
threatenedPieces = (pos.pieces(us, QUEEN) & threatenedByRook)
| (pos.pieces(us, ROOK) & threatenedByMinor)
| (pos.pieces(us, KNIGHT, BISHOP) & threatenedByPawn);
}
for (auto& m : *this)
if constexpr (Type == CAPTURES)
m.value =
7 * int(PieceValue[pos.piece_on(m.to_sq())])
+ (*captureHistory)[pos.moved_piece(m)][m.to_sq()][type_of(pos.piece_on(m.to_sq()))];
else if constexpr (Type == QUIETS)
{
Piece pc = pos.moved_piece(m);
PieceType pt = type_of(pc);
Square from = m.from_sq();
Square to = m.to_sq();
// histories
m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()];
m.value += 2 * (*pawnHistory)[pawn_structure_index(pos)][pc][to];
m.value += 2 * (*continuationHistory[0])[pc][to];
m.value += (*continuationHistory[1])[pc][to];
m.value += (*continuationHistory[2])[pc][to] / 4;
m.value += (*continuationHistory[3])[pc][to];
m.value += (*continuationHistory[5])[pc][to];
// bonus for checks
m.value += bool(pos.check_squares(pt) & to) * 16384;
// bonus for escaping from capture
m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 51700
: pt == ROOK && !(to & threatenedByMinor) ? 25600
: !(to & threatenedByPawn) ? 14450
: 0)
: 0;
// malus for putting piece en prise
m.value -= !(threatenedPieces & from)
? (pt == QUEEN ? bool(to & threatenedByRook) * 48150
+ bool(to & threatenedByMinor) * 10650
: pt == ROOK ? bool(to & threatenedByMinor) * 24335
: pt != PAWN ? bool(to & threatenedByPawn) * 14950
: 0)
: 0;
}
else // Type == EVASIONS
{
if (pos.capture_stage(m))
m.value =
PieceValue[pos.piece_on(m.to_sq())] - type_of(pos.moved_piece(m)) + (1 << 28);
else
m.value = (*mainHistory)[pos.side_to_move()][m.from_to()]
+ (*continuationHistory[0])[pos.moved_piece(m)][m.to_sq()]
+ (*pawnHistory)[pawn_structure_index(pos)][pos.moved_piece(m)][m.to_sq()];
}
}
/// MovePicker::select() returns the next move satisfying a predicate function.
/// It never returns the TT move.
// Returns the next move satisfying a predicate function.
// It never returns the TT move.
template<MovePicker::PickType T, typename Pred>
Move MovePicker::select(Pred filter) {
while (cur < endMoves)
{
if (T == Best)
std::swap(*cur, *std::max_element(cur, endMoves));
while (cur < endMoves)
{
if constexpr (T == Best)
std::swap(*cur, *std::max_element(cur, endMoves));
if (*cur != ttMove && filter())
return *cur++;
if (*cur != ttMove && filter())
return *cur++;
cur++;
}
return MOVE_NONE;
cur++;
}
return Move::none();
}
/// MovePicker::next_move() is the most important method of the MovePicker class. It
/// returns a new pseudo-legal move every time it is called until there are no more
/// moves left, picking the move with the highest score from a list of generated moves.
// Most important method of the MovePicker class. It
// returns a new pseudo-legal move every time it is called until there are no more
// moves left, picking the move with the highest score from a list of generated moves.
Move MovePicker::next_move(bool skipQuiets) {
auto quiet_threshold = [](Depth d) { return -3560 * d; };
top:
switch (stage) {
switch (stage)
{
case MAIN_TT:
case EVASION_TT:
case QSEARCH_TT:
case PROBCUT_TT:
++stage;
return ttMove;
case MAIN_TT :
case EVASION_TT :
case QSEARCH_TT :
case PROBCUT_TT :
++stage;
return ttMove;
case CAPTURE_INIT:
case PROBCUT_INIT:
case QCAPTURE_INIT:
cur = endBadCaptures = moves;
endMoves = generate<CAPTURES>(pos, cur);
case CAPTURE_INIT :
case PROBCUT_INIT :
case QCAPTURE_INIT :
cur = endBadCaptures = moves;
endMoves = generate<CAPTURES>(pos, cur);
score<CAPTURES>();
++stage;
goto top;
score<CAPTURES>();
partial_insertion_sort(cur, endMoves, std::numeric_limits<int>::min());
++stage;
goto top;
case GOOD_CAPTURE:
if (select<Best>([&](){
return pos.see_ge(*cur, Value(-69 * cur->value / 1024)) ?
// Move losing capture to endBadCaptures to be tried later
true : (*endBadCaptures++ = *cur, false); }))
return *(cur - 1);
case GOOD_CAPTURE :
if (select<Next>([&]() {
// Move losing capture to endBadCaptures to be tried later
return pos.see_ge(*cur, -cur->value / 18) ? true
: (*endBadCaptures++ = *cur, false);
}))
return *(cur - 1);
// Prepare the pointers to loop over the refutations array
cur = std::begin(refutations);
endMoves = std::end(refutations);
// Prepare the pointers to loop over the refutations array
cur = std::begin(refutations);
endMoves = std::end(refutations);
// If the countermove is the same as a killer, skip it
if ( refutations[0].move == refutations[2].move
|| refutations[1].move == refutations[2].move)
--endMoves;
// If the countermove is the same as a killer, skip it
if (refutations[0] == refutations[2] || refutations[1] == refutations[2])
--endMoves;
++stage;
[[fallthrough]];
++stage;
[[fallthrough]];
case REFUTATION:
if (select<Next>([&](){ return *cur != MOVE_NONE
&& !pos.capture(*cur)
&& pos.pseudo_legal(*cur); }))
return *(cur - 1);
++stage;
[[fallthrough]];
case REFUTATION :
if (select<Next>([&]() {
return *cur != Move::none() && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur);
}))
return *(cur - 1);
++stage;
[[fallthrough]];
case QUIET_INIT:
if (!skipQuiets)
{
cur = endBadCaptures;
endMoves = generate<QUIETS>(pos, cur);
case QUIET_INIT :
if (!skipQuiets)
{
cur = endBadCaptures;
endMoves = beginBadQuiets = endBadQuiets = generate<QUIETS>(pos, cur);
score<QUIETS>();
partial_insertion_sort(cur, endMoves, -3000 * depth);
}
score<QUIETS>();
partial_insertion_sort(cur, endMoves, quiet_threshold(depth));
}
++stage;
[[fallthrough]];
++stage;
[[fallthrough]];
case QUIET:
if ( !skipQuiets
&& select<Next>([&](){return *cur != refutations[0].move
&& *cur != refutations[1].move
&& *cur != refutations[2].move;}))
return *(cur - 1);
case GOOD_QUIET :
if (!skipQuiets && select<Next>([&]() {
return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2];
}))
{
if ((cur - 1)->value > -7998 || (cur - 1)->value <= quiet_threshold(depth))
return *(cur - 1);
// Prepare the pointers to loop over the bad captures
cur = moves;
endMoves = endBadCaptures;
// Remaining quiets are bad
beginBadQuiets = cur - 1;
}
++stage;
[[fallthrough]];
// Prepare the pointers to loop over the bad captures
cur = moves;
endMoves = endBadCaptures;
case BAD_CAPTURE:
return select<Next>([](){ return true; });
++stage;
[[fallthrough]];
case EVASION_INIT:
cur = moves;
endMoves = generate<EVASIONS>(pos, cur);
case BAD_CAPTURE :
if (select<Next>([]() { return true; }))
return *(cur - 1);
score<EVASIONS>();
++stage;
[[fallthrough]];
// Prepare the pointers to loop over the bad quiets
cur = beginBadQuiets;
endMoves = endBadQuiets;
case EVASION:
return select<Best>([](){ return true; });
++stage;
[[fallthrough]];
case PROBCUT:
return select<Best>([&](){ return pos.see_ge(*cur, threshold); });
case BAD_QUIET :
if (!skipQuiets)
return select<Next>([&]() {
return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2];
});
case QCAPTURE:
if (select<Best>([&](){ return depth > DEPTH_QS_RECAPTURES
|| to_sq(*cur) == recaptureSquare; }))
return *(cur - 1);
return Move::none();
// If we did not find any move and we do not try checks, we have finished
if (depth != DEPTH_QS_CHECKS)
return MOVE_NONE;
case EVASION_INIT :
cur = moves;
endMoves = generate<EVASIONS>(pos, cur);
++stage;
[[fallthrough]];
score<EVASIONS>();
++stage;
[[fallthrough]];
case QCHECK_INIT:
cur = moves;
endMoves = generate<QUIET_CHECKS>(pos, cur);
case EVASION :
return select<Best>([]() { return true; });
++stage;
[[fallthrough]];
case PROBCUT :
return select<Next>([&]() { return pos.see_ge(*cur, threshold); });
case QCHECK:
return select<Next>([](){ return true; });
}
case QCAPTURE :
if (select<Next>([]() { return true; }))
return *(cur - 1);
assert(false);
return MOVE_NONE; // Silence warning
// If we did not find any move and we do not try checks, we have finished
if (depth != DEPTH_QS_CHECKS)
return Move::none();
++stage;
[[fallthrough]];
case QCHECK_INIT :
cur = moves;
endMoves = generate<QUIET_CHECKS>(pos, cur);
++stage;
[[fallthrough]];
case QCHECK :
return select<Next>([]() { return true; });
}
assert(false);
return Move::none(); // Silence warning
}
} // namespace Stockfish
} // namespace Stockfish

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,9 +19,14 @@
#ifndef MOVEPICK_H_INCLUDED
#define MOVEPICK_H_INCLUDED
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <limits>
#include <type_traits>
#include <type_traits> // IWYU pragma: keep
#include "movegen.h"
#include "position.h"
@ -29,125 +34,169 @@
namespace Stockfish {
/// StatsEntry stores the stat table value. It is usually a number but could
/// be a move or even a nested history. We use a class instead of naked value
/// to directly call history update operator<<() on the entry so to use stats
/// tables at caller sites as simple multi-dim arrays.
constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2
constexpr int CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2
constexpr int CORRECTION_HISTORY_LIMIT = 1024;
static_assert((PAWN_HISTORY_SIZE & (PAWN_HISTORY_SIZE - 1)) == 0,
"PAWN_HISTORY_SIZE has to be a power of 2");
static_assert((CORRECTION_HISTORY_SIZE & (CORRECTION_HISTORY_SIZE - 1)) == 0,
"CORRECTION_HISTORY_SIZE has to be a power of 2");
enum PawnHistoryType {
Normal,
Correction
};
template<PawnHistoryType T = Normal>
inline int pawn_structure_index(const Position& pos) {
return pos.pawn_key() & ((T == Normal ? PAWN_HISTORY_SIZE : CORRECTION_HISTORY_SIZE) - 1);
}
// StatsEntry stores the stat table value. It is usually a number but could
// be a move or even a nested history. We use a class instead of a naked value
// to directly call history update operator<<() on the entry so to use stats
// tables at caller sites as simple multi-dim arrays.
template<typename T, int D>
class StatsEntry {
T entry;
T entry;
public:
void operator=(const T& v) { entry = v; }
T* operator&() { return &entry; }
T* operator->() { return &entry; }
operator const T&() const { return entry; }
public:
void operator=(const T& v) { entry = v; }
T* operator&() { return &entry; }
T* operator->() { return &entry; }
operator const T&() const { return entry; }
void operator<<(int bonus) {
assert(abs(bonus) <= D); // Ensure range is [-D, D]
static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
void operator<<(int bonus) {
static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
entry += bonus - entry * abs(bonus) / D;
// Make sure that bonus is in range [-D, D]
int clampedBonus = std::clamp(bonus, -D, D);
entry += clampedBonus - entry * std::abs(clampedBonus) / D;
assert(abs(entry) <= D);
}
assert(std::abs(entry) <= D);
}
};
/// Stats is a generic N-dimensional array used to store various statistics.
/// The first template parameter T is the base type of the array, the second
/// template parameter D limits the range of updates in [-D, D] when we update
/// values with the << operator, while the last parameters (Size and Sizes)
/// encode the dimensions of the array.
template <typename T, int D, int Size, int... Sizes>
struct Stats : public std::array<Stats<T, D, Sizes...>, Size>
{
typedef Stats<T, D, Size, Sizes...> stats;
// Stats is a generic N-dimensional array used to store various statistics.
// 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, while the last parameters (Size and Sizes)
// encode the dimensions of the array.
template<typename T, int D, int Size, int... Sizes>
struct Stats: public std::array<Stats<T, D, Sizes...>, Size> {
using stats = Stats<T, D, Size, Sizes...>;
void fill(const T& v) {
void fill(const T& v) {
// For standard-layout 'this' points to first struct member
assert(std::is_standard_layout<stats>::value);
// For standard-layout 'this' points to the first struct member
assert(std::is_standard_layout_v<stats>);
typedef StatsEntry<T, D> entry;
entry* p = reinterpret_cast<entry*>(this);
std::fill(p, p + sizeof(*this) / sizeof(entry), v);
}
using entry = StatsEntry<T, D>;
entry* p = reinterpret_cast<entry*>(this);
std::fill(p, p + sizeof(*this) / sizeof(entry), v);
}
};
template <typename T, int D, int Size>
struct Stats<T, D, Size> : public std::array<StatsEntry<T, D>, Size> {};
template<typename T, int D, int Size>
struct Stats<T, D, Size>: public std::array<StatsEntry<T, D>, Size> {};
/// In stats table, D=0 means that the template parameter is not used
enum StatsParams { NOT_USED = 0 };
enum StatsType { NoCaptures, Captures };
// In stats table, D=0 means that the template parameter is not used
enum StatsParams {
NOT_USED = 0
};
enum StatsType {
NoCaptures,
Captures
};
/// ButterflyHistory records how often quiet moves have been successful or
/// unsuccessful during the current search, and is used for reduction and move
/// ordering decisions. It uses 2 tables (one for each color) indexed by
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
typedef Stats<int16_t, 14365, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)> ButterflyHistory;
// ButterflyHistory records how often quiet moves have been successful or unsuccessful
// during the current search, and is used for reduction and move ordering decisions.
// It uses 2 tables (one for each color) indexed by the move's from and to squares,
// see www.chessprogramming.org/Butterfly_Boards (~11 elo)
using ButterflyHistory = Stats<int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)>;
/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
/// move, see www.chessprogramming.org/Countermove_Heuristic
typedef Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB> CounterMoveHistory;
// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
// move, see www.chessprogramming.org/Countermove_Heuristic
using CounterMoveHistory = Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB>;
/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
typedef Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB> CapturePieceToHistory;
// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
using CapturePieceToHistory = Stats<int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;
/// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
typedef Stats<int16_t, 29952, PIECE_NB, SQUARE_NB> PieceToHistory;
// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
using PieceToHistory = Stats<int16_t, 29952, PIECE_NB, SQUARE_NB>;
/// ContinuationHistory is the combined history of a given pair of moves, usually
/// the current one given a previous one. The nested history table is based on
/// PieceToHistory instead of ButterflyBoards.
typedef Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB> ContinuationHistory;
// ContinuationHistory is the combined history of a given pair of moves, usually
// the current one given a previous one. The nested history table is based on
// PieceToHistory instead of ButterflyBoards.
// (~63 elo)
using ContinuationHistory = Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB>;
// PawnHistory is addressed by the pawn structure and a move's [piece][to]
using PawnHistory = Stats<int16_t, 8192, PAWN_HISTORY_SIZE, PIECE_NB, SQUARE_NB>;
/// MovePicker class is used to pick one pseudo-legal move at a time from the
/// current position. The most important method is next_move(), which returns a
/// new pseudo-legal move each time it is called, until there are no moves left,
/// when MOVE_NONE is returned. In order to improve the efficiency of the
/// alpha-beta algorithm, MovePicker attempts to return the moves which are most
/// likely to get a cut-off first.
// CorrectionHistory is addressed by color and pawn structure
using CorrectionHistory =
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>;
// MovePicker class is used to pick one pseudo-legal move at a time from the
// current position. The most important method is next_move(), which returns a
// new pseudo-legal move each time it is called, until there are no moves left,
// when Move::none() is returned. In order to improve the efficiency of the
// alpha-beta algorithm, MovePicker attempts to return the moves which are most
// likely to get a cut-off first.
class MovePicker {
enum PickType { Next, Best };
enum PickType {
Next,
Best
};
public:
MovePicker(const MovePicker&) = delete;
MovePicker& operator=(const MovePicker&) = delete;
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
const CapturePieceToHistory*,
const PieceToHistory**,
Move,
const Move*);
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
const CapturePieceToHistory*,
const PieceToHistory**,
Square);
MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
Move next_move(bool skipQuiets = false);
public:
MovePicker(const MovePicker&) = delete;
MovePicker& operator=(const MovePicker&) = delete;
MovePicker(const Position&,
Move,
Depth,
const ButterflyHistory*,
const CapturePieceToHistory*,
const PieceToHistory**,
const PawnHistory*,
Move,
const Move*);
MovePicker(const Position&,
Move,
Depth,
const ButterflyHistory*,
const CapturePieceToHistory*,
const PieceToHistory**,
const PawnHistory*);
MovePicker(const Position&, Move, int, const CapturePieceToHistory*);
Move next_move(bool skipQuiets = false);
private:
template<PickType T, typename Pred> Move select(Pred);
template<GenType> void score();
ExtMove* begin() { return cur; }
ExtMove* end() { return endMoves; }
private:
template<PickType T, typename Pred>
Move select(Pred);
template<GenType>
void score();
ExtMove* begin() { return cur; }
ExtMove* end() { return endMoves; }
const Position& pos;
const ButterflyHistory* mainHistory;
const CapturePieceToHistory* captureHistory;
const PieceToHistory** continuationHistory;
Move ttMove;
ExtMove refutations[3], *cur, *endMoves, *endBadCaptures;
int stage;
Square recaptureSquare;
Value threshold;
Depth depth;
ExtMove moves[MAX_MOVES];
const Position& pos;
const ButterflyHistory* mainHistory;
const CapturePieceToHistory* captureHistory;
const PieceToHistory** continuationHistory;
const PawnHistory* pawnHistory;
Move ttMove;
ExtMove refutations[3], *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets;
int stage;
int threshold;
Depth depth;
ExtMove moves[MAX_MOVES];
};
} // namespace Stockfish
} // namespace Stockfish
#endif // #ifndef MOVEPICK_H_INCLUDED
#endif // #ifndef MOVEPICK_H_INCLUDED

View file

@ -1,403 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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/>.
*/
// Code for calculating NNUE evaluation function
#include <iostream>
#include <set>
#include <sstream>
#include <iomanip>
#include <fstream>
#include "../evaluate.h"
#include "../position.h"
#include "../misc.h"
#include "../uci.h"
#include "../types.h"
#include "evaluate_nnue.h"
namespace Stockfish::Eval::NNUE {
// Input feature converter
LargePagePtr<FeatureTransformer> featureTransformer;
// Evaluation function
AlignedPtr<Network> network[LayerStacks];
// Evaluation function file name
std::string fileName;
std::string netDescription;
namespace Detail {
// Initialize the evaluation function parameters
template <typename T>
void initialize(AlignedPtr<T>& pointer) {
pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
std::memset(pointer.get(), 0, sizeof(T));
}
template <typename T>
void initialize(LargePagePtr<T>& pointer) {
static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
std::memset(pointer.get(), 0, sizeof(T));
}
// Read evaluation function parameters
template <typename T>
bool read_parameters(std::istream& stream, T& reference) {
std::uint32_t header;
header = read_little_endian<std::uint32_t>(stream);
if (!stream || header != T::get_hash_value()) return false;
return reference.read_parameters(stream);
}
// Write evaluation function parameters
template <typename T>
bool write_parameters(std::ostream& stream, const T& reference) {
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
return reference.write_parameters(stream);
}
} // namespace Detail
// Initialize the evaluation function parameters
void initialize() {
Detail::initialize(featureTransformer);
for (std::size_t i = 0; i < LayerStacks; ++i)
Detail::initialize(network[i]);
}
// Read network header
bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc)
{
std::uint32_t 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);
if (!stream || version != Version) return false;
desc->resize(size);
stream.read(&(*desc)[0], size);
return !stream.fail();
}
// Write network header
bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc)
{
write_little_endian<std::uint32_t>(stream, Version);
write_little_endian<std::uint32_t>(stream, hashValue);
write_little_endian<std::uint32_t>(stream, desc.size());
stream.write(&desc[0], desc.size());
return !stream.fail();
}
// Read network parameters
bool read_parameters(std::istream& stream) {
std::uint32_t hashValue;
if (!read_header(stream, &hashValue, &netDescription)) return false;
if (hashValue != HashValue) return false;
if (!Detail::read_parameters(stream, *featureTransformer)) return false;
for (std::size_t i = 0; i < LayerStacks; ++i)
if (!Detail::read_parameters(stream, *(network[i]))) return false;
return stream && stream.peek() == std::ios::traits_type::eof();
}
// Write network parameters
bool write_parameters(std::ostream& stream) {
if (!write_header(stream, HashValue, netDescription)) return false;
if (!Detail::write_parameters(stream, *featureTransformer)) return false;
for (std::size_t i = 0; i < LayerStacks; ++i)
if (!Detail::write_parameters(stream, *(network[i]))) return false;
return (bool)stream;
}
// Evaluation function. Perform differential calculation.
Value evaluate(const Position& pos, bool adjusted) {
// We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly.
constexpr uint64_t alignment = CacheLineSize;
int delta = 7;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType transformedFeaturesUnaligned[
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
#else
alignas(alignment)
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
#endif
ASSERT_ALIGNED(transformedFeatures, alignment);
const std::size_t bucket = (pos.count<ALL_PIECES>() - 1) / 4;
const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket);
const auto positional = network[bucket]->propagate(transformedFeatures);
// Give more value to positional evaluation when adjusted flag is set
if (adjusted)
return static_cast<Value>(((128 - delta) * psqt + (128 + delta) * positional) / 128 / OutputScale);
else
return static_cast<Value>((psqt + positional) / OutputScale);
}
struct NnueEvalTrace {
static_assert(LayerStacks == PSQTBuckets);
Value psqt[LayerStacks];
Value positional[LayerStacks];
std::size_t correctBucket;
};
static NnueEvalTrace trace_evaluate(const Position& pos) {
// We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly.
constexpr uint64_t alignment = CacheLineSize;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType transformedFeaturesUnaligned[
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
#else
alignas(alignment)
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
#endif
ASSERT_ALIGNED(transformedFeatures, alignment);
NnueEvalTrace t{};
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket) {
const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket);
const auto positional = network[bucket]->propagate(transformedFeatures);
t.psqt[bucket] = static_cast<Value>( materialist / OutputScale );
t.positional[bucket] = static_cast<Value>( positional / OutputScale );
}
return t;
}
static const std::string PieceToChar(" PNBRQK pnbrqk");
// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer.
// The buffer must have capacity for at least 5 chars.
static void format_cp_compact(Value v, char* buffer) {
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
int cp = std::abs(100 * v / PawnValueEg);
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;
}
}
// format_cp_aligned_dot() converts a Value into (centi)pawns and writes it in a buffer,
// always keeping two decimals. The buffer must have capacity for at least 7 chars.
static void format_cp_aligned_dot(Value v, char* buffer) {
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
double cp = 1.0 * std::abs(int(v)) / PawnValueEg;
sprintf(&buffer[1], "%6.2f", cp);
}
// trace() 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) {
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](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 (value != VALUE_NONE)
format_cp_compact(value, &board[y+2][x+2]);
};
// 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.
Value base = evaluate(pos);
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)
{
auto st = pos.state();
pos.remove_piece(sq);
st->accumulator.computed[WHITE] = false;
st->accumulator.computed[BLACK] = false;
Value eval = evaluate(pos);
eval = pos.side_to_move() == WHITE ? eval : -eval;
v = base - eval;
pos.put_piece(pc, sq);
st->accumulator.computed[WHITE] = false;
st->accumulator.computed[BLACK] = false;
}
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';
auto t = trace_evaluate(pos);
ss << " NNUE network contributions "
<< (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)
{
char buffer[3][8];
std::memset(buffer, '\0', sizeof(buffer));
format_cp_aligned_dot(t.psqt[bucket], buffer[0]);
format_cp_aligned_dot(t.positional[bucket], buffer[1]);
format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], buffer[2]);
ss << "| " << bucket << " "
<< " | " << buffer[0] << " "
<< " | " << buffer[1] << " "
<< " | " << buffer[2] << " "
<< " |";
if (bucket == t.correctBucket)
ss << " <-- this bucket is used";
ss << '\n';
}
ss << "+------------+------------+------------+------------+\n";
return ss.str();
}
// Load eval, from a file stream or a memory stream
bool load_eval(std::string name, std::istream& stream) {
initialize();
fileName = name;
return read_parameters(stream);
}
// Save eval, to a file stream or a memory stream
bool save_eval(std::ostream& stream) {
if (fileName.empty())
return false;
return write_parameters(stream);
}
/// Save eval, to a file given by its name
bool save_eval(const std::optional<std::string>& filename) {
std::string actualFilename;
std::string msg;
if (filename.has_value())
actualFilename = filename.value();
else
{
if (currentEvalFileName != EvalFileDefaultName)
{
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 = EvalFileDefaultName;
}
std::ofstream stream(actualFilename, std::ios_base::binary);
bool saved = save_eval(stream);
msg = saved ? "Network saved successfully to " + actualFilename
: "Failed to export a net";
sync_cout << msg << sync_endl;
return saved;
}
} // namespace Stockfish::Eval::NNUE

View file

@ -1,59 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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/>.
*/
// header used in NNUE evaluation function
#ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
#define NNUE_EVALUATE_NNUE_H_INCLUDED
#include "nnue_feature_transformer.h"
#include <memory>
namespace Stockfish::Eval::NNUE {
// Hash value of evaluation function structure
constexpr std::uint32_t HashValue =
FeatureTransformer::get_hash_value() ^ Network::get_hash_value();
// Deleter for automating release of memory area
template <typename T>
struct AlignedDeleter {
void operator()(T* ptr) const {
ptr->~T();
std_aligned_free(ptr);
}
};
template <typename T>
struct LargePageDeleter {
void operator()(T* ptr) const {
ptr->~T();
aligned_large_pages_free(ptr);
}
};
template <typename T>
using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
template <typename T>
using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
} // namespace Stockfish::Eval::NNUE
#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -20,64 +20,69 @@
#include "half_ka_v2_hm.h"
#include "../../bitboard.h"
#include "../../position.h"
#include "../../types.h"
#include "../nnue_accumulator.h"
namespace Stockfish::Eval::NNUE::Features {
// Orient a square according to perspective (rotates by 180 for black)
inline Square HalfKAv2_hm::orient(Color perspective, Square s, Square ksq) {
return Square(int(s) ^ (bool(perspective) * SQ_A8) ^ ((file_of(ksq) < FILE_E) * SQ_H1));
}
// Index of a feature for a given king position and another piece on some square
template<Color Perspective>
inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) {
return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc]
+ KingBuckets[Perspective][ksq]);
}
// Index of a feature for a given king position and another piece on some square
inline IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) {
Square o_ksq = orient(perspective, ksq, ksq);
return IndexType(orient(perspective, s, ksq) + PieceSquareIndex[perspective][pc] + PS_NB * KingBuckets[o_ksq]);
}
// Get a list of indices for active features
void HalfKAv2_hm::append_active_indices(
const Position& pos,
Color perspective,
IndexList& active
) {
Square ksq = pos.square<KING>(perspective);
Bitboard bb = pos.pieces();
// Get a list of indices for active features
template<Color Perspective>
void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) {
Square ksq = pos.square<KING>(Perspective);
Bitboard bb = pos.pieces();
while (bb)
{
Square s = pop_lsb(bb);
active.push_back(make_index(perspective, s, pos.piece_on(s), ksq));
Square s = pop_lsb(bb);
active.push_back(make_index<Perspective>(s, pos.piece_on(s), ksq));
}
}
}
// Explicit template instantiations
template void HalfKAv2_hm::append_active_indices<WHITE>(const Position& pos, IndexList& active);
template void HalfKAv2_hm::append_active_indices<BLACK>(const Position& pos, IndexList& active);
template IndexType HalfKAv2_hm::make_index<WHITE>(Square s, Piece pc, Square ksq);
template IndexType HalfKAv2_hm::make_index<BLACK>(Square s, Piece pc, Square ksq);
// append_changed_indices() : get a list of indices for recently changed features
void HalfKAv2_hm::append_changed_indices(
Square ksq,
const DirtyPiece& dp,
Color perspective,
IndexList& removed,
IndexList& added
) {
for (int i = 0; i < dp.dirty_num; ++i) {
if (dp.from[i] != SQ_NONE)
removed.push_back(make_index(perspective, dp.from[i], dp.piece[i], ksq));
if (dp.to[i] != SQ_NONE)
added.push_back(make_index(perspective, dp.to[i], dp.piece[i], ksq));
// Get a list of indices for recently changed features
template<Color Perspective>
void HalfKAv2_hm::append_changed_indices(Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added) {
for (int i = 0; i < dp.dirty_num; ++i)
{
if (dp.from[i] != SQ_NONE)
removed.push_back(make_index<Perspective>(dp.from[i], dp.piece[i], ksq));
if (dp.to[i] != SQ_NONE)
added.push_back(make_index<Perspective>(dp.to[i], dp.piece[i], ksq));
}
}
}
int HalfKAv2_hm::update_cost(const StateInfo* st) {
return st->dirtyPiece.dirty_num;
}
// Explicit template instantiations
template void HalfKAv2_hm::append_changed_indices<WHITE>(Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added);
template void HalfKAv2_hm::append_changed_indices<BLACK>(Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added);
int HalfKAv2_hm::refresh_cost(const Position& pos) {
return pos.count<ALL_PIECES>();
}
int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; }
bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count<ALL_PIECES>(); }
bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
}
}
} // namespace Stockfish::Eval::NNUE::Features

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,52 +21,47 @@
#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"
#include "../../evaluate.h"
#include "../../misc.h"
namespace Stockfish {
struct StateInfo;
struct StateInfo;
class Position;
}
namespace Stockfish::Eval::NNUE::Features {
// Feature HalfKAv2_hm: Combination of the position of own king
// and the position of pieces. Position mirrored such that king always on e..h files.
class HalfKAv2_hm {
// Feature HalfKAv2_hm: Combination of the position of own king and the
// position of pieces. Position mirrored such that king is always on e..h files.
class HalfKAv2_hm {
// unique number for each piece type on each square
// Unique number for each piece type on each square
enum {
PS_NONE = 0,
PS_W_PAWN = 0,
PS_B_PAWN = 1 * SQUARE_NB,
PS_W_KNIGHT = 2 * SQUARE_NB,
PS_B_KNIGHT = 3 * SQUARE_NB,
PS_W_BISHOP = 4 * SQUARE_NB,
PS_B_BISHOP = 5 * SQUARE_NB,
PS_W_ROOK = 6 * SQUARE_NB,
PS_B_ROOK = 7 * SQUARE_NB,
PS_W_QUEEN = 8 * SQUARE_NB,
PS_B_QUEEN = 9 * SQUARE_NB,
PS_KING = 10 * SQUARE_NB,
PS_NB = 11 * SQUARE_NB
PS_NONE = 0,
PS_W_PAWN = 0,
PS_B_PAWN = 1 * SQUARE_NB,
PS_W_KNIGHT = 2 * SQUARE_NB,
PS_B_KNIGHT = 3 * SQUARE_NB,
PS_W_BISHOP = 4 * SQUARE_NB,
PS_B_BISHOP = 5 * SQUARE_NB,
PS_W_ROOK = 6 * SQUARE_NB,
PS_B_ROOK = 7 * SQUARE_NB,
PS_W_QUEEN = 8 * SQUARE_NB,
PS_B_QUEEN = 9 * SQUARE_NB,
PS_KING = 10 * SQUARE_NB,
PS_NB = 11 * SQUARE_NB
};
static constexpr IndexType 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,
PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE },
{ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE }
};
// Orient a square according to perspective (rotates by 180 for black)
static Square orient(Color perspective, Square s, Square ksq);
// 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);
// 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,
PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE},
{PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}};
public:
// Feature name
@ -77,48 +72,79 @@ namespace Stockfish::Eval::NNUE::Features {
// Number of feature dimensions
static constexpr IndexType Dimensions =
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
static constexpr int KingBuckets[64] = {
-1, -1, -1, -1, 31, 30, 29, 28,
-1, -1, -1, -1, 27, 26, 25, 24,
-1, -1, -1, -1, 23, 22, 21, 20,
-1, -1, -1, -1, 19, 18, 17, 16,
-1, -1, -1, -1, 15, 14, 13, 12,
-1, -1, -1, -1, 11, 10, 9, 8,
-1, -1, -1, -1, 7, 6, 5, 4,
-1, -1, -1, -1, 3, 2, 1, 0
#define B(v) (v * PS_NB)
// clang-format off
static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = {
{ B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28),
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0) },
{ B( 0), B( 1), B( 2), B( 3), B( 3), B( 2), B( 1), B( 0),
B( 4), B( 5), B( 6), B( 7), B( 7), B( 6), B( 5), B( 4),
B( 8), B( 9), B(10), B(11), B(11), B(10), B( 9), B( 8),
B(12), B(13), B(14), B(15), B(15), B(14), B(13), B(12),
B(16), B(17), B(18), B(19), B(19), B(18), B(17), B(16),
B(20), B(21), B(22), B(23), B(23), B(22), B(21), B(20),
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) }
};
// clang-format on
#undef B
// clang-format off
// Orient a square according to perspective (rotates by 180 for black)
static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = {
{ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1 },
{ SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 }
};
// clang-format on
// Maximum number of simultaneously active features.
static constexpr IndexType MaxActiveDimensions = 32;
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
// Index of a feature for a given king position and another piece on some square
template<Color Perspective>
static IndexType make_index(Square s, Piece pc, Square ksq);
// Get a list of indices for active features
static void append_active_indices(
const Position& pos,
Color perspective,
IndexList& active);
template<Color Perspective>
static void append_active_indices(const Position& pos, IndexList& active);
// Get a list of indices for recently changed features
static void append_changed_indices(
Square ksq,
const DirtyPiece& dp,
Color perspective,
IndexList& removed,
IndexList& added
);
template<Color Perspective>
static void
append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
// Returns the cost of updating one perspective, the most costly one.
// Assumes no refresh needed.
static int update_cost(const StateInfo* st);
static int refresh_cost(const Position& pos);
// Returns whether the change stored in this StateInfo means that
// a full accumulator refresh is required.
// Returns whether the change stored in this StateInfo means
// that a full accumulator refresh is required.
static bool requires_refresh(const StateInfo* st, Color perspective);
};
};
} // namespace Stockfish::Eval::NNUE::Features
#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
#endif // #ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,34 +21,18 @@
#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
#include <cstdint>
#include <iostream>
#include <algorithm>
#include <type_traits>
#include "../nnue_common.h"
#include "../../simd.h"
#include "simd.h"
/*
This file contains the definition for a fully connected layer (aka affine transform).
Two approaches are employed, depending on the sizes of the transform.
Approach 1:
- used when the PaddedInputDimensions >= 128
- uses AVX512 if possible
- processes inputs in batches of 2*InputSimdWidth
- so in batches of 128 for AVX512
- the weight blocks of size InputSimdWidth are transposed such that
access is sequential
- N columns of the weight matrix are processed a time, where N
depends on the architecture (the amount of registers)
- accumulate + hadd is used
Approach 2:
- used when the PaddedInputDimensions < 128
- does not use AVX512
- expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32.
- that's why AVX512 is hard to implement
- expected use-case is small layers
- not optimized as well as the approach 1
- inputs are processed in chunks of 4, weights are respectively transposed
- accumulation happens directly to int32s
*/
@ -56,111 +40,104 @@
namespace Stockfish::Eval::NNUE::Layers {
// Fallback implementation for older/other architectures.
// Identical for both approaches. Requires the input to be padded to at least 16 values.
// Requires the input to be padded to at least 16 values.
#if !defined(USE_SSSE3)
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)
{
# if defined(USE_SSE2)
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) {
#if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON)
#if defined(USE_SSE2)
// At least a multiple of 16, with SSE2.
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
const __m128i Zeros = _mm_setzero_si128();
const auto inputVector = reinterpret_cast<const __m128i*>(input);
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
const __m128i Zeros = _mm_setzero_si128();
const auto inputVector = reinterpret_cast<const __m128i*>(input);
# elif defined(USE_MMX)
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 8;
const __m64 Zeros = _mm_setzero_si64();
const auto inputVector = reinterpret_cast<const __m64*>(input);
#elif defined(USE_NEON_DOTPROD)
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
const auto inputVector = reinterpret_cast<const int8x16_t*>(input);
# elif defined(USE_NEON)
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
const auto inputVector = reinterpret_cast<const int8x8_t*>(input);
# endif
#elif defined(USE_NEON)
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
const auto inputVector = reinterpret_cast<const int8x8_t*>(input);
#endif
for (IndexType i = 0; i < OutputDimensions; ++i) {
const IndexType offset = i * PaddedInputDimensions;
for (IndexType i = 0; i < OutputDimensions; ++i)
{
const IndexType offset = i * PaddedInputDimensions;
# if defined(USE_SSE2)
__m128i sumLo = _mm_cvtsi32_si128(biases[i]);
__m128i sumHi = Zeros;
const auto row = reinterpret_cast<const __m128i*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j) {
__m128i row_j = _mm_load_si128(&row[j]);
__m128i input_j = _mm_load_si128(&inputVector[j]);
__m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);
__m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8);
__m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros);
__m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros);
__m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo);
__m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi);
sumLo = _mm_add_epi32(sumLo, productLo);
sumHi = _mm_add_epi32(sumHi, productHi);
}
__m128i sum = _mm_add_epi32(sumLo, sumHi);
__m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2));
sum = _mm_add_epi32(sum, sumHigh_64);
__m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2));
sum = _mm_add_epi32(sum, sum_second_32);
output[i] = _mm_cvtsi128_si32(sum);
#if defined(USE_SSE2)
__m128i sumLo = _mm_cvtsi32_si128(biases[i]);
__m128i sumHi = Zeros;
const auto row = reinterpret_cast<const __m128i*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j)
{
__m128i row_j = _mm_load_si128(&row[j]);
__m128i input_j = _mm_load_si128(&inputVector[j]);
__m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);
__m128i extendedRowHi = _mm_srai_epi16(_mm_unpackhi_epi8(row_j, row_j), 8);
__m128i extendedInputLo = _mm_unpacklo_epi8(input_j, Zeros);
__m128i extendedInputHi = _mm_unpackhi_epi8(input_j, Zeros);
__m128i productLo = _mm_madd_epi16(extendedRowLo, extendedInputLo);
__m128i productHi = _mm_madd_epi16(extendedRowHi, extendedInputHi);
sumLo = _mm_add_epi32(sumLo, productLo);
sumHi = _mm_add_epi32(sumHi, productHi);
}
__m128i sum = _mm_add_epi32(sumLo, sumHi);
__m128i sumHigh_64 = _mm_shuffle_epi32(sum, _MM_SHUFFLE(1, 0, 3, 2));
sum = _mm_add_epi32(sum, sumHigh_64);
__m128i sum_second_32 = _mm_shufflelo_epi16(sum, _MM_SHUFFLE(1, 0, 3, 2));
sum = _mm_add_epi32(sum, sum_second_32);
output[i] = _mm_cvtsi128_si32(sum);
# elif defined(USE_MMX)
__m64 sumLo = _mm_cvtsi32_si64(biases[i]);
__m64 sumHi = Zeros;
const auto row = reinterpret_cast<const __m64*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j) {
__m64 row_j = row[j];
__m64 input_j = inputVector[j];
__m64 extendedRowLo = _mm_srai_pi16(_mm_unpacklo_pi8(row_j, row_j), 8);
__m64 extendedRowHi = _mm_srai_pi16(_mm_unpackhi_pi8(row_j, row_j), 8);
__m64 extendedInputLo = _mm_unpacklo_pi8(input_j, Zeros);
__m64 extendedInputHi = _mm_unpackhi_pi8(input_j, Zeros);
__m64 productLo = _mm_madd_pi16(extendedRowLo, extendedInputLo);
__m64 productHi = _mm_madd_pi16(extendedRowHi, extendedInputHi);
sumLo = _mm_add_pi32(sumLo, productLo);
sumHi = _mm_add_pi32(sumHi, productHi);
}
__m64 sum = _mm_add_pi32(sumLo, sumHi);
sum = _mm_add_pi32(sum, _mm_unpackhi_pi32(sum, sum));
output[i] = _mm_cvtsi64_si32(sum);
#elif defined(USE_NEON_DOTPROD)
int32x4_t sum = {biases[i]};
const auto row = reinterpret_cast<const int8x16_t*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j)
{
sum = vdotq_s32(sum, inputVector[j], row[j]);
}
output[i] = vaddvq_s32(sum);
# elif defined(USE_NEON)
int32x4_t sum = {biases[i]};
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j) {
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
sum = vpadalq_s16(sum, product);
}
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
#elif defined(USE_NEON)
int32x4_t sum = {biases[i]};
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j)
{
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
sum = vpadalq_s16(sum, product);
}
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
# else
std::int32_t sum = biases[i];
for (IndexType j = 0; j < InputDimensions; ++j) {
sum += weights[offset + j] * input[j];
}
output[i] = sum;
# endif
#endif
}
#else
std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions);
# if defined(USE_MMX)
_mm_empty();
# endif
}
// 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];
for (IndexType j = 0; j < OutputDimensions; ++j)
output[j] += w[j * PaddedInputDimensions] * in;
}
#endif
}
#endif
template <IndexType InDims, IndexType OutDims, typename Enabled = void>
class AffineTransform;
// A specialization for large inputs.
template <IndexType InDims, IndexType OutDims>
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) >= 2*64)>> {
template<IndexType InDims, IndexType OutDims>
class AffineTransform {
public:
// Input/output type
using InputType = std::uint8_t;
using InputType = std::uint8_t;
using OutputType = std::int32_t;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = InDims;
static constexpr IndexType InputDimensions = InDims;
static constexpr IndexType OutputDimensions = OutDims;
static constexpr IndexType PaddedInputDimensions =
@ -170,370 +147,162 @@ namespace Stockfish::Eval::NNUE::Layers {
using OutputBuffer = OutputType[PaddedOutputDimensions];
static_assert(PaddedInputDimensions >= 128, "Something went wrong. This specialization should not have been chosen.");
#if defined (USE_AVX512)
static constexpr const IndexType InputSimdWidth = 64;
static constexpr const IndexType MaxNumOutputRegs = 16;
#elif defined (USE_AVX2)
static constexpr const IndexType InputSimdWidth = 32;
static constexpr const IndexType MaxNumOutputRegs = 8;
#elif defined (USE_SSSE3)
static constexpr const IndexType InputSimdWidth = 16;
static constexpr const IndexType MaxNumOutputRegs = 8;
#elif defined (USE_NEON)
static constexpr const IndexType InputSimdWidth = 8;
static constexpr const IndexType MaxNumOutputRegs = 8;
#else
// The fallback implementation will not have permuted weights.
// We define these to avoid a lot of ifdefs later.
static constexpr const IndexType InputSimdWidth = 1;
static constexpr const IndexType MaxNumOutputRegs = 1;
#endif
// A big block is a region in the weight matrix of the size [PaddedInputDimensions, NumOutputRegs].
// A small block is a region of size [InputSimdWidth, 1]
static constexpr const IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions);
static constexpr const IndexType SmallBlockSize = InputSimdWidth;
static constexpr const IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions;
static constexpr const IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize;
static constexpr const IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize;
static constexpr const IndexType NumBigBlocks = OutputDimensions / NumOutputRegs;
static_assert(OutputDimensions % NumOutputRegs == 0);
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
std::uint32_t hashValue = 0xCC03DAE4u;
hashValue += OutputDimensions;
hashValue ^= prevHash >> 1;
hashValue ^= prevHash << 31;
return hashValue;
std::uint32_t hashValue = 0xCC03DAE4u;
hashValue += OutputDimensions;
hashValue ^= prevHash >> 1;
hashValue ^= prevHash << 31;
return hashValue;
}
/*
Transposes the small blocks within a block.
Effectively means that weights can be traversed sequentially during inference.
*/
static IndexType get_weight_index(IndexType i)
{
const IndexType smallBlock = (i / SmallBlockSize) % NumSmallBlocksInBigBlock;
const IndexType smallBlockCol = smallBlock / NumSmallBlocksPerOutput;
const IndexType smallBlockRow = smallBlock % NumSmallBlocksPerOutput;
const IndexType bigBlock = i / BigBlockSize;
const IndexType rest = i % SmallBlockSize;
const IndexType idx =
bigBlock * BigBlockSize
+ smallBlockRow * SmallBlockSize * NumOutputRegs
+ smallBlockCol * SmallBlockSize
+ rest;
return idx;
static constexpr IndexType get_weight_index_scrambled(IndexType i) {
return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4
+ i / PaddedInputDimensions * 4 + i % 4;
}
// Read network parameters
bool read_parameters(std::istream& stream) {
for (std::size_t i = 0; i < OutputDimensions; ++i)
biases[i] = read_little_endian<BiasType>(stream);
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
return !stream.fail();
}
// Write network parameters
bool write_parameters(std::ostream& stream) const {
for (std::size_t i = 0; i < OutputDimensions; ++i)
write_little_endian<BiasType>(stream, biases[i]);
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
return !stream.fail();
}
// Forward propagation
const OutputType* propagate(
const InputType* input, OutputType* output) const {
#if defined (USE_AVX512)
using acc_vec_t = __m512i;
using bias_vec_t = __m128i;
using weight_vec_t = __m512i;
using in_vec_t = __m512i;
#define vec_zero _mm512_setzero_si512()
#define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2
#define vec_hadd Simd::m512_hadd
#define vec_haddx4 Simd::m512_haddx4
#elif defined (USE_AVX2)
using acc_vec_t = __m256i;
using bias_vec_t = __m128i;
using weight_vec_t = __m256i;
using in_vec_t = __m256i;
#define vec_zero _mm256_setzero_si256()
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
#define vec_hadd Simd::m256_hadd
#define vec_haddx4 Simd::m256_haddx4
#elif defined (USE_SSSE3)
using acc_vec_t = __m128i;
using bias_vec_t = __m128i;
using weight_vec_t = __m128i;
using in_vec_t = __m128i;
#define vec_zero _mm_setzero_si128()
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
#define vec_hadd Simd::m128_hadd
#define vec_haddx4 Simd::m128_haddx4
#elif defined (USE_NEON)
using acc_vec_t = int32x4_t;
using bias_vec_t = int32x4_t;
using weight_vec_t = int8x8_t;
using in_vec_t = int8x8_t;
#define vec_zero {0}
#define vec_add_dpbusd_32x2 Simd::neon_m128_add_dpbusd_epi32x2
#define vec_hadd Simd::neon_m128_hadd
#define vec_haddx4 Simd::neon_m128_haddx4
#endif
#if defined (USE_SSSE3) || defined (USE_NEON)
const in_vec_t* invec = reinterpret_cast<const in_vec_t*>(input);
// Perform accumulation to registers for each big block
for (IndexType bigBlock = 0; bigBlock < NumBigBlocks; ++bigBlock)
{
acc_vec_t acc[NumOutputRegs] = { vec_zero };
// Each big block has NumOutputRegs small blocks in each "row", one per register.
// We process two small blocks at a time to save on one addition without VNNI.
for (IndexType smallBlock = 0; smallBlock < NumSmallBlocksPerOutput; smallBlock += 2)
{
const weight_vec_t* weightvec =
reinterpret_cast<const weight_vec_t*>(
weights
+ bigBlock * BigBlockSize
+ smallBlock * SmallBlockSize * NumOutputRegs);
const in_vec_t in0 = invec[smallBlock + 0];
const in_vec_t in1 = invec[smallBlock + 1];
for (IndexType k = 0; k < NumOutputRegs; ++k)
vec_add_dpbusd_32x2(acc[k], in0, weightvec[k], in1, weightvec[k + NumOutputRegs]);
}
// Horizontally add all accumulators.
if constexpr (NumOutputRegs % 4 == 0)
{
bias_vec_t* outputvec = reinterpret_cast<bias_vec_t*>(output);
const bias_vec_t* biasvec = reinterpret_cast<const bias_vec_t*>(biases);
for (IndexType k = 0; k < NumOutputRegs; k += 4)
{
const IndexType idx = (bigBlock * NumOutputRegs + k) / 4;
outputvec[idx] = vec_haddx4(acc[k+0], acc[k+1], acc[k+2], acc[k+3], biasvec[idx]);
}
}
else
{
for (IndexType k = 0; k < NumOutputRegs; ++k)
{
const IndexType idx = (bigBlock * NumOutputRegs + k);
output[idx] = vec_hadd(acc[k], biases[idx]);
}
}
}
# undef vec_zero
# undef vec_add_dpbusd_32x2
# undef vec_hadd
# undef vec_haddx4
static constexpr IndexType get_weight_index(IndexType i) {
#if defined(USE_SSSE3)
return get_weight_index_scrambled(i);
#else
// Use old implementation for the other architectures.
affine_transform_non_ssse3<
InputDimensions,
PaddedInputDimensions,
OutputDimensions>(output, weights, biases, input);
#endif
return output;
}
private:
using BiasType = OutputType;
using WeightType = std::int8_t;
alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
};
template <IndexType InDims, IndexType OutDims>
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) < 2*64)>> {
public:
// Input/output type
// Input/output type
using InputType = std::uint8_t;
using OutputType = std::int32_t;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = InDims;
static constexpr IndexType OutputDimensions = OutDims;
static constexpr IndexType PaddedInputDimensions =
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
static constexpr IndexType PaddedOutputDimensions =
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
using OutputBuffer = OutputType[PaddedOutputDimensions];
static_assert(PaddedInputDimensions < 128, "Something went wrong. This specialization should not have been chosen.");
#if defined (USE_SSSE3)
static constexpr const IndexType OutputSimdWidth = SimdWidth / 4;
static constexpr const IndexType InputSimdWidth = SimdWidth;
#endif
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
std::uint32_t hashValue = 0xCC03DAE4u;
hashValue += OutputDimensions;
hashValue ^= prevHash >> 1;
hashValue ^= prevHash << 31;
return hashValue;
}
static IndexType get_weight_index_scrambled(IndexType i)
{
return
(i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 +
i / PaddedInputDimensions * 4 +
i % 4;
}
static IndexType get_weight_index(IndexType i)
{
#if defined (USE_SSSE3)
return get_weight_index_scrambled(i);
#else
return i;
return i;
#endif
}
// Read network parameters
bool read_parameters(std::istream& stream) {
for (std::size_t i = 0; i < OutputDimensions; ++i)
biases[i] = read_little_endian<BiasType>(stream);
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
read_little_endian<BiasType>(stream, biases, OutputDimensions);
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
return !stream.fail();
return !stream.fail();
}
// Write network parameters
bool write_parameters(std::ostream& stream) const {
for (std::size_t i = 0; i < OutputDimensions; ++i)
write_little_endian<BiasType>(stream, biases[i]);
write_little_endian<BiasType>(stream, biases, OutputDimensions);
for (std::size_t i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
return !stream.fail();
return !stream.fail();
}
// Forward propagation
const OutputType* propagate(
const InputType* input, OutputType* output) const {
void propagate(const InputType* input, OutputType* output) const {
#if defined (USE_AVX2)
using vec_t = __m256i;
#define vec_setzero _mm256_setzero_si256
#define vec_set_32 _mm256_set1_epi32
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
#define vec_add_dpbusd_32x4 Simd::m256_add_dpbusd_epi32x4
#define vec_hadd Simd::m256_hadd
#define vec_haddx4 Simd::m256_haddx4
#elif defined (USE_SSSE3)
using vec_t = __m128i;
#define vec_setzero _mm_setzero_si128
#define vec_set_32 _mm_set1_epi32
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
#define vec_add_dpbusd_32x4 Simd::m128_add_dpbusd_epi32x4
#define vec_hadd Simd::m128_hadd
#define vec_haddx4 Simd::m128_haddx4
#endif
#if defined(USE_SSSE3)
#if defined (USE_SSSE3)
const auto inputVector = reinterpret_cast<const vec_t*>(input);
static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1);
if constexpr (OutputDimensions % OutputSimdWidth == 0)
{
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
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)
acc[k] = biasvec[k];
for (IndexType i = 0; i < NumChunks; i += 2)
if constexpr (OutputDimensions > 1)
{
const vec_t in0 = vec_set_32(input32[i + 0]);
const vec_t in1 = vec_set_32(input32[i + 1]);
const auto col0 = reinterpret_cast<const vec_t*>(&weights[(i + 0) * OutputDimensions * 4]);
const auto col1 = reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
for (IndexType k = 0; k < NumRegs; ++k)
vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]);
#if defined(USE_AVX512)
using vec_t = __m512i;
#define vec_setzero _mm512_setzero_si512
#define vec_set_32 _mm512_set1_epi32
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
#define vec_hadd Simd::m512_hadd
#elif defined(USE_AVX2)
using vec_t = __m256i;
#define vec_setzero _mm256_setzero_si256
#define vec_set_32 _mm256_set1_epi32
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
#define vec_hadd Simd::m256_hadd
#elif defined(USE_SSSE3)
using vec_t = __m128i;
#define vec_setzero _mm_setzero_si128
#define vec_set_32 _mm_set1_epi32
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
#define vec_hadd Simd::m128_hadd
#endif
static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);
static_assert(OutputDimensions % OutputSimdWidth == 0);
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
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)
acc[k] = biasvec[k];
for (IndexType i = 0; i < NumChunks; ++i)
{
const vec_t in0 = vec_set_32(input32[i]);
const auto col0 =
reinterpret_cast<const vec_t*>(&weights[i * OutputDimensions * 4]);
for (IndexType k = 0; k < NumRegs; ++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)
outptr[k] = acc[k];
#undef vec_setzero
#undef vec_set_32
#undef vec_add_dpbusd_32
#undef vec_hadd
}
vec_t* outptr = reinterpret_cast<vec_t*>(output);
for (IndexType k = 0; k < NumRegs; ++k)
outptr[k] = acc[k];
}
else if constexpr (OutputDimensions == 1)
{
constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth;
vec_t sum0 = vec_setzero();
const auto row0 = reinterpret_cast<const vec_t*>(&weights[0]);
for (int j = 0; j < (int)NumChunks; ++j)
else if constexpr (OutputDimensions == 1)
{
const vec_t in = inputVector[j];
vec_add_dpbusd_32(sum0, in, row0[j]);
}
output[0] = vec_hadd(sum0, biases[0]);
}
# undef vec_setzero
# undef vec_set_32
# undef vec_add_dpbusd_32
# undef vec_add_dpbusd_32x2
# undef vec_add_dpbusd_32x4
# undef vec_hadd
# undef vec_haddx4
// We cannot use AVX512 for the last layer because there are only 32 inputs
// and the buffer is not padded to 64 elements.
#if defined(USE_AVX2)
using vec_t = __m256i;
#define vec_setzero _mm256_setzero_si256
#define vec_set_32 _mm256_set1_epi32
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
#define vec_hadd Simd::m256_hadd
#elif defined(USE_SSSE3)
using vec_t = __m128i;
#define vec_setzero _mm_setzero_si128
#define vec_set_32 _mm_set1_epi32
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
#define vec_hadd Simd::m128_hadd
#endif
const auto inputVector = reinterpret_cast<const vec_t*>(input);
static constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(InputType);
static_assert(PaddedInputDimensions % InputSimdWidth == 0);
constexpr IndexType NumChunks = PaddedInputDimensions / InputSimdWidth;
vec_t sum0 = vec_setzero();
const auto row0 = reinterpret_cast<const vec_t*>(&weights[0]);
for (int j = 0; j < int(NumChunks); ++j)
{
const vec_t in = inputVector[j];
vec_add_dpbusd_32(sum0, in, row0[j]);
}
output[0] = vec_hadd(sum0, biases[0]);
#undef vec_setzero
#undef vec_set_32
#undef vec_add_dpbusd_32
#undef vec_hadd
}
#else
// Use old implementation for the other architectures.
affine_transform_non_ssse3<
InputDimensions,
PaddedInputDimensions,
OutputDimensions>(output, weights, biases, input);
// Use old implementation for the other architectures.
affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
output, weights, biases, input);
#endif
return output;
}
private:
using BiasType = OutputType;
using BiasType = OutputType;
using WeightType = std::int8_t;
alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
};
};
} // namespace Stockfish::Eval::NNUE::Layers
#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED

View file

@ -0,0 +1,278 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Definition of layer AffineTransformSparseInput of NNUE evaluation function
#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
#include <algorithm>
#include <array>
#include <cstdint>
#include <iostream>
#include "../../bitboard.h"
#include "../nnue_common.h"
#include "affine_transform.h"
#include "simd.h"
/*
This file contains the definition for a fully connected layer (aka affine transform) with block sparse input.
*/
namespace Stockfish::Eval::NNUE::Layers {
#if (USE_SSSE3 | (USE_NEON >= 8))
alignas(CacheLineSize) static inline const
std::array<std::array<std::uint16_t, 8>, 256> lookup_indices = []() {
std::array<std::array<std::uint16_t, 8>, 256> v{};
for (unsigned i = 0; i < 256; ++i)
{
std::uint64_t j = i, k = 0;
while (j)
v[i][k++] = pop_lsb(j);
}
return v;
}();
// Find indices of nonzero numbers in an int32_t array
template<const IndexType InputDimensions>
void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) {
#if defined(USE_SSSE3)
#if defined(USE_AVX512)
using vec_t = __m512i;
#define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
#elif defined(USE_AVX2)
using vec_t = __m256i;
#if defined(USE_VNNI) && !defined(USE_AVXVNNI)
#define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256())
#else
#define vec_nnz(a) \
_mm256_movemask_ps( \
_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
#endif
#elif defined(USE_SSSE3)
using vec_t = __m128i;
#define vec_nnz(a) \
_mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128())))
#endif
using vec128_t = __m128i;
#define vec128_zero _mm_setzero_si128()
#define vec128_set_16(a) _mm_set1_epi16(a)
#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)
#elif defined(USE_NEON)
using vec_t = uint32x4_t;
static const std::uint32_t Mask[4] = {1, 2, 4, 8};
#define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask)))
using vec128_t = uint16x8_t;
#define vec128_zero vdupq_n_u16(0)
#define vec128_set_16(a) vdupq_n_u16(a)
#define vec128_load(a) vld1q_u16(reinterpret_cast<const std::uint16_t*>(a))
#define vec128_storeu(a, b) vst1q_u16(reinterpret_cast<std::uint16_t*>(a), b)
#define vec128_add(a, b) vaddq_u16(a, b)
#endif
constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t);
// Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8)
constexpr IndexType ChunkSize = std::max<IndexType>(InputSimdWidth, 8);
constexpr IndexType NumChunks = InputDimensions / ChunkSize;
constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth;
constexpr IndexType OutputsPerChunk = ChunkSize / 8;
const auto inputVector = reinterpret_cast<const vec_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_t inputChunk = inputVector[i * InputsPerChunk + j];
nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth);
}
for (IndexType j = 0; j < OutputsPerChunk; ++j)
{
const auto lookup = (nnz >> (j * 8)) & 0xFF;
const auto offsets =
vec128_load(reinterpret_cast<const vec128_t*>(&lookup_indices[lookup]));
vec128_storeu(reinterpret_cast<vec128_t*>(out + count), vec128_add(base, offsets));
count += popcount(lookup);
base = vec128_add(base, increment);
}
}
count_out = count;
}
#undef vec_nnz
#undef vec128_zero
#undef vec128_set_16
#undef vec128_load
#undef vec128_storeu
#undef vec128_add
#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;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = InDims;
static constexpr IndexType OutputDimensions = OutDims;
static_assert(OutputDimensions % 16 == 0,
"Only implemented for OutputDimensions divisible by 16.");
static constexpr IndexType PaddedInputDimensions =
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
static constexpr IndexType PaddedOutputDimensions =
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
#if (USE_SSSE3 | (USE_NEON >= 8))
static constexpr IndexType ChunkSize = 4;
#else
static constexpr IndexType ChunkSize = 1;
#endif
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;
hashValue += OutputDimensions;
hashValue ^= prevHash >> 1;
hashValue ^= prevHash << 31;
return hashValue;
}
static constexpr IndexType get_weight_index_scrambled(IndexType i) {
return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize
+ i / PaddedInputDimensions * ChunkSize + i % ChunkSize;
}
static constexpr IndexType get_weight_index(IndexType i) {
#if (USE_SSSE3 | (USE_NEON >= 8))
return get_weight_index_scrambled(i);
#else
return i;
#endif
}
// Read network parameters
bool read_parameters(std::istream& stream) {
read_little_endian<BiasType>(stream, biases, OutputDimensions);
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
weights[get_weight_index(i)] = read_little_endian<WeightType>(stream);
return !stream.fail();
}
// Write network parameters
bool write_parameters(std::ostream& stream) const {
write_little_endian<BiasType>(stream, biases, OutputDimensions);
for (IndexType i = 0; i < OutputDimensions * PaddedInputDimensions; ++i)
write_little_endian<WeightType>(stream, weights[get_weight_index(i)]);
return !stream.fail();
}
// Forward propagation
void propagate(const InputType* input, OutputType* output) const {
#if (USE_SSSE3 | (USE_NEON >= 8))
#if defined(USE_AVX512)
using invec_t = __m512i;
using outvec_t = __m512i;
#define vec_set_32 _mm512_set1_epi32
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
#elif defined(USE_AVX2)
using invec_t = __m256i;
using outvec_t = __m256i;
#define vec_set_32 _mm256_set1_epi32
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
#elif defined(USE_SSSE3)
using invec_t = __m128i;
using outvec_t = __m128i;
#define vec_set_32 _mm_set1_epi32
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
#elif defined(USE_NEON_DOTPROD)
using invec_t = int8x16_t;
using outvec_t = int32x4_t;
#define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
#define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32
#elif defined(USE_NEON)
using invec_t = int8x16_t;
using outvec_t = int32x4_t;
#define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
#define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32
#endif
static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType);
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
std::uint16_t nnz[NumChunks];
IndexType count;
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 < NumRegs; ++k)
acc[k] = biasvec[k];
for (IndexType j = 0; j < count; ++j)
{
const auto i = nnz[j];
const invec_t in = vec_set_32(input32[i]);
const auto col =
reinterpret_cast<const invec_t*>(&weights[i * OutputDimensions * ChunkSize]);
for (IndexType k = 0; k < NumRegs; ++k)
vec_add_dpbusd_32(acc[k], in, col[k]);
}
outvec_t* outptr = reinterpret_cast<outvec_t*>(output);
for (IndexType k = 0; k < NumRegs; ++k)
outptr[k] = acc[k];
#undef vec_set_32
#undef vec_add_dpbusd_32
#else
// Use dense implementation for the other architectures.
affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
output, weights, biases, input);
#endif
}
private:
using BiasType = OutputType;
using WeightType = std::int8_t;
alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
};
} // namespace Stockfish::Eval::NNUE::Layers
#endif // #ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,168 +21,148 @@
#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
#include <algorithm>
#include <cstdint>
#include <iosfwd>
#include "../nnue_common.h"
namespace Stockfish::Eval::NNUE::Layers {
// Clipped ReLU
template <IndexType InDims>
class ClippedReLU {
// Clipped ReLU
template<IndexType InDims>
class ClippedReLU {
public:
// Input/output type
using InputType = std::int32_t;
using InputType = std::int32_t;
using OutputType = std::uint8_t;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = InDims;
static constexpr IndexType InputDimensions = InDims;
static constexpr IndexType OutputDimensions = InputDimensions;
static constexpr IndexType PaddedOutputDimensions =
ceil_to_multiple<IndexType>(OutputDimensions, 32);
ceil_to_multiple<IndexType>(OutputDimensions, 32);
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;
hashValue += prevHash;
return hashValue;
std::uint32_t hashValue = 0x538D24C7u;
hashValue += prevHash;
return hashValue;
}
// Read network parameters
bool read_parameters(std::istream&) {
return true;
}
bool read_parameters(std::istream&) { return true; }
// Write network parameters
bool write_parameters(std::ostream&) const {
return true;
}
bool write_parameters(std::ostream&) const { return true; }
// Forward propagation
const OutputType* propagate(
const InputType* input, OutputType* output) const {
void propagate(const InputType* input, OutputType* output) const {
#if defined(USE_AVX2)
if constexpr (InputDimensions % SimdWidth == 0) {
#if defined(USE_AVX2)
if constexpr (InputDimensions % SimdWidth == 0)
{
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m256i Zero = _mm256_setzero_si256();
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_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 0]),
_mm256_load_si256(&in[i * 4 + 1])),
WeightScaleBits);
const __m256i words1 =
_mm256_srai_epi16(_mm256_packs_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_max_epi8(_mm256_packs_epi16(words0, words1), Zero), Offsets));
}
}
else
{
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
const __m128i Zero = _mm_setzero_si128();
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_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
WeightScaleBits);
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero));
}
}
constexpr IndexType Start = InputDimensions % SimdWidth == 0
? InputDimensions / SimdWidth * SimdWidth
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
#elif defined(USE_SSE2)
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m256i Zero = _mm256_setzero_si256();
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_srai_epi16(_mm256_packs_epi32(
_mm256_load_si256(&in[i * 4 + 0]),
_mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits);
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_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_max_epi8(
_mm256_packs_epi16(words0, words1), Zero), Offsets));
}
} else {
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
#ifdef USE_SSE41
const __m128i Zero = _mm_setzero_si128();
const auto in = reinterpret_cast<const __m128i*>(input);
#else
const __m128i k0x80s = _mm_set1_epi8(-128);
#endif
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_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 0]),
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 2]),
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero));
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m128i words0 = _mm_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
WeightScaleBits);
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i],
#ifdef USE_SSE41
_mm_max_epi8(packedbytes, Zero)
#else
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
#endif
);
}
}
constexpr IndexType Start =
InputDimensions % SimdWidth == 0
? InputDimensions / SimdWidth * SimdWidth
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
constexpr IndexType Start = NumChunks * SimdWidth;
#elif defined(USE_SSE2)
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
#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);
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);
out[i] = vmax_s8(vqmovn_s16(shifted), Zero);
}
constexpr IndexType Start = NumChunks * (SimdWidth / 2);
#else
constexpr IndexType Start = 0;
#endif
#ifdef USE_SSE41
const __m128i Zero = _mm_setzero_si128();
#else
const __m128i k0x80s = _mm_set1_epi8(-128);
#endif
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_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 0]),
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 2]),
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i],
#ifdef USE_SSE41
_mm_max_epi8(packedbytes, Zero)
#else
_mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s)
#endif
);
}
constexpr IndexType Start = NumChunks * SimdWidth;
#elif defined(USE_MMX)
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m64 k0x80s = _mm_set1_pi8(-128);
const auto in = reinterpret_cast<const __m64*>(input);
const auto out = reinterpret_cast<__m64*>(output);
for (IndexType i = 0; i < NumChunks; ++i) {
const __m64 words0 = _mm_srai_pi16(
_mm_packs_pi32(in[i * 4 + 0], in[i * 4 + 1]),
WeightScaleBits);
const __m64 words1 = _mm_srai_pi16(
_mm_packs_pi32(in[i * 4 + 2], in[i * 4 + 3]),
WeightScaleBits);
const __m64 packedbytes = _mm_packs_pi16(words0, words1);
out[i] = _mm_subs_pi8(_mm_adds_pi8(packedbytes, k0x80s), k0x80s);
}
_mm_empty();
constexpr IndexType Start = NumChunks * SimdWidth;
#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);
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);
out[i] = vmax_s8(vqmovn_s16(shifted), Zero);
}
constexpr IndexType Start = NumChunks * (SimdWidth / 2);
#else
constexpr IndexType Start = 0;
#endif
for (IndexType i = Start; i < InputDimensions; ++i) {
output[i] = static_cast<OutputType>(
std::max(0, std::min(127, input[i] >> WeightScaleBits)));
}
// Affine transform layers expect that there is at least
// ceil_to_multiple(OutputDimensions, 32) initialized values.
// We cannot do this in the affine transform because it requires
// preallocating space here.
for (IndexType i = OutputDimensions; i < PaddedOutputDimensions; ++i) {
output[i] = 0;
}
return output;
for (IndexType i = Start; i < InputDimensions; ++i)
{
output[i] = static_cast<OutputType>(std::clamp(input[i] >> WeightScaleBits, 0, 127));
}
}
};
};
} // namespace Stockfish::Eval::NNUE::Layers
#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
#endif // NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED

167
src/nnue/layers/simd.h Normal file
View file

@ -0,0 +1,167 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef STOCKFISH_SIMD_H_INCLUDED
#define STOCKFISH_SIMD_H_INCLUDED
#if defined(USE_AVX2)
#include <immintrin.h>
#elif defined(USE_SSE41)
#include <smmintrin.h>
#elif defined(USE_SSSE3)
#include <tmmintrin.h>
#elif defined(USE_SSE2)
#include <emmintrin.h>
#elif defined(USE_NEON)
#include <arm_neon.h>
#endif
namespace Stockfish::Simd {
#if defined(USE_AVX512)
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
return _mm512_reduce_add_epi32(sum) + bias;
}
/*
Parameters:
sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]]
sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]]
sum2 = [zmm2.i128[0], zmm2.i128[1], zmm2.i128[2], zmm2.i128[3]]
sum3 = [zmm3.i128[0], zmm3.i128[1], zmm3.i128[2], zmm3.i128[3]]
Returns:
ret = [
reduce_add_epi32(zmm0.i128[0]), reduce_add_epi32(zmm1.i128[0]), reduce_add_epi32(zmm2.i128[0]), reduce_add_epi32(zmm3.i128[0]),
reduce_add_epi32(zmm0.i128[1]), reduce_add_epi32(zmm1.i128[1]), reduce_add_epi32(zmm2.i128[1]), reduce_add_epi32(zmm3.i128[1]),
reduce_add_epi32(zmm0.i128[2]), reduce_add_epi32(zmm1.i128[2]), reduce_add_epi32(zmm2.i128[2]), reduce_add_epi32(zmm3.i128[2]),
reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3])
]
*/
[[maybe_unused]] static __m512i
m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
__m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1);
__m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1);
__m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3);
__m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3);
__m512i sum01 = _mm512_add_epi32(sum01a, sum01b);
__m512i sum23 = _mm512_add_epi32(sum23a, sum23b);
__m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23);
__m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23);
return _mm512_add_epi32(sum0123a, sum0123b);
}
[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) {
#if defined(USE_VNNI)
acc = _mm512_dpbusd_epi32(acc, a, b);
#else
__m512i product0 = _mm512_maddubs_epi16(a, b);
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
acc = _mm512_add_epi32(acc, product0);
#endif
}
#endif
#if defined(USE_AVX2)
[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
__m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
return _mm_cvtsi128_si32(sum128) + bias;
}
[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) {
#if defined(USE_VNNI)
acc = _mm256_dpbusd_epi32(acc, a, b);
#else
__m256i product0 = _mm256_maddubs_epi16(a, b);
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
acc = _mm256_add_epi32(acc, product0);
#endif
}
#endif
#if defined(USE_SSSE3)
[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
return _mm_cvtsi128_si32(sum) + bias;
}
[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
__m128i product0 = _mm_maddubs_epi16(a, b);
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
acc = _mm_add_epi32(acc, product0);
}
#endif
#if defined(USE_NEON_DOTPROD)
[[maybe_unused]] static void
dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
acc = vdotq_s32(acc, a, b);
}
#endif
#if defined(USE_NEON)
[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
#if USE_NEON >= 8
return vaddvq_s32(s);
#else
return s[0] + s[1] + s[2] + s[3];
#endif
}
[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {
return neon_m128_reduce_add_epi32(sum) + bias;
}
#endif
#if USE_NEON >= 8
[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b));
int16x8_t product1 = vmull_high_s8(a, b);
int16x8_t sum = vpaddq_s16(product0, product1);
acc = vpadalq_s16(acc, sum);
}
#endif
}
#endif // STOCKFISH_SIMD_H_INCLUDED

View file

@ -0,0 +1,103 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Definition of layer ClippedReLU of NNUE evaluation function
#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
#define NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
#include <algorithm>
#include <cstdint>
#include <iosfwd>
#include "../nnue_common.h"
namespace Stockfish::Eval::NNUE::Layers {
// Clipped ReLU
template<IndexType InDims>
class SqrClippedReLU {
public:
// Input/output type
using InputType = std::int32_t;
using OutputType = std::uint8_t;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = InDims;
static constexpr IndexType OutputDimensions = InputDimensions;
static constexpr IndexType PaddedOutputDimensions =
ceil_to_multiple<IndexType>(OutputDimensions, 32);
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;
hashValue += prevHash;
return hashValue;
}
// Read network parameters
bool read_parameters(std::istream&) { return true; }
// Write network parameters
bool write_parameters(std::ostream&) const { return true; }
// Forward propagation
void propagate(const InputType* input, OutputType* output) const {
#if 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);
for (IndexType i = 0; i < NumChunks; ++i)
{
__m128i words0 =
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1]));
__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);
_mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));
}
constexpr IndexType Start = NumChunks * 16;
#else
constexpr IndexType Start = 0;
#endif
for (IndexType i = Start; i < InputDimensions; ++i)
{
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)));
}
}
};
} // namespace Stockfish::Eval::NNUE::Layers
#endif // NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED

448
src/nnue/network.cpp Normal file
View file

@ -0,0 +1,448 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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 "network.h"
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <optional>
#include <type_traits>
#include <vector>
#include "../evaluate.h"
#include "../incbin/incbin.h"
#include "../misc.h"
#include "../position.h"
#include "../types.h"
#include "nnue_architecture.h"
#include "nnue_common.h"
#include "nnue_misc.h"
namespace {
// Macro to embed the default efficiently updatable neural network (NNUE) file
// data in the engine binary (using incbin.h, by Dale Weiler).
// This macro invocation will declare the following three variables
// const unsigned char gEmbeddedNNUEData[]; // a pointer to the embedded data
// 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);
#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;
#endif
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 Detail {
// Initialize the evaluation function parameters
template<typename T>
void initialize(AlignedPtr<T>& pointer) {
pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
std::memset(pointer.get(), 0, sizeof(T));
}
template<typename T>
void initialize(LargePagePtr<T>& pointer) {
static_assert(alignof(T) <= 4096,
"aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
std::memset(pointer.get(), 0, sizeof(T));
}
// Read evaluation function parameters
template<typename T>
bool read_parameters(std::istream& stream, T& reference) {
std::uint32_t header;
header = read_little_endian<std::uint32_t>(stream);
if (!stream || header != T::get_hash_value())
return false;
return reference.read_parameters(stream);
}
// Write evaluation function parameters
template<typename T>
bool write_parameters(std::ostream& stream, const T& reference) {
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
return reference.write_parameters(stream);
}
} // namespace Detail
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::load(const std::string& rootDirectory, std::string evalfilePath) {
#if defined(DEFAULT_NNUE_DIRECTORY)
std::vector<std::string> dirs = {"<internal>", "", rootDirectory,
stringify(DEFAULT_NNUE_DIRECTORY)};
#else
std::vector<std::string> dirs = {"<internal>", "", rootDirectory};
#endif
if (evalfilePath.empty())
evalfilePath = evalFile.defaultName;
for (const auto& directory : dirs)
{
if (evalFile.current != evalfilePath)
{
if (directory != "<internal>")
{
load_user_net(directory, evalfilePath);
}
if (directory == "<internal>" && evalfilePath == evalFile.defaultName)
{
load_internal();
}
}
}
}
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
{
if (evalFile.current != 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;
}
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";
sync_cout << msg << sync_endl;
return saved;
}
template<typename Arch, typename Transformer>
Value Network<Arch, Transformer>::evaluate(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache,
bool adjusted,
int* complexity) const {
// We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly.
constexpr uint64_t alignment = CacheLineSize;
constexpr int delta = 24;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType
transformedFeaturesUnaligned[FeatureTransformer<FTDimensions, nullptr>::BufferSize
+ alignment / sizeof(TransformedFeatureType)];
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
#else
alignas(alignment) TransformedFeatureType
transformedFeatures[FeatureTransformer<FTDimensions, nullptr>::BufferSize];
#endif
ASSERT_ALIGNED(transformedFeatures, alignment);
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
const auto psqt = featureTransformer->transform(pos, cache, transformedFeatures, bucket);
const auto positional = network[bucket]->propagate(transformedFeatures);
if (complexity)
*complexity = std::abs(psqt - positional) / OutputScale;
// Give more value to positional evaluation when adjusted flag is set
if (adjusted)
return static_cast<Value>(((1024 - delta) * psqt + (1024 + delta) * positional)
/ (1024 * OutputScale));
else
return static_cast<Value>((psqt + positional) / OutputScale);
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::verify(std::string evalfilePath) const {
if (evalfilePath.empty())
evalfilePath = evalFile.defaultName;
if (evalFile.current != evalfilePath)
{
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 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: "
"https://tests.stockfishchess.org/api/nn/"
+ evalFile.defaultName;
std::string msg5 = "The engine will be terminated now.";
sync_cout << "info string ERROR: " << msg1 << sync_endl;
sync_cout << "info string ERROR: " << msg2 << sync_endl;
sync_cout << "info string ERROR: " << msg3 << sync_endl;
sync_cout << "info string ERROR: " << msg4 << sync_endl;
sync_cout << "info string ERROR: " << msg5 << sync_endl;
exit(EXIT_FAILURE);
}
size_t size = sizeof(*featureTransformer) + sizeof(*network) * LayerStacks;
sync_cout << "info string NNUE evaluation using " << evalfilePath << " ("
<< size / (1024 * 1024) << "MiB, (" << featureTransformer->InputDimensions << ", "
<< network[0]->TransformedFeatureDimensions << ", " << network[0]->FC_0_OUTPUTS
<< ", " << network[0]->FC_1_OUTPUTS << ", 1))" << sync_endl;
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::hint_common_access(
const Position& pos, AccumulatorCaches::Cache<FTDimensions>* cache) const {
featureTransformer->hint_common_access(pos, cache);
}
template<typename Arch, typename Transformer>
NnueEvalTrace
Network<Arch, Transformer>::trace_evaluate(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache) const {
// We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly.
constexpr uint64_t alignment = CacheLineSize;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType
transformedFeaturesUnaligned[FeatureTransformer<FTDimensions, nullptr>::BufferSize
+ alignment / sizeof(TransformedFeatureType)];
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
#else
alignas(alignment) TransformedFeatureType
transformedFeatures[FeatureTransformer<FTDimensions, nullptr>::BufferSize];
#endif
ASSERT_ALIGNED(transformedFeatures, alignment);
NnueEvalTrace t{};
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)
{
const auto materialist =
featureTransformer->transform(pos, cache, transformedFeatures, bucket);
const auto positional = network[bucket]->propagate(transformedFeatures);
t.psqt[bucket] = static_cast<Value>(materialist / OutputScale);
t.positional[bucket] = static_cast<Value>(positional / OutputScale);
}
return t;
}
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);
auto description = load(stream);
if (description.has_value())
{
evalFile.current = evalfilePath;
evalFile.netDescription = description.value();
}
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::load_internal() {
// C++ way to prepare a buffer for a memory stream
class MemoryBuffer: public std::basic_streambuf<char> {
public:
MemoryBuffer(char* p, size_t n) {
setg(p, p, p + n);
setp(p, p + n);
}
};
const auto embedded = get_embedded(embeddedType);
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(embedded.data)),
size_t(embedded.size));
std::istream stream(&buffer);
auto description = load(stream);
if (description.has_value())
{
evalFile.current = evalFile.defaultName;
evalFile.netDescription = description.value();
}
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::initialize() {
Detail::initialize(featureTransformer);
for (std::size_t i = 0; i < LayerStacks; ++i)
Detail::initialize(network[i]);
}
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;
return write_parameters(stream, netDescription);
}
template<typename Arch, typename Transformer>
std::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream) {
initialize();
std::string description;
return read_parameters(stream, description) ? std::make_optional(description) : std::nullopt;
}
// 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;
version = read_little_endian<std::uint32_t>(stream);
*hashValue = read_little_endian<std::uint32_t>(stream);
size = read_little_endian<std::uint32_t>(stream);
if (!stream || version != Version)
return false;
desc->resize(size);
stream.read(&(*desc)[0], size);
return !stream.fail();
}
// 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()));
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) const {
std::uint32_t 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)
{
if (!Detail::read_parameters(stream, *(network[i])))
return false;
}
return stream && stream.peek() == std::ios::traits_type::eof();
}
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
const std::string& netDescription) const {
if (!write_header(stream, Network::hash, netDescription))
return false;
if (!Detail::write_parameters(stream, *featureTransformer))
return false;
for (std::size_t i = 0; i < LayerStacks; ++i)
{
if (!Detail::write_parameters(stream, *(network[i])))
return false;
}
return bool(stream);
}
// Explicit template instantiation
template class Network<
NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>,
FeatureTransformer<TransformedFeatureDimensionsBig, &StateInfo::accumulatorBig>>;
template class Network<
NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>,
FeatureTransformer<TransformedFeatureDimensionsSmall, &StateInfo::accumulatorSmall>>;
} // namespace Stockfish::Eval::NNUE

126
src/nnue/network.h Normal file
View file

@ -0,0 +1,126 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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 NETWORK_H_INCLUDED
#define NETWORK_H_INCLUDED
#include <cstdint>
#include <iostream>
#include <optional>
#include <string>
#include <utility>
#include "../misc.h"
#include "../position.h"
#include "../types.h"
#include "nnue_architecture.h"
#include "nnue_feature_transformer.h"
#include "nnue_misc.h"
#include "nnue_accumulator.h"
namespace Stockfish::Eval::NNUE {
enum class EmbeddedNNUEType {
BIG,
SMALL,
};
template<typename Arch, typename Transformer>
class Network {
static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions;
public:
Network(EvalFile file, EmbeddedNNUEType type) :
evalFile(file),
embeddedType(type) {}
void load(const std::string& rootDirectory, std::string evalfilePath);
bool save(const std::optional<std::string>& filename) const;
Value evaluate(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache,
bool adjusted = false,
int* complexity = nullptr) const;
void hint_common_access(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache) const;
void verify(std::string evalfilePath) const;
NnueEvalTrace trace_evaluate(const Position& pos,
AccumulatorCaches::Cache<FTDimensions>* cache) const;
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;
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_parameters(std::istream&, std::string&) const;
bool write_parameters(std::ostream&, const std::string&) const;
// Input feature converter
LargePagePtr<Transformer> featureTransformer;
// Evaluation function
AlignedPtr<Arch> network[LayerStacks];
EvalFile evalFile;
EmbeddedNNUEType embeddedType;
// Hash value of evaluation function structure
static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value();
template<IndexType Size>
friend struct AccumulatorCaches::Cache;
};
// Definitions of the network types
using SmallFeatureTransformer =
FeatureTransformer<TransformedFeatureDimensionsSmall, &StateInfo::accumulatorSmall>;
using SmallNetworkArchitecture =
NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>;
using BigFeatureTransformer =
FeatureTransformer<TransformedFeatureDimensionsBig, &StateInfo::accumulatorBig>;
using BigNetworkArchitecture = NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>;
using NetworkBig = Network<BigNetworkArchitecture, BigFeatureTransformer>;
using NetworkSmall = Network<SmallNetworkArchitecture, SmallFeatureTransformer>;
struct Networks {
Networks(NetworkBig&& nB, NetworkSmall&& nS) :
big(std::move(nB)),
small(std::move(nS)) {}
NetworkBig big;
NetworkSmall small;
};
} // namespace Stockfish
#endif

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,17 +21,85 @@
#ifndef NNUE_ACCUMULATOR_H_INCLUDED
#define NNUE_ACCUMULATOR_H_INCLUDED
#include <cstdint>
#include "nnue_architecture.h"
#include "nnue_common.h"
namespace Stockfish::Eval::NNUE {
// Class that holds the result of affine transformation of input features
struct alignas(CacheLineSize) Accumulator {
std::int16_t accumulation[2][TransformedFeatureDimensions];
std::int32_t psqtAccumulation[2][PSQTBuckets];
bool computed[2];
};
using BiasType = std::int16_t;
using PSQTWeightType = std::int32_t;
using IndexType = std::uint32_t;
// Class that holds the result of affine transformation of input features
template<IndexType Size>
struct alignas(CacheLineSize) Accumulator {
std::int16_t accumulation[COLOR_NB][Size];
std::int32_t psqtAccumulation[COLOR_NB][PSQTBuckets];
bool computed[COLOR_NB];
};
// AccumulatorCaches struct provides per-thread accumulator caches, where each
// cache contains multiple entries for each of the possible king squares.
// When the accumulator needs to be refreshed, the cached entry is used to more
// efficiently update the accumulator, instead of rebuilding it from scratch.
// 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<IndexType Size>
struct alignas(CacheLineSize) Cache {
struct alignas(CacheLineSize) Entry {
BiasType accumulation[Size];
PSQTWeightType psqtAccumulation[PSQTBuckets];
Bitboard byColorBB[COLOR_NB];
Bitboard byTypeBB[PIECE_TYPE_NB];
// To initialize a refresh entry, we set all its bitboards empty,
// so we put the biases in the accumulation, without any weights on top
void clear(const BiasType* biases) {
std::memcpy(accumulation, biases, sizeof(accumulation));
std::memset((uint8_t*) this + offsetof(Entry, psqtAccumulation), 0,
sizeof(Entry) - offsetof(Entry, psqtAccumulation));
}
};
template<typename Network>
void clear(const Network& network) {
for (auto& entries1D : entries)
for (auto& entry : entries1D)
entry.clear(network.featureTransformer->biases);
}
void clear(const BiasType* biases) {
for (auto& entry : entries)
entry.clear(biases);
}
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);
}
Cache<TransformedFeatureDimensionsBig> big;
Cache<TransformedFeatureDimensionsSmall> small;
};
} // namespace Stockfish::Eval::NNUE
#endif // NNUE_ACCUMULATOR_H_INCLUDED
#endif // NNUE_ACCUMULATOR_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,14 +21,16 @@
#ifndef NNUE_ARCHITECTURE_H_INCLUDED
#define NNUE_ARCHITECTURE_H_INCLUDED
#include "nnue_common.h"
#include <cstdint>
#include <cstring>
#include <iosfwd>
#include "features/half_ka_v2_hm.h"
#include "layers/affine_transform.h"
#include "layers/affine_transform_sparse_input.h"
#include "layers/clipped_relu.h"
#include "../misc.h"
#include "layers/sqr_clipped_relu.h"
#include "nnue_common.h"
namespace Stockfish::Eval::NNUE {
@ -36,96 +38,100 @@ namespace Stockfish::Eval::NNUE {
using FeatureSet = Features::HalfKAv2_hm;
// Number of input feature dimensions after conversion
constexpr IndexType TransformedFeatureDimensions = 1024;
constexpr IndexType TransformedFeatureDimensionsBig = 3072;
constexpr int L2Big = 15;
constexpr int L3Big = 32;
constexpr IndexType TransformedFeatureDimensionsSmall = 128;
constexpr int L2Small = 15;
constexpr int L3Small = 32;
constexpr IndexType PSQTBuckets = 8;
constexpr IndexType LayerStacks = 8;
struct Network
{
static constexpr int FC_0_OUTPUTS = 15;
static constexpr int FC_1_OUTPUTS = 32;
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;
Layers::AffineTransform<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
Layers::ClippedReLU<FC_0_OUTPUTS> ac_0;
Layers::AffineTransform<FC_0_OUTPUTS, 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 + 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;
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value() {
// input slice hash
std::uint32_t hashValue = 0xEC42E90Du;
hashValue ^= TransformedFeatureDimensions * 2;
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value() {
// input slice hash
std::uint32_t hashValue = 0xEC42E90Du;
hashValue ^= TransformedFeatureDimensions * 2;
hashValue = decltype(fc_0)::get_hash_value(hashValue);
hashValue = decltype(ac_0)::get_hash_value(hashValue);
hashValue = decltype(fc_1)::get_hash_value(hashValue);
hashValue = decltype(ac_1)::get_hash_value(hashValue);
hashValue = decltype(fc_2)::get_hash_value(hashValue);
hashValue = decltype(fc_0)::get_hash_value(hashValue);
hashValue = decltype(ac_0)::get_hash_value(hashValue);
hashValue = decltype(fc_1)::get_hash_value(hashValue);
hashValue = decltype(ac_1)::get_hash_value(hashValue);
hashValue = decltype(fc_2)::get_hash_value(hashValue);
return hashValue;
}
return hashValue;
}
// Read network parameters
bool read_parameters(std::istream& stream) {
if (!fc_0.read_parameters(stream)) return false;
if (!ac_0.read_parameters(stream)) return false;
if (!fc_1.read_parameters(stream)) return false;
if (!ac_1.read_parameters(stream)) return false;
if (!fc_2.read_parameters(stream)) return false;
return true;
}
// Read network parameters
bool read_parameters(std::istream& stream) {
return fc_0.read_parameters(stream) && ac_0.read_parameters(stream)
&& fc_1.read_parameters(stream) && ac_1.read_parameters(stream)
&& fc_2.read_parameters(stream);
}
// Read network parameters
bool write_parameters(std::ostream& stream) const {
if (!fc_0.write_parameters(stream)) return false;
if (!ac_0.write_parameters(stream)) return false;
if (!fc_1.write_parameters(stream)) return false;
if (!ac_1.write_parameters(stream)) return false;
if (!fc_2.write_parameters(stream)) return false;
return true;
}
// Write network parameters
bool write_parameters(std::ostream& stream) const {
return fc_0.write_parameters(stream) && ac_0.write_parameters(stream)
&& fc_1.write_parameters(stream) && ac_1.write_parameters(stream)
&& fc_2.write_parameters(stream);
}
std::int32_t propagate(const TransformedFeatureType* transformedFeatures)
{
constexpr uint64_t alignment = CacheLineSize;
std::int32_t propagate(const TransformedFeatureType* transformedFeatures) {
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;
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;
struct Buffer
{
alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out;
alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out;
alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out;
alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out;
alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out;
};
Buffer() { std::memset(this, 0, sizeof(*this)); }
};
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
char bufferRaw[sizeof(Buffer) + alignment];
char* bufferRawAligned = align_ptr_up<alignment>(&bufferRaw[0]);
Buffer& buffer = *(new (bufferRawAligned) Buffer);
#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;
#else
alignas(alignment) Buffer buffer;
alignas(CacheLineSize) static thread_local Buffer buffer;
#endif
fc_0.propagate(transformedFeatures, buffer.fc_0_out);
ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out);
fc_1.propagate(buffer.ac_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_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);
// buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in quantized form
// but we want 1.0 to be equal to 600*OutputScale
std::int32_t fwdOut = int(buffer.fc_0_out[FC_0_OUTPUTS]) * (600*OutputScale) / (127*(1<<WeightScaleBits));
std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
// buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) 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;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
buffer.~Buffer();
#endif
return outputValue;
}
return outputValue;
}
};
} // namespace Stockfish::Eval::NNUE
#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED
#endif // #ifndef NNUE_ARCHITECTURE_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,144 +21,264 @@
#ifndef NNUE_COMMON_H_INCLUDED
#define NNUE_COMMON_H_INCLUDED
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <type_traits>
#include "../misc.h" // for IsLittleEndian
#include "../misc.h"
#if defined(USE_AVX2)
#include <immintrin.h>
#include <immintrin.h>
#elif defined(USE_SSE41)
#include <smmintrin.h>
#include <smmintrin.h>
#elif defined(USE_SSSE3)
#include <tmmintrin.h>
#include <tmmintrin.h>
#elif defined(USE_SSE2)
#include <emmintrin.h>
#elif defined(USE_MMX)
#include <mmintrin.h>
#include <emmintrin.h>
#elif defined(USE_NEON)
#include <arm_neon.h>
#include <arm_neon.h>
#endif
namespace Stockfish::Eval::NNUE {
// Version of the evaluation file
constexpr std::uint32_t Version = 0x7AF32F20u;
// Version of the evaluation file
constexpr std::uint32_t Version = 0x7AF32F20u;
// Constant used in evaluation value calculation
constexpr int OutputScale = 16;
constexpr int WeightScaleBits = 6;
// Constant used in evaluation value calculation
constexpr int OutputScale = 16;
constexpr int WeightScaleBits = 6;
// Size of cache line (in bytes)
constexpr std::size_t CacheLineSize = 64;
// Size of cache line (in bytes)
constexpr std::size_t CacheLineSize = 64;
// SIMD width (in bytes)
#if defined(USE_AVX2)
constexpr std::size_t SimdWidth = 32;
constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128";
constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;
#elif defined(USE_SSE2)
constexpr std::size_t SimdWidth = 16;
// SIMD width (in bytes)
#if defined(USE_AVX2)
constexpr std::size_t SimdWidth = 32;
#elif defined(USE_MMX)
constexpr std::size_t SimdWidth = 8;
#elif defined(USE_SSE2)
constexpr std::size_t SimdWidth = 16;
#elif defined(USE_NEON)
constexpr std::size_t SimdWidth = 16;
#endif
#elif defined(USE_NEON)
constexpr std::size_t SimdWidth = 16;
#endif
constexpr std::size_t MaxSimdWidth = 32;
constexpr std::size_t MaxSimdWidth = 32;
// Type of input feature after conversion
using TransformedFeatureType = std::uint8_t;
using IndexType = std::uint32_t;
// Type of input feature after conversion
using TransformedFeatureType = std::uint8_t;
using IndexType = std::uint32_t;
// Round n up to be a multiple of base
template <typename IntType>
constexpr IntType ceil_to_multiple(IntType n, IntType base) {
return (n + base - 1) / base * base;
}
// Round n up to be a multiple of base
template<typename IntType>
constexpr IntType ceil_to_multiple(IntType n, IntType base) {
return (n + base - 1) / base * base;
}
// read_little_endian() is our utility to read an integer (signed or unsigned, any size)
// from a stream in little-endian order. We swap the byte order after the read if
// necessary to return a result with the byte ordering of the compiling machine.
template <typename IntType>
inline IntType read_little_endian(std::istream& stream) {
IntType result;
if (IsLittleEndian)
stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
else
{
std::uint8_t u[sizeof(IntType)];
typename std::make_unsigned<IntType>::type v = 0;
// Utility to read an integer (signed or unsigned, any size)
// from a stream in little-endian order. We swap the byte order after the read if
// necessary to return a result with the byte ordering of the compiling machine.
template<typename IntType>
inline IntType read_little_endian(std::istream& stream) {
IntType result;
stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
for (std::size_t i = 0; i < sizeof(IntType); ++i)
v = (v << 8) | u[sizeof(IntType) - i - 1];
if (IsLittleEndian)
stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
else
{
std::uint8_t u[sizeof(IntType)];
std::make_unsigned_t<IntType> v = 0;
std::memcpy(&result, &v, sizeof(IntType));
}
stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
for (std::size_t i = 0; i < sizeof(IntType); ++i)
v = (v << 8) | u[sizeof(IntType) - i - 1];
return result;
}
std::memcpy(&result, &v, sizeof(IntType));
}
// write_little_endian() is our utility to write an integer (signed or unsigned, any size)
// to a stream in little-endian order. We swap the byte order before the write if
// necessary to always write in little endian order, independently of the byte
// ordering of the compiling machine.
template <typename IntType>
inline void write_little_endian(std::ostream& stream, IntType value) {
return result;
}
if (IsLittleEndian)
stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
else
{
std::uint8_t u[sizeof(IntType)];
typename std::make_unsigned<IntType>::type v = value;
std::size_t i = 0;
// if constexpr to silence the warning about shift by 8
if constexpr (sizeof(IntType) > 1)
{
// Utility to write an integer (signed or unsigned, any size)
// to a stream in little-endian order. We swap the byte order before the write if
// necessary to always write in little-endian order, independently of the byte
// ordering of the compiling machine.
template<typename IntType>
inline void write_little_endian(std::ostream& stream, IntType value) {
if (IsLittleEndian)
stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
else
{
std::uint8_t u[sizeof(IntType)];
std::make_unsigned_t<IntType> v = value;
std::size_t 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] = v;
u[i] = std::uint8_t(v);
v >>= 8;
}
}
u[i] = v;
}
u[i] = std::uint8_t(v);
stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
}
}
stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
}
}
// read_little_endian(s, out, N) : read integers in bulk from a little indian stream.
// This reads N integers from stream s and put them in array out.
template <typename IntType>
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
if (IsLittleEndian)
stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
else
for (std::size_t i = 0; i < count; ++i)
out[i] = read_little_endian<IntType>(stream);
}
// write_little_endian(s, values, N) : write integers in bulk to a little indian 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) {
if (IsLittleEndian)
stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
else
for (std::size_t i = 0; i < count; ++i)
write_little_endian<IntType>(stream, values[i]);
}
// 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) {
if (IsLittleEndian)
stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
else
for (std::size_t i = 0; i < count; ++i)
out[i] = read_little_endian<IntType>(stream);
}
// 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) {
if (IsLittleEndian)
stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
else
for (std::size_t 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 IntType>
inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) {
// Check the presence of our LEB128 magic string
char leb128MagicString[Leb128MagicStringSize];
stream.read(leb128MagicString, Leb128MagicStringSize);
assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
const std::uint32_t BUF_SIZE = 4096;
std::uint8_t buf[BUF_SIZE];
auto bytes_left = read_little_endian<std::uint32_t>(stream);
std::uint32_t buf_pos = BUF_SIZE;
for (std::size_t i = 0; i < count; ++i)
{
IntType result = 0;
size_t shift = 0;
do
{
if (buf_pos == BUF_SIZE)
{
stream.read(reinterpret_cast<char*>(buf), std::min(bytes_left, BUF_SIZE));
buf_pos = 0;
}
std::uint8_t byte = buf[buf_pos++];
--bytes_left;
result |= (byte & 0x7f) << shift;
shift += 7;
if ((byte & 0x80) == 0)
{
out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0)
? result
: result | ~((1 << shift) - 1);
break;
}
} while (shift < sizeof(IntType) * 8);
}
assert(bytes_left == 0);
}
// Write signed integers to a stream with LEB128 compression.
// This takes N integers from array values, compresses them with
// the LEB128 algorithm and writes the result on the stream s.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template<typename IntType>
inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) {
// 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)
{
IntType value = values[i];
std::uint8_t byte;
do
{
byte = value & 0x7f;
value >>= 7;
++byte_count;
} while ((byte & 0x40) == 0 ? value != 0 : value != -1);
}
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;
auto flush = [&]() {
if (buf_pos > 0)
{
stream.write(reinterpret_cast<char*>(buf), buf_pos);
buf_pos = 0;
}
};
auto write = [&](std::uint8_t byte) {
buf[buf_pos++] = byte;
if (buf_pos == BUF_SIZE)
flush();
};
for (std::size_t i = 0; i < count; ++i)
{
IntType value = values[i];
while (true)
{
std::uint8_t byte = value & 0x7f;
value >>= 7;
if ((byte & 0x40) == 0 ? value == 0 : value == -1)
{
write(byte);
break;
}
write(byte | 0x80);
}
}
flush();
}
} // namespace Stockfish::Eval::NNUE
#endif // #ifndef NNUE_COMMON_H_INCLUDED
#endif // #ifndef NNUE_COMMON_H_INCLUDED

File diff suppressed because it is too large Load diff

202
src/nnue/nnue_misc.cpp Normal file
View file

@ -0,0 +1,202 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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/>.
*/
// Code for calculating NNUE evaluation function
#include "nnue_misc.h"
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iosfwd>
#include <iostream>
#include <sstream>
#include <string_view>
#include "../evaluate.h"
#include "../position.h"
#include "../types.h"
#include "../uci.h"
#include "network.h"
#include "nnue_accumulator.h"
namespace Stockfish::Eval::NNUE {
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
void hint_common_parent_position(const Position& pos,
const Networks& networks,
AccumulatorCaches& caches) {
int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move()));
if (simpleEvalAbs > Eval::SmallNetThreshold)
networks.small.hint_common_access(pos, &caches.small);
else
networks.big.hint_common_access(pos, &caches.big);
}
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
void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& pos) {
const double pawns = std::abs(0.01 * UCIEngine::to_cp(v, pos));
stream << (v < 0 ? '-'
: v > 0 ? '+'
: ' ')
<< std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns;
}
}
// 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) {
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 (value != VALUE_NONE)
format_cp_compact(value, &board[y + 2][x + 2], pos);
};
// 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.
Value base = networks.big.evaluate(pos, &caches.big);
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)
{
auto st = pos.state();
pos.remove_piece(sq);
st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false;
Value eval = networks.big.evaluate(pos, &caches.big);
eval = pos.side_to_move() == WHITE ? eval : -eval;
v = base - eval;
pos.put_piece(pc, sq);
st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] = false;
}
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';
auto t = networks.big.trace_evaluate(pos, &caches.big);
ss << " NNUE network contributions "
<< (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)
{
ss << "| " << bucket << " ";
ss << " | ";
format_cp_aligned_dot(t.psqt[bucket], ss, pos);
ss << " "
<< " | ";
format_cp_aligned_dot(t.positional[bucket], ss, pos);
ss << " "
<< " | ";
format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss, pos);
ss << " "
<< " |";
if (bucket == t.correctBucket)
ss << " <-- this bucket is used";
ss << '\n';
}
ss << "+------------+------------+------------+------------+\n";
return ss.str();
}
} // namespace Stockfish::Eval::NNUE

64
src/nnue/nnue_misc.h Normal file
View file

@ -0,0 +1,64 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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_MISC_H_INCLUDED
#define NNUE_MISC_H_INCLUDED
#include <cstddef>
#include <string>
#include "../types.h"
#include "nnue_architecture.h"
namespace Stockfish {
class Position;
namespace Eval::NNUE {
struct EvalFile {
// Default net name, will use one of the EvalFileDefaultName* macros defined
// in evaluate.h
std::string defaultName;
// Selected net name, either via uci option or default
std::string current;
// Net description extracted from the net file
std::string netDescription;
};
struct NnueEvalTrace {
static_assert(LayerStacks == PSQTBuckets);
Value psqt[LayerStacks];
Value positional[LayerStacks];
std::size_t correctBucket;
};
struct Networks;
struct AccumulatorCaches;
std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches);
void hint_common_parent_position(const Position& pos,
const Networks& networks,
AccumulatorCaches& caches);
} // namespace Stockfish::Eval::NNUE
} // namespace Stockfish
#endif // #ifndef NNUE_MISC_H_INCLUDED

View file

@ -1,305 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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 <algorithm>
#include <cassert>
#include "bitboard.h"
#include "pawns.h"
#include "position.h"
#include "thread.h"
namespace Stockfish {
namespace {
#define V Value
#define S(mg, eg) make_score(mg, eg)
// Pawn penalties
constexpr Score Backward = S( 9, 22);
constexpr Score Doubled = S(13, 51);
constexpr Score DoubledEarly = S(20, 7);
constexpr Score Isolated = S( 3, 15);
constexpr Score WeakLever = S( 4, 58);
constexpr Score WeakUnopposed = S(13, 24);
// Bonus for blocked pawns at 5th or 6th rank
constexpr Score BlockedPawn[2] = { S(-17, -6), S(-9, 2) };
constexpr Score BlockedStorm[RANK_NB] = {
S(0, 0), S(0, 0), S(75, 78), S(-8, 16), S(-6, 10), S(-6, 6), S(0, 2)
};
// Connected pawn bonus
constexpr int Connected[RANK_NB] = { 0, 5, 7, 11, 23, 48, 87 };
// Strength of pawn shelter for our king by [distance from edge][rank].
// RANK_1 = 0 is used for files where we have no pawn, or pawn is behind our king.
constexpr Value ShelterStrength[int(FILE_NB) / 2][RANK_NB] = {
{ V( -5), V( 82), V( 92), V( 54), V( 36), V( 22), V( 28) },
{ V(-44), V( 63), V( 33), V(-50), V(-30), V(-12), V( -62) },
{ V(-11), V( 77), V( 22), V( -6), V( 31), V( 8), V( -45) },
{ V(-39), V(-12), V(-29), V(-50), V(-43), V(-68), V(-164) }
};
// Danger of enemy pawns moving toward our king by [distance from edge][rank].
// RANK_1 = 0 is used for files where the enemy has no pawn, or their pawn
// is behind our king. Note that UnblockedStorm[0][1-2] accommodate opponent pawn
// on edge, likely blocked by our king.
constexpr Value UnblockedStorm[int(FILE_NB) / 2][RANK_NB] = {
{ V( 87), V(-288), V(-168), V( 96), V( 47), V( 44), V( 46) },
{ V( 42), V( -25), V( 120), V( 45), V( 34), V( -9), V( 24) },
{ V( -8), V( 51), V( 167), V( 35), V( -4), V(-16), V(-12) },
{ V(-17), V( -13), V( 100), V( 4), V( 9), V(-16), V(-31) }
};
// KingOnFile[semi-open Us][semi-open Them] contains bonuses/penalties
// for king when the king is on a semi-open or open file.
constexpr Score KingOnFile[2][2] = {{ S(-21,10), S(-7, 1) },
{ S( 0,-3), S( 9,-4) }};
#undef S
#undef V
/// evaluate() calculates a score for the static pawn structure of the given position.
/// We cannot use the location of pieces or king in this function, as the evaluation
/// of the pawn structure will be stored in a small cache for speed reasons, and will
/// be re-used even when the pieces have moved.
template<Color Us>
Score evaluate(const Position& pos, Pawns::Entry* e) {
constexpr Color Them = ~Us;
constexpr Direction Up = pawn_push(Us);
constexpr Direction Down = -Up;
Bitboard neighbours, stoppers, support, phalanx, opposed;
Bitboard lever, leverPush, blocked;
Square s;
bool backward, passed, doubled;
Score score = SCORE_ZERO;
Bitboard b = pos.pieces(Us, PAWN);
Bitboard ourPawns = pos.pieces( Us, PAWN);
Bitboard theirPawns = pos.pieces(Them, PAWN);
Bitboard doubleAttackThem = pawn_double_attacks_bb<Them>(theirPawns);
e->passedPawns[Us] = 0;
e->kingSquares[Us] = SQ_NONE;
e->pawnAttacks[Us] = e->pawnAttacksSpan[Us] = pawn_attacks_bb<Us>(ourPawns);
e->blockedCount += popcount(shift<Up>(ourPawns) & (theirPawns | doubleAttackThem));
// Loop through all pawns of the current color and score each pawn
while (b)
{
s = pop_lsb(b);
assert(pos.piece_on(s) == make_piece(Us, PAWN));
Rank r = relative_rank(Us, s);
// Flag the pawn
opposed = theirPawns & forward_file_bb(Us, s);
blocked = theirPawns & (s + Up);
stoppers = theirPawns & passed_pawn_span(Us, s);
lever = theirPawns & pawn_attacks_bb(Us, s);
leverPush = theirPawns & pawn_attacks_bb(Us, s + Up);
doubled = ourPawns & (s - Up);
neighbours = ourPawns & adjacent_files_bb(s);
phalanx = neighbours & rank_bb(s);
support = neighbours & rank_bb(s - Up);
if (doubled)
{
// Additional doubled penalty if none of their pawns is fixed
if (!(ourPawns & shift<Down>(theirPawns | pawn_attacks_bb<Them>(theirPawns))))
score -= DoubledEarly;
}
// A pawn is backward when it is behind all pawns of the same color on
// the adjacent files and cannot safely advance.
backward = !(neighbours & forward_ranks_bb(Them, s + Up))
&& (leverPush | blocked);
// Compute additional span if pawn is not backward nor blocked
if (!backward && !blocked)
e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s);
// A pawn is passed if one of the three following conditions is true:
// (a) there is no stoppers except some levers
// (b) the only stoppers are the leverPush, but we outnumber them
// (c) there is only one front stopper which can be levered.
// (Refined in Evaluation::passed)
passed = !(stoppers ^ lever)
|| ( !(stoppers ^ leverPush)
&& popcount(phalanx) >= popcount(leverPush))
|| ( stoppers == blocked && r >= RANK_5
&& (shift<Up>(support) & ~(theirPawns | doubleAttackThem)));
passed &= !(forward_file_bb(Us, s) & ourPawns);
// Passed pawns will be properly scored later in evaluation when we have
// full attack info.
if (passed)
e->passedPawns[Us] |= s;
// Score this pawn
if (support | phalanx)
{
int v = Connected[r] * (2 + bool(phalanx) - bool(opposed))
+ 22 * popcount(support);
score += make_score(v, v * (r - 2) / 4);
}
else if (!neighbours)
{
if ( opposed
&& (ourPawns & forward_file_bb(Them, s))
&& !(theirPawns & adjacent_files_bb(s)))
score -= Doubled;
else
score -= Isolated
+ WeakUnopposed * !opposed;
}
else if (backward)
score -= Backward
+ WeakUnopposed * !opposed * bool(~(FileABB | FileHBB) & s);
if (!support)
score -= Doubled * doubled
+ WeakLever * more_than_one(lever);
if (blocked && r >= RANK_5)
score += BlockedPawn[r - RANK_5];
}
return score;
}
} // namespace
namespace Pawns {
/// Pawns::probe() looks up the current position's pawns configuration in
/// the pawns hash table. It returns a pointer to the Entry if the position
/// is found. Otherwise a new Entry is computed and stored there, so we don't
/// have to recompute all when the same pawns configuration occurs again.
Entry* probe(const Position& pos) {
Key key = pos.pawn_key();
Entry* e = pos.this_thread()->pawnsTable[key];
if (e->key == key)
return e;
e->key = key;
e->blockedCount = 0;
e->scores[WHITE] = evaluate<WHITE>(pos, e);
e->scores[BLACK] = evaluate<BLACK>(pos, e);
return e;
}
/// Entry::evaluate_shelter() calculates the shelter bonus and the storm
/// penalty for a king, looking at the king file and the two closest files.
template<Color Us>
Score Entry::evaluate_shelter(const Position& pos, Square ksq) const {
constexpr Color Them = ~Us;
Bitboard b = pos.pieces(PAWN) & ~forward_ranks_bb(Them, ksq);
Bitboard ourPawns = b & pos.pieces(Us) & ~pawnAttacks[Them];
Bitboard theirPawns = b & pos.pieces(Them);
Score bonus = make_score(5, 5);
File center = std::clamp(file_of(ksq), FILE_B, FILE_G);
for (File f = File(center - 1); f <= File(center + 1); ++f)
{
b = ourPawns & file_bb(f);
int ourRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
b = theirPawns & file_bb(f);
int theirRank = b ? relative_rank(Us, frontmost_sq(Them, b)) : 0;
int d = edge_distance(f);
bonus += make_score(ShelterStrength[d][ourRank], 0);
if (ourRank && (ourRank == theirRank - 1))
bonus -= BlockedStorm[theirRank];
else
bonus -= make_score(UnblockedStorm[d][theirRank], 0);
}
// King On File
bonus -= KingOnFile[pos.is_on_semiopen_file(Us, ksq)][pos.is_on_semiopen_file(Them, ksq)];
return bonus;
}
/// Entry::do_king_safety() calculates a bonus for king safety. It is called only
/// when king square changes, which is about 20% of total king_safety() calls.
template<Color Us>
Score Entry::do_king_safety(const Position& pos) {
Square ksq = pos.square<KING>(Us);
kingSquares[Us] = ksq;
castlingRights[Us] = pos.castling_rights(Us);
auto compare = [](Score a, Score b) { return mg_value(a) < mg_value(b); };
Score shelter = evaluate_shelter<Us>(pos, ksq);
// If we can castle use the bonus after castling if it is bigger
if (pos.can_castle(Us & KING_SIDE))
shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_G1)), compare);
if (pos.can_castle(Us & QUEEN_SIDE))
shelter = std::max(shelter, evaluate_shelter<Us>(pos, relative_square(Us, SQ_C1)), compare);
// In endgame we like to bring our king near our closest pawn
Bitboard pawns = pos.pieces(Us, PAWN);
int minPawnDist = 6;
if (pawns & attacks_bb<KING>(ksq))
minPawnDist = 1;
else while (pawns)
minPawnDist = std::min(minPawnDist, distance(ksq, pop_lsb(pawns)));
return shelter - make_score(0, 16 * minPawnDist);
}
// Explicit template instantiation
template Score Entry::do_king_safety<WHITE>(const Position& pos);
template Score Entry::do_king_safety<BLACK>(const Position& pos);
} // namespace Pawns
} // namespace Stockfish

View file

@ -1,70 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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 PAWNS_H_INCLUDED
#define PAWNS_H_INCLUDED
#include "misc.h"
#include "position.h"
#include "types.h"
namespace Stockfish::Pawns {
/// Pawns::Entry contains various information about a pawn structure. A lookup
/// to the pawn hash table (performed by calling the probe function) returns a
/// pointer to an Entry object.
struct Entry {
Score pawn_score(Color c) const { return scores[c]; }
Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; }
Bitboard passed_pawns(Color c) const { return passedPawns[c]; }
Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; }
int passed_count() const { return popcount(passedPawns[WHITE] | passedPawns[BLACK]); }
int blocked_count() const { return blockedCount; }
template<Color Us>
Score king_safety(const Position& pos) {
return kingSquares[Us] == pos.square<KING>(Us) && castlingRights[Us] == pos.castling_rights(Us)
? kingSafety[Us] : (kingSafety[Us] = do_king_safety<Us>(pos));
}
template<Color Us>
Score do_king_safety(const Position& pos);
template<Color Us>
Score evaluate_shelter(const Position& pos, Square ksq) const;
Key key;
Score scores[COLOR_NB];
Bitboard passedPawns[COLOR_NB];
Bitboard pawnAttacks[COLOR_NB];
Bitboard pawnAttacksSpan[COLOR_NB];
Square kingSquares[COLOR_NB];
Score kingSafety[COLOR_NB];
int castlingRights[COLOR_NB];
int blockedCount;
};
typedef HashTable<Entry, 131072> Table;
Entry* probe(const Position& pos);
} // namespace Stockfish::Pawns
#endif // #ifndef PAWNS_H_INCLUDED

68
src/perft.h Normal file
View file

@ -0,0 +1,68 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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 PERFT_H_INCLUDED
#define PERFT_H_INCLUDED
#include <cstdint>
#include "movegen.h"
#include "position.h"
#include "types.h"
#include "uci.h"
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) {
StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
uint64_t cnt, nodes = 0;
const bool leaf = (depth == 2);
for (const auto& m : MoveList<LEGAL>(pos))
{
if (Root && depth <= 1)
cnt = 1, nodes++;
else
{
pos.do_move(m, st);
cnt = leaf ? MoveList<LEGAL>(pos).size() : perft<false>(pos, depth - 1);
nodes += cnt;
pos.undo_move(m);
}
if (Root)
sync_cout << UCIEngine::move(m, pos.is_chess960()) << ": " << cnt << sync_endl;
}
return nodes;
}
inline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) {
StateListPtr states(new std::deque<StateInfo>(1));
Position p;
p.set(fen, isChess960, &states->back());
return perft<true>(p, depth);
}
}
#endif // PERFT_H_INCLUDED

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,399 +21,344 @@
#include <cassert>
#include <deque>
#include <memory> // For std::unique_ptr
#include <iosfwd>
#include <memory>
#include <string>
#include "bitboard.h"
#include "evaluate.h"
#include "psqt.h"
#include "types.h"
#include "nnue/nnue_accumulator.h"
#include "nnue/nnue_architecture.h"
#include "types.h"
namespace Stockfish {
/// StateInfo struct stores information needed to restore a Position object to
/// its previous state when we retract a move. Whenever a move is made on the
/// board (by calling Position::do_move), a StateInfo object must be passed.
class TranspositionTable;
// StateInfo struct stores information needed to restore a Position object to
// its previous state when we retract a move. Whenever a move is made on the
// board (by calling Position::do_move), a StateInfo object must be passed.
struct StateInfo {
// Copied when making a move
Key pawnKey;
Key materialKey;
Value nonPawnMaterial[COLOR_NB];
int castlingRights;
int rule50;
int pliesFromNull;
Square epSquare;
// Copied when making a move
Key materialKey;
Key pawnKey;
Value nonPawnMaterial[COLOR_NB];
int castlingRights;
int rule50;
int pliesFromNull;
Square epSquare;
// Not copied when making a move (will be recomputed anyhow)
Key key;
Bitboard checkersBB;
StateInfo* previous;
Bitboard blockersForKing[COLOR_NB];
Bitboard pinners[COLOR_NB];
Bitboard checkSquares[PIECE_TYPE_NB];
Piece capturedPiece;
int repetition;
// Not copied when making a move (will be recomputed anyhow)
Key key;
Bitboard checkersBB;
StateInfo* previous;
Bitboard blockersForKing[COLOR_NB];
Bitboard pinners[COLOR_NB];
Bitboard checkSquares[PIECE_TYPE_NB];
Piece capturedPiece;
int repetition;
// Used by NNUE
Eval::NNUE::Accumulator accumulator;
DirtyPiece dirtyPiece;
// Used by NNUE
Eval::NNUE::Accumulator<Eval::NNUE::TransformedFeatureDimensionsBig> accumulatorBig;
Eval::NNUE::Accumulator<Eval::NNUE::TransformedFeatureDimensionsSmall> accumulatorSmall;
DirtyPiece dirtyPiece;
};
/// A list to keep track of the position states along the setup moves (from the
/// start position to the position just before the search starts). Needed by
/// 'draw by repetition' detection. Use a std::deque because pointers to
/// elements are not invalidated upon list resizing.
typedef std::unique_ptr<std::deque<StateInfo>> StateListPtr;
// A list to keep track of the position states along the setup moves (from the
// start position to the position just before the search starts). Needed by
// 'draw by repetition' detection. Use a std::deque because pointers to
// elements are not invalidated upon list resizing.
using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
/// Position class stores information regarding the board representation as
/// pieces, side to move, hash keys, castling info, etc. Important methods are
/// do_move() and undo_move(), used by the search to update node info when
/// traversing the search tree.
class Thread;
// 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
// traversing the search tree.
class Position {
public:
static void init();
public:
static void init();
Position() = default;
Position(const Position&) = delete;
Position& operator=(const Position&) = delete;
Position() = default;
Position(const Position&) = delete;
Position& operator=(const Position&) = delete;
// FEN string input/output
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
Position& set(const std::string& code, Color c, StateInfo* si);
std::string fen() const;
// 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;
// Position representation
Bitboard pieces(PieceType pt) const;
Bitboard pieces(PieceType pt1, PieceType pt2) const;
Bitboard pieces(Color c) const;
Bitboard pieces(Color c, PieceType pt) const;
Bitboard pieces(Color c, PieceType pt1, PieceType pt2) const;
Piece piece_on(Square s) const;
Square ep_square() const;
bool empty(Square s) const;
template<PieceType Pt> int count(Color c) const;
template<PieceType Pt> int count() const;
template<PieceType Pt> Square square(Color c) const;
bool is_on_semiopen_file(Color c, Square s) const;
// Position representation
Bitboard pieces(PieceType pt = ALL_PIECES) const;
template<typename... PieceTypes>
Bitboard pieces(PieceType pt, PieceTypes... pts) const;
Bitboard pieces(Color c) const;
template<typename... PieceTypes>
Bitboard pieces(Color c, PieceTypes... pts) const;
Piece piece_on(Square s) const;
Square ep_square() const;
bool empty(Square s) const;
template<PieceType Pt>
int count(Color c) const;
template<PieceType Pt>
int count() const;
template<PieceType Pt>
Square square(Color c) const;
// Castling
CastlingRights castling_rights(Color c) const;
bool can_castle(CastlingRights cr) const;
bool castling_impeded(CastlingRights cr) const;
Square castling_rook_square(CastlingRights cr) const;
// Castling
CastlingRights castling_rights(Color c) const;
bool can_castle(CastlingRights cr) const;
bool castling_impeded(CastlingRights cr) const;
Square castling_rook_square(CastlingRights cr) const;
// Checking
Bitboard checkers() const;
Bitboard blockers_for_king(Color c) const;
Bitboard check_squares(PieceType pt) const;
Bitboard pinners(Color c) const;
// Checking
Bitboard checkers() const;
Bitboard blockers_for_king(Color c) const;
Bitboard check_squares(PieceType pt) const;
Bitboard pinners(Color c) const;
// Attacks to/from a given square
Bitboard attackers_to(Square s) const;
Bitboard attackers_to(Square s, Bitboard occupied) const;
Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
// Attacks to/from a given square
Bitboard attackers_to(Square s) const;
Bitboard attackers_to(Square s, Bitboard occupied) const;
void update_slider_blockers(Color c) const;
template<PieceType Pt>
Bitboard attacks_by(Color c) const;
// Properties of moves
bool legal(Move m) const;
bool pseudo_legal(const Move m) const;
bool capture(Move m) const;
bool capture_or_promotion(Move m) const;
bool gives_check(Move m) const;
Piece moved_piece(Move m) const;
Piece captured_piece() const;
// Properties of moves
bool legal(Move m) const;
bool pseudo_legal(const Move m) const;
bool capture(Move m) const;
bool capture_stage(Move m) const;
bool gives_check(Move m) const;
Piece moved_piece(Move m) const;
Piece captured_piece() const;
// Piece specific
bool pawn_passed(Color c, Square s) const;
bool opposite_bishops() const;
int pawns_on_same_color_squares(Color c, Square s) const;
// Doing and undoing moves
void do_move(Move m, StateInfo& newSt);
void do_move(Move m, StateInfo& newSt, bool givesCheck);
void undo_move(Move m);
void do_null_move(StateInfo& newSt, TranspositionTable& tt);
void undo_null_move();
// Doing and undoing moves
void do_move(Move m, StateInfo& newSt);
void do_move(Move m, StateInfo& newSt, bool givesCheck);
void undo_move(Move m);
void do_null_move(StateInfo& newSt);
void undo_null_move();
// Static Exchange Evaluation
bool see_ge(Move m, int threshold = 0) const;
// Static Exchange Evaluation
bool see_ge(Move m, Value threshold = VALUE_ZERO) const;
// Accessing hash keys
Key key() const;
Key key_after(Move m) const;
Key material_key() const;
Key pawn_key() const;
// Accessing hash keys
Key key() const;
Key key_after(Move m) const;
Key material_key() const;
Key pawn_key() const;
// Other properties of the position
Color side_to_move() const;
int game_ply() const;
bool is_chess960() const;
bool is_draw(int ply) const;
bool has_game_cycle(int ply) const;
bool has_repeated() const;
int rule50_count() const;
Value non_pawn_material(Color c) const;
Value non_pawn_material() const;
// Other properties of the position
Color side_to_move() const;
int game_ply() const;
bool is_chess960() const;
Thread* this_thread() const;
bool is_draw(int ply) const;
bool has_game_cycle(int ply) const;
bool has_repeated() const;
int rule50_count() const;
Score psq_score() const;
Value non_pawn_material(Color c) const;
Value non_pawn_material() const;
// Position consistency check, for debugging
bool pos_is_ok() const;
void flip();
// Position consistency check, for debugging
bool pos_is_ok() const;
void flip();
// Used by NNUE
StateInfo* state() const;
// Used by NNUE
StateInfo* state() const;
void put_piece(Piece pc, Square s);
void remove_piece(Square s);
void put_piece(Piece pc, Square s);
void remove_piece(Square s);
private:
// Initialization helpers (used while setting up a position)
void set_castling_right(Color c, Square rfrom);
void set_state() const;
void set_check_info() const;
private:
// Initialization helpers (used while setting up a position)
void set_castling_right(Color c, Square rfrom);
void set_state(StateInfo* si) const;
void set_check_info(StateInfo* si) const;
// Other helpers
void move_piece(Square from, Square to);
template<bool Do>
void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
template<bool AfterMove>
Key adjust_key50(Key k) const;
// Other helpers
void move_piece(Square from, Square to);
template<bool Do>
void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto);
// Data members
Piece board[SQUARE_NB];
Bitboard byTypeBB[PIECE_TYPE_NB];
Bitboard byColorBB[COLOR_NB];
int pieceCount[PIECE_NB];
int castlingRightsMask[SQUARE_NB];
Square castlingRookSquare[CASTLING_RIGHT_NB];
Bitboard castlingPath[CASTLING_RIGHT_NB];
Thread* thisThread;
StateInfo* st;
int gamePly;
Color sideToMove;
Score psq;
bool chess960;
// Data members
Piece board[SQUARE_NB];
Bitboard byTypeBB[PIECE_TYPE_NB];
Bitboard byColorBB[COLOR_NB];
int pieceCount[PIECE_NB];
int castlingRightsMask[SQUARE_NB];
Square castlingRookSquare[CASTLING_RIGHT_NB];
Bitboard castlingPath[CASTLING_RIGHT_NB];
StateInfo* st;
int gamePly;
Color sideToMove;
bool chess960;
};
extern std::ostream& operator<<(std::ostream& os, const Position& pos);
std::ostream& operator<<(std::ostream& os, const Position& pos);
inline Color Position::side_to_move() const {
return sideToMove;
}
inline Color Position::side_to_move() const { return sideToMove; }
inline Piece Position::piece_on(Square s) const {
assert(is_ok(s));
return board[s];
assert(is_ok(s));
return board[s];
}
inline bool Position::empty(Square s) const {
return piece_on(s) == NO_PIECE;
inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; }
inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); }
inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; }
template<typename... PieceTypes>
inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const {
return pieces(pt) | pieces(pts...);
}
inline Piece Position::moved_piece(Move m) const {
return piece_on(from_sq(m));
inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; }
template<typename... PieceTypes>
inline Bitboard Position::pieces(Color c, PieceTypes... pts) const {
return pieces(c) & pieces(pts...);
}
inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const {
return byTypeBB[pt];
template<PieceType Pt>
inline int Position::count(Color c) const {
return pieceCount[make_piece(c, Pt)];
}
inline Bitboard Position::pieces(PieceType pt1, PieceType pt2) const {
return pieces(pt1) | pieces(pt2);
template<PieceType Pt>
inline int Position::count() const {
return count<Pt>(WHITE) + count<Pt>(BLACK);
}
inline Bitboard Position::pieces(Color c) const {
return byColorBB[c];
template<PieceType Pt>
inline Square Position::square(Color c) const {
assert(count<Pt>(c) == 1);
return lsb(pieces(c, Pt));
}
inline Bitboard Position::pieces(Color c, PieceType pt) const {
return pieces(c) & pieces(pt);
}
inline Square Position::ep_square() const { return st->epSquare; }
inline Bitboard Position::pieces(Color c, PieceType pt1, PieceType pt2) const {
return pieces(c) & (pieces(pt1) | pieces(pt2));
}
template<PieceType Pt> inline int Position::count(Color c) const {
return pieceCount[make_piece(c, Pt)];
}
template<PieceType Pt> inline int Position::count() const {
return count<Pt>(WHITE) + count<Pt>(BLACK);
}
template<PieceType Pt> inline Square Position::square(Color c) const {
assert(count<Pt>(c) == 1);
return lsb(pieces(c, Pt));
}
inline Square Position::ep_square() const {
return st->epSquare;
}
inline bool Position::is_on_semiopen_file(Color c, Square s) const {
return !(pieces(c, PAWN) & file_bb(s));
}
inline bool Position::can_castle(CastlingRights cr) const {
return st->castlingRights & cr;
}
inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; }
inline CastlingRights Position::castling_rights(Color c) const {
return c & CastlingRights(st->castlingRights);
return c & CastlingRights(st->castlingRights);
}
inline bool Position::castling_impeded(CastlingRights cr) const {
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
return pieces() & castlingPath[cr];
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
return pieces() & castlingPath[cr];
}
inline Square Position::castling_rook_square(CastlingRights cr) const {
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
return castlingRookSquare[cr];
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
return castlingRookSquare[cr];
}
inline Bitboard Position::attackers_to(Square s) const {
return attackers_to(s, pieces());
inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); }
template<PieceType Pt>
inline Bitboard Position::attacks_by(Color c) const {
if constexpr (Pt == PAWN)
return c == WHITE ? pawn_attacks_bb<WHITE>(pieces(WHITE, PAWN))
: pawn_attacks_bb<BLACK>(pieces(BLACK, PAWN));
else
{
Bitboard threats = 0;
Bitboard attackers = pieces(c, Pt);
while (attackers)
threats |= attacks_bb<Pt>(pop_lsb(attackers), pieces());
return threats;
}
}
inline Bitboard Position::checkers() const {
return st->checkersBB;
inline Bitboard Position::checkers() const { return st->checkersBB; }
inline Bitboard Position::blockers_for_king(Color c) const { return st->blockersForKing[c]; }
inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; }
inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; }
inline Key Position::key() const { return adjust_key50<false>(st->key); }
template<bool AfterMove>
inline Key Position::adjust_key50(Key k) const {
return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
}
inline Bitboard Position::blockers_for_king(Color c) const {
return st->blockersForKing[c];
}
inline Key Position::pawn_key() const { return st->pawnKey; }
inline Bitboard Position::pinners(Color c) const {
return st->pinners[c];
}
inline Key Position::material_key() const { return st->materialKey; }
inline Bitboard Position::check_squares(PieceType pt) const {
return st->checkSquares[pt];
}
inline bool Position::pawn_passed(Color c, Square s) const {
return !(pieces(~c, PAWN) & passed_pawn_span(c, s));
}
inline int Position::pawns_on_same_color_squares(Color c, Square s) const {
return popcount(pieces(c, PAWN) & ((DarkSquares & s) ? DarkSquares : ~DarkSquares));
}
inline Key Position::key() const {
return st->rule50 < 14 ? st->key
: st->key ^ make_key((st->rule50 - 14) / 8);
}
inline Key Position::pawn_key() const {
return st->pawnKey;
}
inline Key Position::material_key() const {
return st->materialKey;
}
inline Score Position::psq_score() const {
return psq;
}
inline Value Position::non_pawn_material(Color c) const {
return st->nonPawnMaterial[c];
}
inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; }
inline Value Position::non_pawn_material() const {
return non_pawn_material(WHITE) + non_pawn_material(BLACK);
return non_pawn_material(WHITE) + non_pawn_material(BLACK);
}
inline int Position::game_ply() const {
return gamePly;
}
inline int Position::game_ply() const { return gamePly; }
inline int Position::rule50_count() const {
return st->rule50;
}
inline int Position::rule50_count() const { return st->rule50; }
inline bool Position::opposite_bishops() const {
return count<BISHOP>(WHITE) == 1
&& count<BISHOP>(BLACK) == 1
&& opposite_colors(square<BISHOP>(WHITE), square<BISHOP>(BLACK));
}
inline bool Position::is_chess960() const {
return chess960;
}
inline bool Position::capture_or_promotion(Move m) const {
assert(is_ok(m));
return type_of(m) != NORMAL ? type_of(m) != CASTLING : !empty(to_sq(m));
}
inline bool Position::is_chess960() const { return chess960; }
inline bool Position::capture(Move m) const {
assert(is_ok(m));
// Castling is encoded as "king captures rook"
return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT;
assert(m.is_ok());
return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT;
}
inline Piece Position::captured_piece() const {
return st->capturedPiece;
// Returns true if a move is generated from the capture stage, having also
// queen promotions covered, i.e. consistency with the capture stage move 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;
}
inline Thread* Position::this_thread() const {
return thisThread;
}
inline Piece Position::captured_piece() const { return st->capturedPiece; }
inline void Position::put_piece(Piece pc, Square s) {
board[s] = pc;
byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;
byColorBB[color_of(pc)] |= s;
pieceCount[pc]++;
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
psq += PSQT::psq[pc][s];
board[s] = pc;
byTypeBB[ALL_PIECES] |= byTypeBB[type_of(pc)] |= s;
byColorBB[color_of(pc)] |= s;
pieceCount[pc]++;
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
}
inline void Position::remove_piece(Square s) {
Piece pc = board[s];
byTypeBB[ALL_PIECES] ^= s;
byTypeBB[type_of(pc)] ^= s;
byColorBB[color_of(pc)] ^= s;
board[s] = NO_PIECE;
pieceCount[pc]--;
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
psq -= PSQT::psq[pc][s];
Piece pc = board[s];
byTypeBB[ALL_PIECES] ^= s;
byTypeBB[type_of(pc)] ^= s;
byColorBB[color_of(pc)] ^= s;
board[s] = NO_PIECE;
pieceCount[pc]--;
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
}
inline void Position::move_piece(Square from, Square to) {
Piece pc = board[from];
Bitboard fromTo = from | to;
byTypeBB[ALL_PIECES] ^= fromTo;
byTypeBB[type_of(pc)] ^= fromTo;
byColorBB[color_of(pc)] ^= fromTo;
board[from] = NO_PIECE;
board[to] = pc;
psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
Piece pc = board[from];
Bitboard fromTo = from | to;
byTypeBB[ALL_PIECES] ^= fromTo;
byTypeBB[type_of(pc)] ^= fromTo;
byColorBB[color_of(pc)] ^= fromTo;
board[from] = NO_PIECE;
board[to] = pc;
}
inline void Position::do_move(Move m, StateInfo& newSt) {
do_move(m, newSt, gives_check(m));
}
inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); }
inline StateInfo* Position::state() const {
inline StateInfo* Position::state() const { return st; }
return st;
}
} // namespace Stockfish
} // namespace Stockfish
#endif // #ifndef POSITION_H_INCLUDED
#endif // #ifndef POSITION_H_INCLUDED

View file

@ -1,131 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 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 "psqt.h"
#include <algorithm>
#include "bitboard.h"
#include "types.h"
namespace Stockfish {
namespace
{
auto constexpr S = make_score;
// 'Bonus' contains Piece-Square parameters.
// Scores are explicit for files A to D, implicitly mirrored for E to H.
constexpr Score Bonus[][RANK_NB][int(FILE_NB) / 2] = {
{ },
{ },
{ // Knight
{ S(-175, -96), S(-92,-65), S(-74,-49), S(-73,-21) },
{ S( -77, -67), S(-41,-54), S(-27,-18), S(-15, 8) },
{ S( -61, -40), S(-17,-27), S( 6, -8), S( 12, 29) },
{ S( -35, -35), S( 8, -2), S( 40, 13), S( 49, 28) },
{ S( -34, -45), S( 13,-16), S( 44, 9), S( 51, 39) },
{ S( -9, -51), S( 22,-44), S( 58,-16), S( 53, 17) },
{ S( -67, -69), S(-27,-50), S( 4,-51), S( 37, 12) },
{ S(-201,-100), S(-83,-88), S(-56,-56), S(-26,-17) }
},
{ // Bishop
{ S(-37,-40), S(-4 ,-21), S( -6,-26), S(-16, -8) },
{ S(-11,-26), S( 6, -9), S( 13,-12), S( 3, 1) },
{ S(-5 ,-11), S( 15, -1), S( -4, -1), S( 12, 7) },
{ S(-4 ,-14), S( 8, -4), S( 18, 0), S( 27, 12) },
{ S(-8 ,-12), S( 20, -1), S( 15,-10), S( 22, 11) },
{ S(-11,-21), S( 4, 4), S( 1, 3), S( 8, 4) },
{ S(-12,-22), S(-10,-14), S( 4, -1), S( 0, 1) },
{ S(-34,-32), S( 1,-29), S(-10,-26), S(-16,-17) }
},
{ // Rook
{ S(-31, -9), S(-20,-13), S(-14,-10), S(-5, -9) },
{ S(-21,-12), S(-13, -9), S( -8, -1), S( 6, -2) },
{ S(-25, 6), S(-11, -8), S( -1, -2), S( 3, -6) },
{ S(-13, -6), S( -5, 1), S( -4, -9), S(-6, 7) },
{ S(-27, -5), S(-15, 8), S( -4, 7), S( 3, -6) },
{ S(-22, 6), S( -2, 1), S( 6, -7), S(12, 10) },
{ S( -2, 4), S( 12, 5), S( 16, 20), S(18, -5) },
{ S(-17, 18), S(-19, 0), S( -1, 19), S( 9, 13) }
},
{ // Queen
{ S( 3,-69), S(-5,-57), S(-5,-47), S( 4,-26) },
{ S(-3,-54), S( 5,-31), S( 8,-22), S(12, -4) },
{ S(-3,-39), S( 6,-18), S(13, -9), S( 7, 3) },
{ S( 4,-23), S( 5, -3), S( 9, 13), S( 8, 24) },
{ S( 0,-29), S(14, -6), S(12, 9), S( 5, 21) },
{ S(-4,-38), S(10,-18), S( 6,-11), S( 8, 1) },
{ S(-5,-50), S( 6,-27), S(10,-24), S( 8, -8) },
{ S(-2,-74), S(-2,-52), S( 1,-43), S(-2,-34) }
},
{ // King
{ S(271, 1), S(327, 45), S(271, 85), S(198, 76) },
{ S(278, 53), S(303,100), S(234,133), S(179,135) },
{ S(195, 88), S(258,130), S(169,169), S(120,175) },
{ S(164,103), S(190,156), S(138,172), S( 98,172) },
{ S(154, 96), S(179,166), S(105,199), S( 70,199) },
{ S(123, 92), S(145,172), S( 81,184), S( 31,191) },
{ S( 88, 47), S(120,121), S( 65,116), S( 33,131) },
{ S( 59, 11), S( 89, 59), S( 45, 73), S( -1, 78) }
}
};
constexpr Score PBonus[RANK_NB][FILE_NB] =
{ // Pawn (asymmetric distribution)
{ },
{ S( 2, -8), S( 4, -6), S( 11, 9), S( 18, 5), S( 16, 16), S( 21, 6), S( 9, -6), S( -3,-18) },
{ S( -9, -9), S(-15, -7), S( 11,-10), S( 15, 5), S( 31, 2), S( 23, 3), S( 6, -8), S(-20, -5) },
{ S( -3, 7), S(-20, 1), S( 8, -8), S( 19, -2), S( 39,-14), S( 17,-13), S( 2,-11), S( -5, -6) },
{ S( 11, 12), S( -4, 6), S(-11, 2), S( 2, -6), S( 11, -5), S( 0, -4), S(-12, 14), S( 5, 9) },
{ S( 3, 27), S(-11, 18), S( -6, 19), S( 22, 29), S( -8, 30), S( -5, 9), S(-14, 8), S(-11, 14) },
{ S( -7, -1), S( 6,-14), S( -2, 13), S(-11, 22), S( 4, 24), S(-14, 17), S( 10, 7), S( -9, 7) }
};
} // namespace
namespace PSQT
{
Score psq[PIECE_NB][SQUARE_NB];
// PSQT::init() initializes piece-square tables: the white halves of the tables are
// copied from Bonus[] and PBonus[], adding the piece value, then the black halves of
// the tables are initialized by flipping and changing the sign of the white scores.
void init() {
for (Piece pc : {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING})
{
Score score = make_score(PieceValue[MG][pc], PieceValue[EG][pc]);
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
File f = File(edge_distance(file_of(s)));
psq[ pc][s] = score + (type_of(pc) == PAWN ? PBonus[rank_of(s)][file_of(s)]
: Bonus[pc][rank_of(s)][f]);
psq[~pc][flip_rank(s)] = -psq[pc][s];
}
}
}
} // namespace PSQT
} // namespace Stockfish

48
src/score.cpp Normal file
View file

@ -0,0 +1,48 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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 "score.h"
#include <cassert>
#include <cmath>
#include <cstdlib>
#include "uci.h"
namespace Stockfish {
Score::Score(Value v, const Position& pos) {
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
{
score = InternalUnits{UCIEngine::to_cp(v, pos)};
}
else if (std::abs(v) <= VALUE_TB)
{
auto distance = VALUE_TB - std::abs(v);
score = (v > 0) ? Tablebase{distance, true} : Tablebase{-distance, false};
}
else
{
auto distance = VALUE_MATE - std::abs(v);
score = (v > 0) ? Mate{distance} : Mate{-distance};
}
}
}

70
src/score.h Normal file
View file

@ -0,0 +1,70 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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 SCORE_H_INCLUDED
#define SCORE_H_INCLUDED
#include <variant>
#include <utility>
#include "types.h"
namespace Stockfish {
class Position;
class Score {
public:
struct Mate {
int plies;
};
struct Tablebase {
int plies;
bool win;
};
struct InternalUnits {
int value;
};
Score() = default;
Score(Value v, const Position& pos);
template<typename T>
bool is() const {
return std::holds_alternative<T>(score);
}
template<typename T>
T get() const {
return std::get<T>(score);
}
template<typename F>
decltype(auto) visit(F&& f) const {
return std::visit(std::forward<F>(f), score);
}
private:
std::variant<Mate, Tablebase, InternalUnits> score;
};
}
#endif // #ifndef SCORE_H_INCLUDED

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,98 +19,303 @@
#ifndef SEARCH_H_INCLUDED
#define SEARCH_H_INCLUDED
#include <array>
#include <atomic>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "misc.h"
#include "movepick.h"
#include "position.h"
#include "score.h"
#include "syzygy/tbprobe.h"
#include "timeman.h"
#include "types.h"
#include "nnue/nnue_accumulator.h"
namespace Stockfish {
class Position;
namespace Eval::NNUE {
struct Networks;
}
// Different node types, used as a template parameter
enum NodeType {
NonPV,
PV,
Root
};
class TranspositionTable;
class ThreadPool;
class OptionsMap;
namespace Search {
/// Threshold used for countermoves based pruning
constexpr int CounterMovePruneThreshold = 0;
/// 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.
// 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;
PieceToHistory* continuationHistory;
int ply;
Move currentMove;
Move excludedMove;
Move killers[2];
Value staticEval;
Depth depth;
int statScore;
int moveCount;
bool inCheck;
bool ttPv;
bool ttHit;
int doubleExtensions;
Move* pv;
PieceToHistory* continuationHistory;
int ply;
Move currentMove;
Move excludedMove;
Move killers[2];
Value staticEval;
int statScore;
int moveCount;
bool inCheck;
bool ttPv;
bool ttHit;
int cutoffCnt;
};
/// RootMove struct is used for moves at the root of the tree. For each root move
/// we store a score and a PV (really a refutation in the case of moves which
/// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.
// RootMove struct is used for moves at the root of the tree. For each root move
// we store a score and a PV (really a refutation in the case of moves which
// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.
struct RootMove {
explicit RootMove(Move m) : pv(1, m) {}
bool extract_ponder_from_tt(Position& pos);
bool operator==(const Move& m) const { return pv[0] == m; }
bool operator<(const RootMove& m) const { // Sort in descending order
return m.score != score ? m.score < score
: m.previousScore < previousScore;
}
explicit RootMove(Move m) :
pv(1, m) {}
bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos);
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;
}
Value score = -VALUE_INFINITE;
Value previousScore = -VALUE_INFINITE;
Value averageScore = -VALUE_INFINITE;
int selDepth = 0;
int tbRank = 0;
Value tbScore;
std::vector<Move> pv;
uint64_t effort = 0;
Value score = -VALUE_INFINITE;
Value previousScore = -VALUE_INFINITE;
Value averageScore = -VALUE_INFINITE;
Value uciScore = -VALUE_INFINITE;
bool scoreLowerbound = false;
bool scoreUpperbound = false;
int selDepth = 0;
int tbRank = 0;
Value tbScore;
std::vector<Move> pv;
};
typedef std::vector<RootMove> RootMoves;
using RootMoves = std::vector<RootMove>;
/// LimitsType struct stores information sent by GUI about available time to
/// search the current move, maximum depth/time, or if we are in analysis mode.
// LimitsType struct stores information sent by the caller about the analysis required.
struct LimitsType {
LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC
time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
movestogo = depth = mate = perft = infinite = 0;
nodes = 0;
}
// Init explicitly due to broken value-initialization of non POD in MSVC
LimitsType() {
time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
movestogo = depth = mate = perft = infinite = 0;
nodes = 0;
ponderMode = false;
}
bool use_time_management() const {
return time[WHITE] || time[BLACK];
}
bool use_time_management() const { return time[WHITE] || time[BLACK]; }
std::vector<Move> searchmoves;
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
int movestogo, depth, mate, perft, infinite;
int64_t nodes;
std::vector<std::string> searchmoves;
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
int movestogo, depth, mate, perft, infinite;
uint64_t nodes;
bool ponderMode;
Square capSq;
};
extern LimitsType Limits;
void init();
void clear();
// The UCI stores the uci options, thread pool, and transposition table.
// This struct is used to easily forward data to the Search::Worker class.
struct SharedState {
SharedState(const OptionsMap& optionsMap,
ThreadPool& threadPool,
TranspositionTable& transpositionTable,
const Eval::NNUE::Networks& nets) :
options(optionsMap),
threads(threadPool),
tt(transpositionTable),
networks(nets) {}
} // namespace Search
const OptionsMap& options;
ThreadPool& threads;
TranspositionTable& tt;
const Eval::NNUE::Networks& networks;
};
} // namespace Stockfish
class Worker;
#endif // #ifndef SEARCH_H_INCLUDED
// Null Object Pattern, implement a common interface for the SearchManagers.
// A Null Object will be given to non-mainthread workers.
class ISearchManager {
public:
virtual ~ISearchManager() {}
virtual void check_time(Search::Worker&) = 0;
};
struct InfoShort {
int depth;
Score score;
};
struct InfoFull: InfoShort {
int selDepth;
size_t multiPV;
std::string_view wdl;
std::string_view bound;
size_t timeMs;
size_t nodes;
size_t nps;
size_t tbHits;
std::string_view pv;
int hashfull;
};
struct InfoIteration {
int depth;
std::string_view currmove;
size_t currmovenumber;
};
// SearchManager manages the search from the main thread. It is responsible for
// keeping track of the time, and storing data strictly related to the main thread.
class SearchManager: public ISearchManager {
public:
using UpdateShort = std::function<void(const InfoShort&)>;
using UpdateFull = std::function<void(const InfoFull&)>;
using UpdateIter = std::function<void(const InfoIteration&)>;
using UpdateBestmove = std::function<void(std::string_view, std::string_view)>;
struct UpdateContext {
UpdateShort onUpdateNoMoves;
UpdateFull onUpdateFull;
UpdateIter onIter;
UpdateBestmove onBestmove;
};
SearchManager(const UpdateContext& updateContext) :
updates(updateContext) {}
void check_time(Search::Worker& worker) override;
void pv(const Search::Worker& worker,
const ThreadPool& threads,
const TranspositionTable& tt,
Depth depth) const;
Stockfish::TimeManagement tm;
int callsCnt;
std::atomic_bool ponder;
std::array<Value, 4> iterValue;
double previousTimeReduction;
Value bestPreviousScore;
Value bestPreviousAverageScore;
bool stopOnPonderhit;
size_t id;
const UpdateContext& updates;
};
class NullSearchManager: public ISearchManager {
public:
void check_time(Search::Worker&) override {}
};
// Search::Worker is the class that does the actual search.
// It is instantiated once per thread, and it is responsible for keeping track
// of the search history, and storing data required for the search.
class Worker {
public:
Worker(SharedState&, std::unique_ptr<ISearchManager>, size_t);
// Called at instantiation to initialize Reductions tables
// Reset histories, usually before a new game
void clear();
// Called when the program receives the UCI 'go' command.
// It searches from the root position and outputs the "bestmove".
void start_searching();
bool is_mainthread() const { return thread_idx == 0; }
// Public because they need to be updatable by the stats
CounterMoveHistory counterMoves;
ButterflyHistory mainHistory;
CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2];
PawnHistory pawnHistory;
CorrectionHistory correctionHistory;
private:
void iterative_deepening();
// 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);
// Quiescence search function, which is called by the main search
template<NodeType nodeType>
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = 0);
Depth reduction(bool i, Depth d, int mn, int delta);
// Get a pointer to the search manager, only allowed to be called by the
// main thread.
SearchManager* main_manager() const {
assert(thread_idx == 0);
return static_cast<SearchManager*>(manager.get());
}
TimePoint elapsed() const;
LimitsType limits;
size_t pvIdx, pvLast;
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
int selDepth, nmpMinPly;
Value optimism[COLOR_NB];
Position rootPos;
StateInfo rootState;
RootMoves rootMoves;
Depth rootDepth, completedDepth;
Value rootDelta;
size_t thread_idx;
// Reductions lookup table initialized at startup
std::array<int, MAX_MOVES> reductions; // [depth or moveNumber]
// The main thread has a SearchManager, the others have a NullSearchManager
std::unique_ptr<ISearchManager> manager;
Tablebases::Config tbConfig;
const OptionsMap& options;
ThreadPool& threads;
TranspositionTable& tt;
const Eval::NNUE::Networks& networks;
// Used by NNUE
Eval::NNUE::AccumulatorCaches refreshTable;
friend class Stockfish::ThreadPool;
friend class SearchManager;
};
} // namespace Search
} // namespace Stockfish
#endif // #ifndef SEARCH_H_INCLUDED

View file

@ -1,387 +0,0 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef STOCKFISH_SIMD_H_INCLUDED
#define STOCKFISH_SIMD_H_INCLUDED
#if defined(USE_AVX2)
# include <immintrin.h>
#elif defined(USE_SSE41)
# include <smmintrin.h>
#elif defined(USE_SSSE3)
# include <tmmintrin.h>
#elif defined(USE_SSE2)
# include <emmintrin.h>
#elif defined(USE_MMX)
# include <mmintrin.h>
#elif defined(USE_NEON)
# include <arm_neon.h>
#endif
// The inline asm is only safe for GCC, where it is necessary to get good codegen.
// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101693
// Clang does fine without it.
// Play around here: https://godbolt.org/z/7EWqrYq51
#if (defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER))
#define USE_INLINE_ASM
#endif
// Use either the AVX512 or AVX-VNNI version of the VNNI instructions.
#if defined(USE_AVXVNNI)
#define VNNI_PREFIX "%{vex%} "
#else
#define VNNI_PREFIX ""
#endif
namespace Stockfish::Simd {
#if defined (USE_AVX512)
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
return _mm512_reduce_add_epi32(sum) + bias;
}
/*
Parameters:
sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]]
sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]]
sum2 = [zmm2.i128[0], zmm2.i128[1], zmm2.i128[2], zmm2.i128[3]]
sum3 = [zmm3.i128[0], zmm3.i128[1], zmm3.i128[2], zmm3.i128[3]]
Returns:
ret = [
reduce_add_epi32(zmm0.i128[0]), reduce_add_epi32(zmm1.i128[0]), reduce_add_epi32(zmm2.i128[0]), reduce_add_epi32(zmm3.i128[0]),
reduce_add_epi32(zmm0.i128[1]), reduce_add_epi32(zmm1.i128[1]), reduce_add_epi32(zmm2.i128[1]), reduce_add_epi32(zmm3.i128[1]),
reduce_add_epi32(zmm0.i128[2]), reduce_add_epi32(zmm1.i128[2]), reduce_add_epi32(zmm2.i128[2]), reduce_add_epi32(zmm3.i128[2]),
reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3])
]
*/
[[maybe_unused]] static __m512i m512_hadd128x16_interleave(
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
__m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1);
__m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1);
__m512i sum23a = _mm512_unpacklo_epi32(sum2, sum3);
__m512i sum23b = _mm512_unpackhi_epi32(sum2, sum3);
__m512i sum01 = _mm512_add_epi32(sum01a, sum01b);
__m512i sum23 = _mm512_add_epi32(sum23a, sum23b);
__m512i sum0123a = _mm512_unpacklo_epi64(sum01, sum23);
__m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23);
return _mm512_add_epi32(sum0123a, sum0123b);
}
[[maybe_unused]] static __m128i m512_haddx4(
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3,
__m128i bias) {
__m512i sum = m512_hadd128x16_interleave(sum0, sum1, sum2, sum3);
__m256i sum256lo = _mm512_castsi512_si256(sum);
__m256i sum256hi = _mm512_extracti64x4_epi64(sum, 1);
sum256lo = _mm256_add_epi32(sum256lo, sum256hi);
__m128i sum128lo = _mm256_castsi256_si128(sum256lo);
__m128i sum128hi = _mm256_extracti128_si256(sum256lo, 1);
return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias);
}
[[maybe_unused]] static void m512_add_dpbusd_epi32(
__m512i& acc,
__m512i a,
__m512i b) {
# if defined (USE_VNNI)
# if defined (USE_INLINE_ASM)
asm(
"vpdpbusd %[b], %[a], %[acc]\n\t"
: [acc]"+v"(acc)
: [a]"v"(a), [b]"vm"(b)
);
# else
acc = _mm512_dpbusd_epi32(acc, a, b);
# endif
# else
# if defined (USE_INLINE_ASM)
__m512i tmp = _mm512_maddubs_epi16(a, b);
asm(
"vpmaddwd %[tmp], %[ones], %[tmp]\n\t"
"vpaddd %[acc], %[tmp], %[acc]\n\t"
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
: [ones]"v"(_mm512_set1_epi16(1))
);
# else
__m512i product0 = _mm512_maddubs_epi16(a, b);
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
acc = _mm512_add_epi32(acc, product0);
# endif
# endif
}
[[maybe_unused]] static void m512_add_dpbusd_epi32x2(
__m512i& acc,
__m512i a0, __m512i b0,
__m512i a1, __m512i b1) {
# if defined (USE_VNNI)
# if defined (USE_INLINE_ASM)
asm(
"vpdpbusd %[b0], %[a0], %[acc]\n\t"
"vpdpbusd %[b1], %[a1], %[acc]\n\t"
: [acc]"+v"(acc)
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
);
# else
acc = _mm512_dpbusd_epi32(acc, a0, b0);
acc = _mm512_dpbusd_epi32(acc, a1, b1);
# endif
# else
# if defined (USE_INLINE_ASM)
__m512i tmp0 = _mm512_maddubs_epi16(a0, b0);
__m512i tmp1 = _mm512_maddubs_epi16(a1, b1);
asm(
"vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
: [tmp1]"v"(tmp1), [ones]"v"(_mm512_set1_epi16(1))
);
# else
__m512i product0 = _mm512_maddubs_epi16(a0, b0);
__m512i product1 = _mm512_maddubs_epi16(a1, b1);
product0 = _mm512_adds_epi16(product0, product1);
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
acc = _mm512_add_epi32(acc, product0);
# endif
# endif
}
#endif
#if defined (USE_AVX2)
[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
__m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
return _mm_cvtsi128_si32(sum128) + bias;
}
[[maybe_unused]] static __m128i m256_haddx4(
__m256i sum0, __m256i sum1, __m256i sum2, __m256i sum3,
__m128i bias) {
sum0 = _mm256_hadd_epi32(sum0, sum1);
sum2 = _mm256_hadd_epi32(sum2, sum3);
sum0 = _mm256_hadd_epi32(sum0, sum2);
__m128i sum128lo = _mm256_castsi256_si128(sum0);
__m128i sum128hi = _mm256_extracti128_si256(sum0, 1);
return _mm_add_epi32(_mm_add_epi32(sum128lo, sum128hi), bias);
}
[[maybe_unused]] static void m256_add_dpbusd_epi32(
__m256i& acc,
__m256i a,
__m256i b) {
# if defined (USE_VNNI)
# if defined (USE_INLINE_ASM)
asm(
VNNI_PREFIX "vpdpbusd %[b], %[a], %[acc]\n\t"
: [acc]"+v"(acc)
: [a]"v"(a), [b]"vm"(b)
);
# else
acc = _mm256_dpbusd_epi32(acc, a, b);
# endif
# else
# if defined (USE_INLINE_ASM)
__m256i tmp = _mm256_maddubs_epi16(a, b);
asm(
"vpmaddwd %[tmp], %[ones], %[tmp]\n\t"
"vpaddd %[acc], %[tmp], %[acc]\n\t"
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
: [ones]"v"(_mm256_set1_epi16(1))
);
# else
__m256i product0 = _mm256_maddubs_epi16(a, b);
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
acc = _mm256_add_epi32(acc, product0);
# endif
# endif
}
[[maybe_unused]] static void m256_add_dpbusd_epi32x2(
__m256i& acc,
__m256i a0, __m256i b0,
__m256i a1, __m256i b1) {
# if defined (USE_VNNI)
# if defined (USE_INLINE_ASM)
asm(
VNNI_PREFIX "vpdpbusd %[b0], %[a0], %[acc]\n\t"
VNNI_PREFIX "vpdpbusd %[b1], %[a1], %[acc]\n\t"
: [acc]"+v"(acc)
: [a0]"v"(a0), [b0]"vm"(b0), [a1]"v"(a1), [b1]"vm"(b1)
);
# else
acc = _mm256_dpbusd_epi32(acc, a0, b0);
acc = _mm256_dpbusd_epi32(acc, a1, b1);
# endif
# else
# if defined (USE_INLINE_ASM)
__m256i tmp0 = _mm256_maddubs_epi16(a0, b0);
__m256i tmp1 = _mm256_maddubs_epi16(a1, b1);
asm(
"vpaddsw %[tmp0], %[tmp1], %[tmp0]\n\t"
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
"vpaddd %[acc], %[tmp0], %[acc]\n\t"
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
: [tmp1]"v"(tmp1), [ones]"v"(_mm256_set1_epi16(1))
);
# else
__m256i product0 = _mm256_maddubs_epi16(a0, b0);
__m256i product1 = _mm256_maddubs_epi16(a1, b1);
product0 = _mm256_adds_epi16(product0, product1);
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
acc = _mm256_add_epi32(acc, product0);
# endif
# endif
}
#endif
#if defined (USE_SSSE3)
[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
return _mm_cvtsi128_si32(sum) + bias;
}
[[maybe_unused]] static __m128i m128_haddx4(
__m128i sum0, __m128i sum1, __m128i sum2, __m128i sum3,
__m128i bias) {
sum0 = _mm_hadd_epi32(sum0, sum1);
sum2 = _mm_hadd_epi32(sum2, sum3);
sum0 = _mm_hadd_epi32(sum0, sum2);
return _mm_add_epi32(sum0, bias);
}
[[maybe_unused]] static void m128_add_dpbusd_epi32(
__m128i& acc,
__m128i a,
__m128i b) {
# if defined (USE_INLINE_ASM)
__m128i tmp = _mm_maddubs_epi16(a, b);
asm(
"pmaddwd %[ones], %[tmp]\n\t"
"paddd %[tmp], %[acc]\n\t"
: [acc]"+v"(acc), [tmp]"+&v"(tmp)
: [ones]"v"(_mm_set1_epi16(1))
);
# else
__m128i product0 = _mm_maddubs_epi16(a, b);
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
acc = _mm_add_epi32(acc, product0);
# endif
}
[[maybe_unused]] static void m128_add_dpbusd_epi32x2(
__m128i& acc,
__m128i a0, __m128i b0,
__m128i a1, __m128i b1) {
# if defined (USE_INLINE_ASM)
__m128i tmp0 = _mm_maddubs_epi16(a0, b0);
__m128i tmp1 = _mm_maddubs_epi16(a1, b1);
asm(
"paddsw %[tmp1], %[tmp0]\n\t"
"pmaddwd %[ones], %[tmp0]\n\t"
"paddd %[tmp0], %[acc]\n\t"
: [acc]"+v"(acc), [tmp0]"+&v"(tmp0)
: [tmp1]"v"(tmp1), [ones]"v"(_mm_set1_epi16(1))
);
# else
__m128i product0 = _mm_maddubs_epi16(a0, b0);
__m128i product1 = _mm_maddubs_epi16(a1, b1);
product0 = _mm_adds_epi16(product0, product1);
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
acc = _mm_add_epi32(acc, product0);
# endif
}
#endif
#if defined (USE_NEON)
[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
# if USE_NEON >= 8
return vaddvq_s32(s);
# else
return s[0] + s[1] + s[2] + s[3];
# endif
}
[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {
return neon_m128_reduce_add_epi32(sum) + bias;
}
[[maybe_unused]] static int32x4_t neon_m128_haddx4(
int32x4_t sum0, int32x4_t sum1, int32x4_t sum2, int32x4_t sum3,
int32x4_t bias) {
int32x4_t hsums {
neon_m128_reduce_add_epi32(sum0),
neon_m128_reduce_add_epi32(sum1),
neon_m128_reduce_add_epi32(sum2),
neon_m128_reduce_add_epi32(sum3)
};
return vaddq_s32(hsums, bias);
}
[[maybe_unused]] static void neon_m128_add_dpbusd_epi32x2(
int32x4_t& acc,
int8x8_t a0, int8x8_t b0,
int8x8_t a1, int8x8_t b1) {
int16x8_t product = vmull_s8(a0, b0);
product = vmlal_s8(product, a1, b1);
acc = vpadalq_s16(acc, product);
}
#endif
}
#endif // STOCKFISH_SIMD_H_INCLUDED

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,60 +19,57 @@
#ifndef TBPROBE_H
#define TBPROBE_H
#include <ostream>
#include <string>
#include <vector>
#include "../search.h"
namespace Stockfish {
class Position;
class OptionsMap;
using Depth = int;
namespace Search {
struct RootMove;
using RootMoves = std::vector<RootMove>;
}
}
namespace Stockfish::Tablebases {
enum WDLScore {
WDLLoss = -2, // Loss
WDLBlessedLoss = -1, // Loss, but draw under 50-move rule
WDLDraw = 0, // Draw
WDLCursedWin = 1, // Win, but draw under 50-move rule
WDLWin = 2, // Win
struct Config {
int cardinality = 0;
bool rootInTB = false;
bool useRule50 = false;
Depth probeDepth = 0;
};
WDLScoreNone = -1000
enum WDLScore {
WDLLoss = -2, // Loss
WDLBlessedLoss = -1, // Loss, but draw under 50-move rule
WDLDraw = 0, // Draw
WDLCursedWin = 1, // Win, but draw under 50-move rule
WDLWin = 2, // Win
};
// Possible states after a probing operation
enum ProbeState {
FAIL = 0, // Probe failed (missing file table)
OK = 1, // Probe successful
CHANGE_STM = -1, // DTZ should check the other side
ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
FAIL = 0, // Probe failed (missing file table)
OK = 1, // Probe successful
CHANGE_STM = -1, // DTZ should check the other side
ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move)
};
extern int MaxCardinality;
void init(const std::string& paths);
void init(const std::string& paths);
WDLScore probe_wdl(Position& pos, ProbeState* result);
int probe_dtz(Position& pos, ProbeState* result);
bool root_probe(Position& pos, Search::RootMoves& rootMoves);
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves);
void rank_root_moves(Position& pos, Search::RootMoves& rootMoves);
int probe_dtz(Position& pos, ProbeState* result);
bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50);
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50);
Config rank_root_moves(const OptionsMap& options, Position& pos, Search::RootMoves& rootMoves);
inline std::ostream& operator<<(std::ostream& os, const WDLScore v) {
os << (v == WDLLoss ? "Loss" :
v == WDLBlessedLoss ? "Blessed loss" :
v == WDLDraw ? "Draw" :
v == WDLCursedWin ? "Cursed win" :
v == WDLWin ? "Win" : "None");
return os;
}
inline std::ostream& operator<<(std::ostream& os, const ProbeState v) {
os << (v == FAIL ? "Failed" :
v == OK ? "Success" :
v == CHANGE_STM ? "Probed opponent side" :
v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None");
return os;
}
} // namespace Stockfish::Tablebases
} // namespace Stockfish::Tablebases
#endif

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,227 +16,280 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#include "thread.h"
#include <algorithm> // For std::count
#include <algorithm>
#include <cassert>
#include <deque>
#include <memory>
#include <unordered_map>
#include <utility>
#include <string>
#include "misc.h"
#include "movegen.h"
#include "search.h"
#include "thread.h"
#include "uci.h"
#include "syzygy/tbprobe.h"
#include "timeman.h"
#include "tt.h"
#include "types.h"
#include "ucioption.h"
#include "uci.h"
namespace Stockfish {
ThreadPool Threads; // Global object
// Constructor launches the thread and waits until it goes to sleep
// 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) :
worker(std::make_unique<Search::Worker>(sharedState, std::move(sm), n)),
idx(n),
nthreads(sharedState.options["Threads"]),
stdThread(&Thread::idle_loop, this) {
/// Thread constructor launches the thread and waits until it goes to sleep
/// in idle_loop(). Note that 'searching' and 'exit' should be already set.
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) {
wait_for_search_finished();
wait_for_search_finished();
}
/// Thread destructor wakes up the thread in idle_loop() and waits
/// for its termination. Thread should be already waiting.
// Destructor wakes up the thread in idle_loop() and waits
// for its termination. Thread should be already waiting.
Thread::~Thread() {
assert(!searching);
assert(!searching);
exit = true;
start_searching();
stdThread.join();
exit = true;
start_searching();
stdThread.join();
}
/// Thread::clear() reset histories, usually before a new game
void Thread::clear() {
counterMoves.fill(MOVE_NONE);
mainHistory.fill(0);
captureHistory.fill(0);
for (bool inCheck : { false, true })
for (StatsType c : { NoCaptures, Captures })
{
for (auto& to : continuationHistory[inCheck][c])
for (auto& h : to)
h->fill(-71);
continuationHistory[inCheck][c][NO_PIECE][0]->fill(Search::CounterMovePruneThreshold - 1);
}
}
/// Thread::start_searching() wakes up the thread that will start the search
// Wakes up the thread that will start the search
void Thread::start_searching() {
std::lock_guard<std::mutex> lk(mutex);
searching = true;
cv.notify_one(); // Wake up the thread in idle_loop()
mutex.lock();
searching = true;
mutex.unlock(); // Unlock before notifying saves a few CPU-cycles
cv.notify_one(); // Wake up the thread in idle_loop()
}
/// Thread::wait_for_search_finished() blocks on the condition variable
/// until the thread has finished searching.
// Blocks on the condition variable
// until the thread has finished searching.
void Thread::wait_for_search_finished() {
std::unique_lock<std::mutex> lk(mutex);
cv.wait(lk, [&]{ return !searching; });
std::unique_lock<std::mutex> lk(mutex);
cv.wait(lk, [&] { return !searching; });
}
/// Thread::idle_loop() is where the thread is parked, blocked on the
/// condition variable, when it has no work to do.
// Thread gets parked here, blocked on the
// condition variable, when it has no work to do.
void Thread::idle_loop() {
// If OS already scheduled us on a different group than 0 then don't overwrite
// the choice, eventually we are one of many one-threaded processes running on
// some Windows NUMA hardware, for instance in fishtest. To make it simple,
// just check if running threads are below a threshold, in this case all this
// NUMA machinery is not needed.
if (Options["Threads"] > 8)
WinProcGroup::bindThisThread(idx);
// If OS already scheduled us on a different group than 0 then don't overwrite
// the choice, eventually we are one of many one-threaded processes running on
// some Windows NUMA hardware, for instance in fishtest. To make it simple,
// just check if running threads are below a threshold, in this case, all this
// NUMA machinery is not needed.
if (nthreads > 8)
WinProcGroup::bind_this_thread(idx);
while (true)
{
std::unique_lock<std::mutex> lk(mutex);
searching = false;
cv.notify_one(); // Wake up anyone waiting for search finished
cv.wait(lk, [&]{ return searching; });
while (true)
{
std::unique_lock<std::mutex> lk(mutex);
searching = false;
cv.notify_one(); // Wake up anyone waiting for search finished
cv.wait(lk, [&] { return searching; });
if (exit)
return;
if (exit)
return;
lk.unlock();
lk.unlock();
search();
}
worker->start_searching();
}
}
/// ThreadPool::set() creates/destroys threads to match the requested number.
/// Created and launched threads will immediately go to sleep in idle_loop.
/// Upon resizing, threads are recreated to allow for binding if necessary.
Search::SearchManager* ThreadPool::main_manager() {
return static_cast<Search::SearchManager*>(main_thread()->worker.get()->manager.get());
}
void ThreadPool::set(size_t requested) {
uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); }
uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); }
if (size() > 0) // destroy any existing thread(s)
{
main()->wait_for_search_finished();
// Creates/destroys threads to match the requested number.
// Created and launched threads will immediately go to sleep in idle_loop.
// Upon resizing, threads are recreated to allow for binding if necessary.
void ThreadPool::set(Search::SharedState sharedState,
const Search::SearchManager::UpdateContext& updateContext) {
while (size() > 0)
delete back(), pop_back();
}
if (threads.size() > 0) // destroy any existing thread(s)
{
main_thread()->wait_for_search_finished();
if (requested > 0) // create new thread(s)
{
push_back(new MainThread(0));
while (threads.size() > 0)
delete threads.back(), threads.pop_back();
}
while (size() < requested)
push_back(new Thread(size()));
clear();
const size_t requested = sharedState.options["Threads"];
// Reallocate the hash with the new threadpool size
TT.resize(size_t(Options["Hash"]));
if (requested > 0) // create new thread(s)
{
auto manager = std::make_unique<Search::SearchManager>(updateContext);
threads.push_back(new Thread(sharedState, std::move(manager), 0));
// Init thread number dependent search params.
Search::init();
}
while (threads.size() < requested)
{
auto null_manager = std::make_unique<Search::NullSearchManager>();
threads.push_back(new Thread(sharedState, std::move(null_manager), threads.size()));
}
clear();
main_thread()->wait_for_search_finished();
// Reallocate the hash with the new threadpool size
sharedState.tt.resize(sharedState.options["Hash"], requested);
}
}
/// ThreadPool::clear() sets threadPool data to initial values
// Sets threadPool data to initial values
void ThreadPool::clear() {
for (Thread* th : *this)
th->clear();
for (Thread* th : threads)
th->worker->clear();
main()->callsCnt = 0;
main()->bestPreviousScore = VALUE_INFINITE;
main()->bestPreviousAverageScore = VALUE_INFINITE;
main()->previousTimeReduction = 1.0;
if (threads.size() == 0)
return;
main_manager()->callsCnt = 0;
main_manager()->bestPreviousScore = VALUE_INFINITE;
main_manager()->bestPreviousAverageScore = VALUE_INFINITE;
main_manager()->previousTimeReduction = 1.0;
main_manager()->tm.clear();
}
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
/// returns immediately. Main thread will wake up other threads and start the search.
// Wakes up main thread waiting in idle_loop() and
// returns immediately. Main thread will wake up other threads and start the search.
void ThreadPool::start_thinking(const OptionsMap& options,
Position& pos,
StateListPtr& states,
Search::LimitsType limits) {
void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
const Search::LimitsType& limits, bool ponderMode) {
main_thread()->wait_for_search_finished();
main()->wait_for_search_finished();
main_manager()->stopOnPonderhit = stop = abortedSearch = false;
main_manager()->ponder = limits.ponderMode;
main()->stopOnPonderhit = stop = false;
increaseDepth = true;
main()->ponder = ponderMode;
Search::Limits = limits;
Search::RootMoves rootMoves;
increaseDepth = true;
for (const auto& m : MoveList<LEGAL>(pos))
if ( limits.searchmoves.empty()
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
rootMoves.emplace_back(m);
Search::RootMoves rootMoves;
const auto legalmoves = MoveList<LEGAL>(pos);
if (!rootMoves.empty())
Tablebases::rank_root_moves(pos, rootMoves);
for (const auto& uciMove : limits.searchmoves)
{
auto move = UCIEngine::to_move(pos, uciMove);
// After ownership transfer 'states' becomes empty, so if we stop the search
// and call 'go' again without setting a new position states.get() == NULL.
assert(states.get() || setupStates.get());
if (std::find(legalmoves.begin(), legalmoves.end(), move) != legalmoves.end())
rootMoves.emplace_back(move);
}
if (states.get())
setupStates = std::move(states); // Ownership transfer, states is now empty
if (rootMoves.empty())
for (const auto& m : legalmoves)
rootMoves.emplace_back(m);
// We use Position::set() to set root position across threads. But there are
// some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot
// be deduced from a fen string, so set() clears them and they are set from
// setupStates->back() later. The rootState is per thread, earlier states are shared
// since they are read-only.
for (Thread* th : *this)
{
th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
th->rootDepth = th->completedDepth = 0;
th->rootMoves = rootMoves;
th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
th->rootState = setupStates->back();
}
Tablebases::Config tbConfig = Tablebases::rank_root_moves(options, pos, rootMoves);
main()->start_searching();
// After ownership transfer 'states' becomes empty, so if we stop the search
// and call 'go' again without setting a new position states.get() == nullptr.
assert(states.get() || setupStates.get());
if (states.get())
setupStates = std::move(states); // Ownership transfer, states is now empty
// We use Position::set() to set root position across threads. But there are
// some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot
// be deduced from a fen string, so set() clears them and they are set from
// setupStates->back() later. The rootState is per thread, earlier states are shared
// since they are read-only.
for (Thread* th : threads)
{
th->worker->limits = limits;
th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly =
th->worker->bestMoveChanges = 0;
th->worker->rootDepth = th->worker->completedDepth = 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;
}
main_thread()->start_searching();
}
Thread* ThreadPool::get_best_thread() const {
Thread* bestThread = front();
std::map<Move, int64_t> votes;
Value minScore = VALUE_NONE;
Thread* bestThread = threads.front();
Value minScore = VALUE_NONE;
// Find minimum score of all threads
for (Thread* th: *this)
minScore = std::min(minScore, th->rootMoves[0].score);
std::unordered_map<Move, int64_t, Move::MoveHash> votes(
2 * std::min(size(), bestThread->worker->rootMoves.size()));
// Find the minimum score of all threads
for (Thread* th : threads)
minScore = std::min(minScore, th->worker->rootMoves[0].score);
// Vote according to score and depth, and select the best thread
for (Thread* th : *this)
{
votes[th->rootMoves[0].pv[0]] +=
(th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
auto thread_voting_value = [minScore](Thread* th) {
return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth);
};
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
for (Thread* th : threads)
votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th);
for (Thread* th : threads)
{
const auto bestThreadScore = bestThread->worker->rootMoves[0].score;
const auto newThreadScore = th->worker->rootMoves[0].score;
const auto& bestThreadPV = bestThread->worker->rootMoves[0].pv;
const auto& newThreadPV = th->worker->rootMoves[0].pv;
const auto bestThreadMoveVote = votes[bestThreadPV[0]];
const auto newThreadMoveVote = votes[newThreadPV[0]];
const bool bestThreadInProvenWin = bestThreadScore >= VALUE_TB_WIN_IN_MAX_PLY;
const bool newThreadInProvenWin = newThreadScore >= VALUE_TB_WIN_IN_MAX_PLY;
const bool bestThreadInProvenLoss =
bestThreadScore != -VALUE_INFINITE && bestThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY;
const bool newThreadInProvenLoss =
newThreadScore != -VALUE_INFINITE && newThreadScore <= VALUE_TB_LOSS_IN_MAX_PLY;
// Note that we make sure not to pick a thread with truncated-PV for better viewer experience.
const bool betterVotingValue =
thread_voting_value(th) * int(newThreadPV.size() > 2)
> thread_voting_value(bestThread) * int(bestThreadPV.size() > 2);
if (bestThreadInProvenWin)
{
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
// Make sure we pick the shortest mate / TB conversion
if (newThreadScore > bestThreadScore)
bestThread = th;
}
else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|| ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
&& votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]))
else if (bestThreadInProvenLoss)
{
// Make sure we pick the shortest mated / TB conversion
if (newThreadInProvenLoss && newThreadScore < bestThreadScore)
bestThread = th;
}
else if (newThreadInProvenWin || newThreadInProvenLoss
|| (newThreadScore > VALUE_TB_LOSS_IN_MAX_PLY
&& (newThreadMoveVote > bestThreadMoveVote
|| (newThreadMoveVote == bestThreadMoveVote && betterVotingValue))))
bestThread = th;
}
@ -244,23 +297,23 @@ Thread* ThreadPool::get_best_thread() const {
}
/// Start non-main threads
// Start non-main threads
// Will be invoked by main thread after it has started searching
void ThreadPool::start_searching() {
for (Thread* th : *this)
if (th != front())
for (Thread* th : threads)
if (th != threads.front())
th->start_searching();
}
/// Wait for non-main threads
// Wait for non-main threads
void ThreadPool::wait_for_search_finished() const {
for (Thread* th : *this)
if (th != front())
for (Thread* th : threads)
if (th != threads.front())
th->wait_for_search_finished();
}
} // namespace Stockfish
} // namespace Stockfish

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,116 +21,99 @@
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
#include "material.h"
#include "movepick.h"
#include "pawns.h"
#include "position.h"
#include "search.h"
#include "thread_win32_osx.h"
namespace Stockfish {
/// Thread class keeps together all the thread-related stuff. We use
/// per-thread pawn and material hash tables so that once we get a
/// pointer to an entry its life time is unlimited and we don't have
/// to care about someone changing the entry under our feet.
class OptionsMap;
using Value = int;
// Abstraction of a thread. It contains a pointer to the worker and a native thread.
// After construction, the native thread is started with idle_loop()
// waiting for a signal to start searching.
// When the signal is received, the thread starts searching and when
// the search is finished, it goes back to idle_loop() waiting for a new signal.
class Thread {
public:
Thread(Search::SharedState&, std::unique_ptr<Search::ISearchManager>, size_t);
virtual ~Thread();
std::mutex mutex;
std::condition_variable cv;
size_t idx;
bool exit = false, searching = true; // Set before starting std::thread
NativeThread stdThread;
void idle_loop();
void start_searching();
void wait_for_search_finished();
size_t id() const { return idx; }
public:
explicit Thread(size_t);
virtual ~Thread();
virtual void search();
void clear();
void idle_loop();
void start_searching();
void wait_for_search_finished();
size_t id() const { return idx; }
std::unique_ptr<Search::Worker> worker;
Pawns::Table pawnsTable;
Material::Table materialTable;
size_t pvIdx, pvLast;
RunningAverage complexityAverage;
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
int selDepth, nmpMinPly;
Color nmpColor;
Value bestValue, optimism[COLOR_NB];
Position rootPos;
StateInfo rootState;
Search::RootMoves rootMoves;
Depth rootDepth, completedDepth;
Value rootDelta;
CounterMoveHistory counterMoves;
ButterflyHistory mainHistory;
CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2];
Score trend;
private:
std::mutex mutex;
std::condition_variable cv;
size_t idx, nthreads;
bool exit = false, searching = true; // Set before starting std::thread
NativeThread stdThread;
};
/// MainThread is a derived class specific for main thread
// ThreadPool struct handles all the threads-related stuff like init, starting,
// parking and, most importantly, launching a thread. All the access to threads
// is done through this class.
class ThreadPool {
struct MainThread : public Thread {
public:
~ThreadPool() {
// destroy any existing thread(s)
if (threads.size() > 0)
{
main_thread()->wait_for_search_finished();
using Thread::Thread;
while (threads.size() > 0)
delete threads.back(), threads.pop_back();
}
}
void search() override;
void check_time();
void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType);
void clear();
void set(Search::SharedState, const Search::SearchManager::UpdateContext&);
double previousTimeReduction;
Value bestPreviousScore;
Value bestPreviousAverageScore;
Value iterValue[4];
int callsCnt;
bool stopOnPonderhit;
std::atomic_bool ponder;
Search::SearchManager* main_manager();
Thread* main_thread() const { return threads.front(); }
uint64_t nodes_searched() const;
uint64_t tb_hits() const;
Thread* get_best_thread() const;
void start_searching();
void wait_for_search_finished() const;
std::atomic_bool stop, abortedSearch, increaseDepth;
auto cbegin() const noexcept { return threads.cbegin(); }
auto begin() noexcept { return threads.begin(); }
auto end() noexcept { return threads.end(); }
auto cend() const noexcept { return threads.cend(); }
auto size() const noexcept { return threads.size(); }
auto empty() const noexcept { return threads.empty(); }
private:
StateListPtr setupStates;
std::vector<Thread*> threads;
uint64_t accumulate(std::atomic<uint64_t> Search::Worker::*member) const {
uint64_t sum = 0;
for (Thread* th : threads)
sum += (th->worker.get()->*member).load(std::memory_order_relaxed);
return sum;
}
};
} // namespace Stockfish
/// ThreadPool struct handles all the threads-related stuff like init, starting,
/// parking and, most importantly, launching a thread. All the access to threads
/// is done through this class.
struct ThreadPool : public std::vector<Thread*> {
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
void clear();
void set(size_t);
MainThread* main() const { return static_cast<MainThread*>(front()); }
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
Thread* get_best_thread() const;
void start_searching();
void wait_for_search_finished() const;
std::atomic_bool stop, increaseDepth;
private:
StateListPtr setupStates;
uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {
uint64_t sum = 0;
for (Thread* th : *this)
sum += (th->*member).load(std::memory_order_relaxed);
return sum;
}
};
extern ThreadPool Threads;
} // namespace Stockfish
#endif // #ifndef THREAD_H_INCLUDED
#endif // #ifndef THREAD_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -21,54 +21,58 @@
#include <thread>
/// 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
/// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE.
/// The implementation calls pthread_create() with the stack size parameter
/// equal to the linux 8MB default, on platforms that support it.
// 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
// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE.
// 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 <pthread.h>
#include <functional>
namespace Stockfish {
static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
template <class T, class P = std::pair<T*, void(T::*)()>>
void* start_routine(void* ptr)
{
P* p = reinterpret_cast<P*>(ptr);
(p->first->*(p->second))(); // Call member function pointer
delete p;
return NULL;
}
class NativeThread {
pthread_t thread;
pthread_t thread;
static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024;
public:
template<class T, class P = std::pair<T*, void(T::*)()>>
explicit NativeThread(void(T::*fun)(), T* obj) {
pthread_attr_t attr_storage, *attr = &attr_storage;
pthread_attr_init(attr);
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
}
void join() { pthread_join(thread, NULL); }
public:
template<class Function, class... Args>
explicit NativeThread(Function&& fun, Args&&... args) {
auto func = new std::function<void()>(
std::bind(std::forward<Function>(fun), std::forward<Args>(args)...));
pthread_attr_t attr_storage, *attr = &attr_storage;
pthread_attr_init(attr);
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
auto start_routine = [](void* ptr) -> void* {
auto f = reinterpret_cast<std::function<void()>*>(ptr);
// Call the function
(*f)();
delete f;
return nullptr;
};
pthread_create(&thread, attr, start_routine, func);
}
void join() { pthread_join(thread, nullptr); }
};
} // namespace Stockfish
} // namespace Stockfish
#else // Default case: use STL classes
#else // Default case: use STL classes
namespace Stockfish {
typedef std::thread NativeThread;
using NativeThread = std::thread;
} // namespace Stockfish
} // namespace Stockfish
#endif
#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,90 +16,114 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "timeman.h"
#include <algorithm>
#include <cfloat>
#include <cassert>
#include <cmath>
#include <cstdint>
#include "search.h"
#include "timeman.h"
#include "uci.h"
#include "ucioption.h"
namespace Stockfish {
TimeManagement Time; // Our global time management object
TimePoint TimeManagement::optimum() const { return optimumTime; }
TimePoint TimeManagement::maximum() const { return maximumTime; }
/// TimeManagement::init() is called at the beginning of the search and calculates
/// the bounds of time allowed for the current game ply. We currently support:
// 1) x basetime (+ z increment)
// 2) x moves in y seconds (+ z increment)
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
TimePoint moveOverhead = TimePoint(Options["Move Overhead"]);
TimePoint slowMover = TimePoint(Options["Slow Mover"]);
TimePoint npmsec = TimePoint(Options["nodestime"]);
// optScale is a percentage of available time to use for the current move.
// maxScale is a multiplier applied to optimumTime.
double optScale, maxScale;
// If we have to play in 'nodes as time' mode, then convert from time
// to nodes, and use resulting values in time management formulas.
// WARNING: to avoid time losses, the given npmsec (nodes per millisecond)
// must be much lower than the real engine speed.
if (npmsec)
{
if (!availableNodes) // Only once at game start
availableNodes = npmsec * limits.time[us]; // Time is in msec
// Convert from milliseconds to nodes
limits.time[us] = TimePoint(availableNodes);
limits.inc[us] *= npmsec;
limits.npmsec = npmsec;
}
startTime = limits.startTime;
// Maximum move horizon of 50 moves
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
// 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] * (mtg - 1) - moveOverhead * (2 + mtg));
// Use extra time with larger increments
double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12);
// A user may scale time usage by setting UCI option "Slow Mover"
// Default is 100 and changing this value will probably lose elo.
timeLeft = slowMover * timeLeft / 100;
// x basetime (+ z increment)
// If there is a healthy increment, timeLeft can exceed actual available
// game time for the current move, so also cap to 20% of available game time.
if (limits.movestogo == 0)
{
optScale = std::min(0.0084 + std::pow(ply + 3.0, 0.5) * 0.0042,
0.2 * limits.time[us] / double(timeLeft))
* optExtra;
maxScale = std::min(7.0, 4.0 + ply / 12.0);
}
// x moves in y seconds (+ z increment)
else
{
optScale = std::min((0.88 + ply / 116.4) / mtg,
0.88 * limits.time[us] / double(timeLeft));
maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
}
// Never use more than 80% of the available time for this move
optimumTime = TimePoint(optScale * timeLeft);
maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime));
if (Options["Ponder"])
optimumTime += optimumTime / 4;
void TimeManagement::clear() {
availableNodes = 0; // When in 'nodes as time' mode
}
} // namespace Stockfish
void TimeManagement::advance_nodes_time(std::int64_t nodes) {
assert(useNodesTime);
availableNodes += nodes;
}
// Called at the beginning of the search and calculates
// the bounds of time allowed for the current game ply. We currently support:
// 1) x basetime (+ z increment)
// 2) x moves in y seconds (+ z increment)
void TimeManagement::init(Search::LimitsType& limits,
Color us,
int ply,
const OptionsMap& options) {
// If we have no time, no need to initialize TM, except for the start time,
// which is used by movetime.
startTime = limits.startTime;
if (limits.time[us] == 0)
return;
TimePoint moveOverhead = TimePoint(options["Move Overhead"]);
TimePoint npmsec = TimePoint(options["nodestime"]);
// optScale is a percentage of available time to use for the current move.
// maxScale is a multiplier applied to optimumTime.
double optScale, maxScale;
// If we have to play in 'nodes as time' mode, then convert from time
// to nodes, and use resulting values in time management formulas.
// WARNING: to avoid time losses, the given npmsec (nodes per millisecond)
// must be much lower than the real engine speed.
if (npmsec)
{
useNodesTime = true;
if (!availableNodes) // Only once at game start
availableNodes = npmsec * limits.time[us]; // Time is in msec
// Convert from milliseconds to nodes
limits.time[us] = TimePoint(availableNodes);
limits.inc[us] *= npmsec;
limits.npmsec = npmsec;
}
// Maximum move horizon of 50 moves
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
// if less than one second, gradually reduce mtg
if (limits.time[us] < 1000 && (double(mtg) / limits.time[us] > 0.05))
{
mtg = limits.time[us] * 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] * (mtg - 1)
- moveOverhead * (2 + mtg));
// x basetime (+ z increment)
// If there is a healthy increment, timeLeft can exceed the actual available
// game time for the current move, so also cap to a percentage of available game time.
if (limits.movestogo == 0)
{
// Use extra time with larger increments
double optExtra = limits.inc[us] < 500 ? 1.0 : 1.13;
// Calculate time constants based on current time left.
double optConstant =
std::min(0.00308 + 0.000319 * std::log10(limits.time[us] / 1000.0), 0.00506);
double maxConstant = std::max(3.39 + 3.01 * std::log10(limits.time[us] / 1000.0), 2.93);
optScale = std::min(0.0122 + std::pow(ply + 2.95, 0.462) * optConstant,
0.213 * limits.time[us] / double(timeLeft))
* optExtra;
maxScale = std::min(6.64, maxConstant + ply / 12.0);
}
// x moves in y seconds (+ z increment)
else
{
optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft));
maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
}
// Limit the maximum possible time for this move
optimumTime = TimePoint(optScale * timeLeft);
maximumTime =
TimePoint(std::min(0.825 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
if (options["Ponder"])
optimumTime += optimumTime / 4;
}
} // namespace Stockfish

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,33 +19,44 @@
#ifndef TIMEMAN_H_INCLUDED
#define TIMEMAN_H_INCLUDED
#include <cstdint>
#include "misc.h"
#include "search.h"
#include "thread.h"
#include "types.h"
namespace Stockfish {
/// The TimeManagement class computes the optimal time to think depending on
/// the maximum available time, the game move number and other parameters.
class OptionsMap;
namespace Search {
struct LimitsType;
}
// The TimeManagement class computes the optimal time to think depending on
// the maximum available time, the game move number, and other parameters.
class TimeManagement {
public:
void init(Search::LimitsType& limits, Color us, int ply);
TimePoint optimum() const { return optimumTime; }
TimePoint maximum() const { return maximumTime; }
TimePoint elapsed() const { return Search::Limits.npmsec ?
TimePoint(Threads.nodes_searched()) : now() - startTime; }
public:
void init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options);
int64_t availableNodes; // When in 'nodes as time' mode
TimePoint optimum() const;
TimePoint maximum() const;
template<typename FUNC>
TimePoint elapsed(FUNC nodes) const {
return useNodesTime ? TimePoint(nodes()) : now() - startTime;
}
private:
TimePoint startTime;
TimePoint optimumTime;
TimePoint maximumTime;
void clear();
void advance_nodes_time(std::int64_t nodes);
private:
TimePoint startTime;
TimePoint optimumTime;
TimePoint maximumTime;
std::int64_t availableNodes = 0; // When in 'nodes as time' mode
bool useNodesTime = false; // True if we are in 'nodes as time' mode
};
extern TimeManagement Time;
} // namespace Stockfish
} // namespace Stockfish
#endif // #ifndef TIMEMAN_H_INCLUDED
#endif // #ifndef TIMEMAN_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,147 +16,139 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstring> // For std::memset
#include "tt.h"
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <vector>
#include "bitboard.h"
#include "misc.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
namespace Stockfish {
TranspositionTable TT; // Our global transposition table
// Populates the TTEntry with a new node's data, possibly
// overwriting an old position. The update is not 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) {
/// TTEntry::save() populates the TTEntry with a new node's data, possibly
/// overwriting an old position. Update is not atomic and can be racy.
// Preserve any existing move for the same position
if (m || uint16_t(k) != key16)
move16 = m;
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
// Overwrite less valuable entries (cheapest checks first)
if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4
|| relative_age(generation8))
{
assert(d > DEPTH_OFFSET);
assert(d < 256 + DEPTH_OFFSET);
// Preserve any existing move for the same position
if (m || (uint16_t)k != key16)
move16 = (uint16_t)m;
// Overwrite less valuable entries (cheapest checks first)
if ( b == BOUND_EXACT
|| (uint16_t)k != key16
|| d - DEPTH_OFFSET + 2 * pv > depth8 - 4)
{
assert(d > DEPTH_OFFSET);
assert(d < 256 + DEPTH_OFFSET);
key16 = (uint16_t)k;
depth8 = (uint8_t)(d - DEPTH_OFFSET);
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
value16 = (int16_t)v;
eval16 = (int16_t)ev;
}
key16 = uint16_t(k);
depth8 = uint8_t(d - DEPTH_OFFSET);
genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b);
value16 = int16_t(v);
eval16 = int16_t(ev);
}
}
/// TranspositionTable::resize() sets the size of the transposition table,
/// measured in megabytes. Transposition table consists of a power of 2 number
/// of clusters and each cluster consists of ClusterSize number of TTEntry.
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.
void TranspositionTable::resize(size_t mbSize) {
Threads.main()->wait_for_search_finished();
aligned_large_pages_free(table);
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
if (!table)
{
std::cerr << "Failed to allocate " << mbSize
<< "MB for transposition table." << std::endl;
exit(EXIT_FAILURE);
}
clear();
return (TranspositionTable::GENERATION_CYCLE + generation8 - genBound8)
& TranspositionTable::GENERATION_MASK;
}
/// TranspositionTable::clear() initializes the entire transposition table to zero,
// in a multi-threaded way.
// 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, int threadCount) {
aligned_large_pages_free(table);
void TranspositionTable::clear() {
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
std::vector<std::thread> threads;
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
if (!table)
{
std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl;
exit(EXIT_FAILURE);
}
for (size_t idx = 0; idx < Options["Threads"]; ++idx)
{
threads.emplace_back([this, idx]() {
// Thread binding gives faster search on systems with a first-touch policy
if (Options["Threads"] > 8)
WinProcGroup::bindThisThread(idx);
// Each thread will zero its part of the hash table
const size_t stride = size_t(clusterCount / Options["Threads"]),
start = size_t(stride * idx),
len = idx != Options["Threads"] - 1 ?
stride : clusterCount - start;
std::memset(&table[start], 0, len * sizeof(Cluster));
});
}
for (std::thread& th : threads)
th.join();
clear(threadCount);
}
/// TranspositionTable::probe() looks up the current position in the transposition
/// table. It returns true and a pointer to the TTEntry if the position is found.
/// 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.
// Initializes the entire transposition table to zero,
// in a multi-threaded way.
void TranspositionTable::clear(size_t threadCount) {
std::vector<std::thread> threads;
for (size_t idx = 0; idx < size_t(threadCount); ++idx)
{
threads.emplace_back([this, idx, threadCount]() {
// Thread binding gives faster search on systems with a first-touch policy
if (threadCount > 8)
WinProcGroup::bind_this_thread(idx);
// Each thread will zero its part of the hash table
const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx),
len = idx != size_t(threadCount) - 1 ? stride : clusterCount - start;
std::memset(&table[start], 0, len * sizeof(Cluster));
});
}
for (std::thread& th : threads)
th.join();
}
// Looks up the current position in the transposition
// table. It returns true and a pointer to the TTEntry if the position is found.
// 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.
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
TTEntry* const tte = first_entry(key);
const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster
TTEntry* const tte = first_entry(key);
const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster
for (int i = 0; i < ClusterSize; ++i)
if (tte[i].key16 == key16 || !tte[i].depth8)
{
tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh
for (int i = 0; i < ClusterSize; ++i)
if (tte[i].key16 == key16 || !tte[i].depth8)
return found = bool(tte[i].depth8), &tte[i];
return found = (bool)tte[i].depth8, &tte[i];
}
// Find an entry to be replaced according to the replacement strategy
TTEntry* replace = tte;
for (int i = 1; i < ClusterSize; ++i)
if (replace->depth8 - replace->relative_age(generation8) * 2
> tte[i].depth8 - tte[i].relative_age(generation8) * 2)
replace = &tte[i];
// Find an entry to be replaced according to the replacement strategy
TTEntry* replace = tte;
for (int i = 1; i < ClusterSize; ++i)
// 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.
if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK)
> tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK))
replace = &tte[i];
return found = false, replace;
return found = false, replace;
}
/// TranspositionTable::hashfull() returns an approximation of the hashtable
/// occupation during a search. The hash is x permill full, as per UCI protocol.
// 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.
int TranspositionTable::hashfull() const {
int cnt = 0;
for (int i = 0; i < 1000; ++i)
for (int j = 0; j < ClusterSize; ++j)
cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
int cnt = 0;
for (int i = 0; i < 1000; ++i)
for (int j = 0; j < ClusterSize; ++j)
cnt += table[i].entry[j].depth8
&& (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
return cnt / ClusterSize;
return cnt / ClusterSize;
}
} // namespace Stockfish
} // namespace Stockfish

140
src/tt.h
View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,89 +19,103 @@
#ifndef TT_H_INCLUDED
#define TT_H_INCLUDED
#include <cstddef>
#include <cstdint>
#include "misc.h"
#include "types.h"
namespace Stockfish {
/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
///
/// key 16 bit
/// depth 8 bit
/// generation 5 bit
/// pv node 1 bit
/// bound type 2 bit
/// move 16 bit
/// value 16 bit
/// eval value 16 bit
// TTEntry struct is the 10 bytes transposition table entry, defined as below:
//
// key 16 bit
// depth 8 bit
// generation 5 bit
// pv node 1 bit
// bound type 2 bit
// move 16 bit
// value 16 bit
// eval value 16 bit
struct TTEntry {
Move move() const { return (Move )move16; }
Value value() const { return (Value)value16; }
Value eval() const { return (Value)eval16; }
Depth depth() const { return (Depth)depth8 + DEPTH_OFFSET; }
bool is_pv() const { return (bool)(genBound8 & 0x4); }
Bound bound() const { return (Bound)(genBound8 & 0x3); }
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev);
Move move() const { return Move(move16); }
Value value() const { return Value(value16); }
Value eval() const { return Value(eval16); }
Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); }
bool is_pv() const { return bool(genBound8 & 0x4); }
Bound bound() const { return Bound(genBound8 & 0x3); }
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;
private:
friend class TranspositionTable;
private:
friend class TranspositionTable;
uint16_t key16;
uint8_t depth8;
uint8_t genBound8;
uint16_t move16;
int16_t value16;
int16_t eval16;
uint16_t key16;
uint8_t depth8;
uint8_t genBound8;
Move move16;
int16_t value16;
int16_t eval16;
};
/// A TranspositionTable is an array of Cluster, of size clusterCount. Each
/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry
/// contains information on exactly one position. The size of a Cluster should
/// divide the size of a cache line for best performance, as the cacheline is
/// prefetched when possible.
// A TranspositionTable is an array of Cluster, of size clusterCount. Each
// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry
// contains information on exactly one position. The size of a Cluster should
// divide the size of a cache line for best performance, as the cacheline is
// prefetched when possible.
class TranspositionTable {
static constexpr int ClusterSize = 3;
static constexpr int ClusterSize = 3;
struct Cluster {
TTEntry entry[ClusterSize];
char padding[2]; // Pad to 32 bytes
};
struct Cluster {
TTEntry entry[ClusterSize];
char padding[2]; // Pad to 32 bytes
};
static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
// Constants used to refresh the hash table periodically
static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things
static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field
static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length
static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number
// Constants used to refresh the hash table periodically
public:
~TranspositionTable() { aligned_large_pages_free(table); }
void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things
TTEntry* probe(const Key key, bool& found) const;
int hashfull() const;
void resize(size_t mbSize);
void clear();
// We have 8 bits available where the lowest 3 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;
TTEntry* first_entry(const Key key) const {
return &table[mul_hi64(key, clusterCount)].entry[0];
}
public:
~TranspositionTable() { aligned_large_pages_free(table); }
private:
friend struct TTEntry;
void new_search() {
// increment by delta to keep lower bits as is
generation8 += GENERATION_DELTA;
}
size_t clusterCount;
Cluster* table;
uint8_t generation8; // Size must be not bigger than TTEntry::genBound8
TTEntry* probe(const Key key, bool& found) const;
int hashfull() const;
void resize(size_t mbSize, int threadCount);
void clear(size_t threadCount);
TTEntry* first_entry(const Key key) const {
return &table[mul_hi64(key, clusterCount)].entry[0];
}
uint8_t generation() const { return generation8; }
private:
friend struct TTEntry;
size_t clusterCount;
Cluster* table = nullptr;
uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8
};
extern TranspositionTable TT;
} // namespace Stockfish
} // namespace Stockfish
#endif // #ifndef TT_H_INCLUDED
#endif // #ifndef TT_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,100 +16,94 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tune.h"
#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include "types.h"
#include "misc.h"
#include "uci.h"
#include "ucioption.h"
using std::string;
namespace Stockfish {
bool Tune::update_on_last;
const UCI::Option* LastOption = nullptr;
static std::map<std::string, int> TuneResults;
bool Tune::update_on_last;
const Option* LastOption = nullptr;
OptionsMap* Tune::options;
namespace {
std::map<std::string, int> TuneResults;
void on_tune(const Option& o) {
if (!Tune::update_on_last || LastOption == &o)
Tune::read_options();
}
void make_option(OptionsMap* options, const string& n, int v, const SetRange& r) {
// Do not generate option when there is nothing to tune (ie. min = max)
if (r(v).first == r(v).second)
return;
if (TuneResults.count(n))
v = TuneResults[n];
(*options)[n] << Option(v, r(v).first, r(v).second, on_tune);
LastOption = &((*options)[n]);
// Print formatted parameters, ready to be copy-pasted in Fishtest
std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << ","
<< (r(v).second - r(v).first) / 20.0 << ","
<< "0.0020" << std::endl;
}
}
string Tune::next(string& names, bool pop) {
string name;
string name;
do {
string token = names.substr(0, names.find(','));
do
{
string token = names.substr(0, names.find(','));
if (pop)
names.erase(0, token.size() + 1);
if (pop)
names.erase(0, token.size() + 1);
std::stringstream ws(token);
name += (ws >> token, token); // Remove trailing whitespace
std::stringstream ws(token);
name += (ws >> token, token); // Remove trailing whitespace
} while ( std::count(name.begin(), name.end(), '(')
- std::count(name.begin(), name.end(), ')'));
} while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')'));
return name;
return name;
}
static void on_tune(const UCI::Option& o) {
if (!Tune::update_on_last || LastOption == &o)
Tune::read_options();
template<>
void Tune::Entry<int>::init_option() {
make_option(options, name, value, range);
}
static void make_option(const string& n, int v, const SetRange& r) {
// Do not generate option when there is nothing to tune (ie. min = max)
if (r(v).first == r(v).second)
return;
if (TuneResults.count(n))
v = TuneResults[n];
Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune);
LastOption = &Options[n];
// Print formatted parameters, ready to be copy-pasted in Fishtest
std::cout << n << ","
<< v << ","
<< r(v).first << "," << r(v).second << ","
<< (r(v).second - r(v).first) / 20.0 << ","
<< "0.0020"
<< std::endl;
}
template<> void Tune::Entry<int>::init_option() { make_option(name, value, range); }
template<> void Tune::Entry<int>::read_option() {
if (Options.count(name))
value = int(Options[name]);
}
template<> void Tune::Entry<Value>::init_option() { make_option(name, value, range); }
template<> void Tune::Entry<Value>::read_option() {
if (Options.count(name))
value = Value(int(Options[name]));
}
template<> void Tune::Entry<Score>::init_option() {
make_option("m" + name, mg_value(value), range);
make_option("e" + name, eg_value(value), range);
}
template<> void Tune::Entry<Score>::read_option() {
if (Options.count("m" + name))
value = make_score(int(Options["m" + name]), eg_value(value));
if (Options.count("e" + name))
value = make_score(mg_value(value), int(Options["e" + name]));
template<>
void Tune::Entry<int>::read_option() {
if (options->count(name))
value = int((*options)[name]);
}
// Instead of a variable here we have a PostUpdate function: just call it
template<> void Tune::Entry<Tune::PostUpdate>::init_option() {}
template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
template<>
void Tune::Entry<Tune::PostUpdate>::init_option() {}
template<>
void Tune::Entry<Tune::PostUpdate>::read_option() {
value();
}
} // namespace Stockfish
} // namespace Stockfish
// Init options with tuning session results instead of default values. Useful to
@ -121,13 +115,10 @@ template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
//
// Then paste the output below, as the function body
#include <cmath>
namespace Stockfish {
void Tune::read_results() {
/* ...insert your values here... */
void Tune::read_results() { /* ...insert your values here... */
}
} // namespace Stockfish
} // namespace Stockfish

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,145 +19,163 @@
#ifndef TUNE_H_INCLUDED
#define TUNE_H_INCLUDED
#include <cstddef>
#include <memory>
#include <string>
#include <type_traits>
#include <type_traits> // IWYU pragma: keep
#include <utility>
#include <vector>
namespace Stockfish {
typedef std::pair<int, int> Range; // Option's min-max values
typedef Range (RangeFun) (int);
class OptionsMap;
using Range = std::pair<int, int>; // Option's min-max values
using RangeFun = Range(int);
// Default Range function, to calculate Option's min-max values
inline Range default_range(int v) {
return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0);
}
inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); }
struct SetRange {
explicit SetRange(RangeFun f) : fun(f) {}
SetRange(int min, int max) : fun(nullptr), range(min, max) {}
Range operator()(int v) const { return fun ? fun(v) : range; }
explicit SetRange(RangeFun f) :
fun(f) {}
SetRange(int min, int max) :
fun(nullptr),
range(min, max) {}
Range operator()(int v) const { return fun ? fun(v) : range; }
RangeFun* fun;
Range range;
RangeFun* fun;
Range range;
};
#define SetDefaultRange SetRange(default_range)
/// Tune class implements the 'magic' code that makes the setup of a fishtest
/// tuning session as easy as it can be. Mainly you have just to remove const
/// qualifiers from the variables you want to tune and flag them for tuning, so
/// if you have:
///
/// const Score myScore = S(10, 15);
/// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } };
///
/// If you have a my_post_update() function to run after values have been updated,
/// and a my_range() function to set custom Option's min-max values, then you just
/// remove the 'const' qualifiers and write somewhere below in the file:
///
/// TUNE(SetRange(my_range), myScore, myValue, my_post_update);
///
/// You can also set the range directly, and restore the default at the end
///
/// TUNE(SetRange(-100, 100), myScore, SetDefaultRange);
///
/// In case update function is slow and you have many parameters, you can add:
///
/// UPDATE_ON_LAST();
///
/// And the values update, including post update function call, will be done only
/// once, after the engine receives the last UCI option, that is the one defined
/// and created as the last one, so the GUI should send the options in the same
/// order in which have been defined.
// Tune class implements the 'magic' code that makes the setup of a fishtest tuning
// session as easy as it can be. Mainly you have just to remove const qualifiers
// from the variables you want to tune and flag them for tuning, so if you have:
//
// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } };
//
// If you have a my_post_update() function to run after values have been updated,
// and a my_range() function to set custom Option's min-max values, then you just
// remove the 'const' qualifiers and write somewhere below in the file:
//
// TUNE(SetRange(my_range), myValue, my_post_update);
//
// You can also set the range directly, and restore the default at the end
//
// TUNE(SetRange(-100, 100), myValue, SetDefaultRange);
//
// In case update function is slow and you have many parameters, you can add:
//
// UPDATE_ON_LAST();
//
// And the values update, including post update function call, will be done only
// once, after the engine receives the last UCI option, that is the one defined
// and created as the last one, so the GUI should send the options in the same
// order in which have been defined.
class Tune {
typedef void (PostUpdate) (); // Post-update function
using PostUpdate = void(); // Post-update function
Tune() { read_results(); }
Tune(const Tune&) = delete;
void operator=(const Tune&) = delete;
void read_results();
Tune() { read_results(); }
Tune(const Tune&) = delete;
void operator=(const Tune&) = delete;
void read_results();
static Tune& instance() { static Tune t; return t; } // Singleton
static Tune& instance() {
static Tune t;
return t;
} // Singleton
// Use polymorphism to accommodate Entry of different types in the same vector
struct EntryBase {
virtual ~EntryBase() = default;
virtual void init_option() = 0;
virtual void read_option() = 0;
};
// Use polymorphism to accommodate Entry of different types in the same vector
struct EntryBase {
virtual ~EntryBase() = default;
virtual void init_option() = 0;
virtual void read_option() = 0;
};
template<typename T>
struct Entry : public EntryBase {
template<typename T>
struct Entry: public EntryBase {
static_assert(!std::is_const<T>::value, "Parameter cannot be const!");
static_assert(!std::is_const_v<T>, "Parameter cannot be const!");
static_assert( std::is_same<T, int>::value
|| std::is_same<T, Value>::value
|| std::is_same<T, Score>::value
|| std::is_same<T, PostUpdate>::value, "Parameter type not supported!");
static_assert(std::is_same_v<T, int> || std::is_same_v<T, PostUpdate>,
"Parameter type not supported!");
Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {}
void operator=(const Entry&) = delete; // Because 'value' is a reference
void init_option() override;
void read_option() override;
Entry(const std::string& n, T& v, const SetRange& r) :
name(n),
value(v),
range(r) {}
void operator=(const Entry&) = delete; // Because 'value' is a reference
void init_option() override;
void read_option() override;
std::string name;
T& value;
SetRange range;
};
std::string name;
T& value;
SetRange range;
};
// Our facility to fill the container, each Entry corresponds to a parameter
// to tune. We use variadic templates to deal with an unspecified number of
// entries, each one of a possible different type.
static std::string next(std::string& names, bool pop = true);
// Our facility to fill the container, each Entry corresponds to a parameter
// to tune. We use variadic templates to deal with an unspecified number of
// entries, each one of a possible different type.
static std::string next(std::string& names, bool pop = true);
int add(const SetRange&, std::string&&) { return 0; }
int add(const SetRange&, std::string&&) { return 0; }
template<typename T, typename... Args>
int add(const SetRange& range, std::string&& names, T& value, Args&&... args) {
list.push_back(std::unique_ptr<EntryBase>(new Entry<T>(next(names), value, range)));
return add(range, std::move(names), args...);
}
template<typename T, typename... Args>
int add(const SetRange& range, std::string&& names, T& value, Args&&... args) {
list.push_back(std::unique_ptr<EntryBase>(new Entry<T>(next(names), value, range)));
return add(range, std::move(names), args...);
}
// Template specialization for arrays: recursively handle multi-dimensional arrays
template<typename T, size_t N, typename... Args>
int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) {
for (size_t 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...);
}
// Template specialization for arrays: recursively handle multi-dimensional arrays
template<typename T, size_t N, typename... Args>
int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) {
for (size_t 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...);
}
// Template specialization for SetRange
template<typename... Args>
int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) {
return add(value, (next(names), std::move(names)), args...);
}
// Template specialization for SetRange
template<typename... Args>
int add(const SetRange&, std::string&& names, SetRange& value, Args&&... args) {
return add(value, (next(names), std::move(names)), args...);
}
std::vector<std::unique_ptr<EntryBase>> list;
std::vector<std::unique_ptr<EntryBase>> list;
public:
template<typename... Args>
static int add(const std::string& names, Args&&... args) {
return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis
}
static void init() { for (auto& e : instance().list) e->init_option(); read_options(); } // Deferred, due to UCI::Options access
static void read_options() { for (auto& e : instance().list) e->read_option(); }
static bool update_on_last;
public:
template<typename... Args>
static int add(const std::string& names, Args&&... args) {
return instance().add(SetDefaultRange, names.substr(1, names.size() - 2),
args...); // Remove trailing parenthesis
}
static void init(OptionsMap& o) {
options = &o;
for (auto& e : instance().list)
e->init_option();
read_options();
} // Deferred, due to UCIEngine::Options access
static void read_options() {
for (auto& e : instance().list)
e->read_option();
}
static bool update_on_last;
static OptionsMap* options;
};
// Some macro magic :-) we define a dummy int variable that compiler initializes calling Tune::add()
// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add()
#define STRINGIFY(x) #x
#define UNIQUE2(x, y) x ## y
#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__
#define UNIQUE2(x, y) x##y
#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__
#define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__)
#define UPDATE_ON_LAST() bool UNIQUE(p, __LINE__) = Tune::update_on_last = true
} // namespace Stockfish
} // namespace Stockfish
#endif // #ifndef TUNE_H_INCLUDED
#endif // #ifndef TUNE_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -17,472 +17,403 @@
*/
#ifndef TYPES_H_INCLUDED
#define TYPES_H_INCLUDED
#define TYPES_H_INCLUDED
/// When compiling with provided Makefile (e.g. for Linux and OSX), configuration
/// is done automatically. To get started type 'make help'.
///
/// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches
/// need to be set manually:
///
/// -DNDEBUG | Disable debugging mode. Always use this for release.
///
/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to
/// | run on some very old machines.
///
/// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works
/// | only in 64-bit mode and requires hardware with popcnt support.
///
/// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works
/// | only in 64-bit mode and requires hardware with pext support.
// When compiling with provided Makefile (e.g. for Linux and OSX), configuration
// is done automatically. To get started type 'make help'.
//
// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches
// need to be set manually:
//
// -DNDEBUG | Disable debugging mode. Always use this for release.
//
// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to
// | run on some very old machines.
//
// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works
// | only in 64-bit mode and requires hardware with popcnt support.
//
// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works
// | only in 64-bit mode and requires hardware with pext support.
#include <cassert>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <algorithm>
#include <cassert>
#include <cstdint>
#if defined(_MSC_VER)
// Disable some silly and noisy warning from MSVC compiler
#pragma warning(disable: 4127) // Conditional expression is constant
#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type
#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false'
#endif
#if defined(_MSC_VER)
// Disable some silly and noisy warnings from MSVC compiler
#pragma warning(disable: 4127) // Conditional expression is constant
#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type
#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false'
#endif
/// Predefined macros hell:
///
/// __GNUC__ Compiler is gcc, Clang or Intel on Linux
/// __INTEL_COMPILER Compiler is Intel
/// _MSC_VER Compiler is MSVC or Intel on Windows
/// _WIN32 Building on Windows (any)
/// _WIN64 Building on Windows 64 bit
// Predefined macros hell:
//
// __GNUC__ Compiler is GCC, Clang or ICX
// __clang__ Compiler is Clang or ICX
// __INTEL_LLVM_COMPILER Compiler is ICX
// _MSC_VER Compiler is MSVC
// _WIN32 Building on Windows (any)
// _WIN64 Building on Windows 64 bit
#if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__)
#define ALIGNAS_ON_STACK_VARIABLES_BROKEN
#endif
#if defined(__GNUC__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) \
&& defined(_WIN32) && !defined(__clang__)
#define ALIGNAS_ON_STACK_VARIABLES_BROKEN
#endif
#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
# include <intrin.h> // Microsoft header for _BitScanForward64()
# define IS_64BIT
#endif
#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
#include <intrin.h> // Microsoft header for _BitScanForward64()
#define IS_64BIT
#endif
#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
# include <nmmintrin.h> // Intel and Microsoft header for _mm_popcnt_u64()
#endif
#if defined(USE_POPCNT) && defined(_MSC_VER)
#include <nmmintrin.h> // Microsoft header for _mm_popcnt_u64()
#endif
#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
# include <xmmintrin.h> // Intel and Microsoft header for _mm_prefetch()
#endif
#if !defined(NO_PREFETCH) && defined(_MSC_VER)
#include <xmmintrin.h> // Microsoft header for _mm_prefetch()
#endif
#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
#endif
#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
#endif
namespace Stockfish {
#ifdef USE_POPCNT
#ifdef USE_POPCNT
constexpr bool HasPopCnt = true;
#else
#else
constexpr bool HasPopCnt = false;
#endif
#endif
#ifdef USE_PEXT
#ifdef USE_PEXT
constexpr bool HasPext = true;
#else
#else
constexpr bool HasPext = false;
#endif
#endif
#ifdef IS_64BIT
#ifdef IS_64BIT
constexpr bool Is64Bit = true;
#else
#else
constexpr bool Is64Bit = false;
#endif
#endif
typedef uint64_t Key;
typedef uint64_t Bitboard;
using Key = uint64_t;
using Bitboard = uint64_t;
constexpr int MAX_MOVES = 256;
constexpr int MAX_PLY = 246;
/// A move needs 16 bits to be stored
///
/// bit 0- 5: destination square (from 0 to 63)
/// bit 6-11: origin square (from 0 to 63)
/// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2)
/// bit 14-15: special move flag: promotion (1), en passant (2), castling (3)
/// NOTE: en passant bit is set only when a pawn can be captured
///
/// Special cases are MOVE_NONE and MOVE_NULL. We can sneak these in because in
/// any normal move destination square is always different from origin square
/// while MOVE_NONE and MOVE_NULL have the same origin and destination square.
enum Move : int {
MOVE_NONE,
MOVE_NULL = 65
};
enum MoveType {
NORMAL,
PROMOTION = 1 << 14,
EN_PASSANT = 2 << 14,
CASTLING = 3 << 14
};
enum Color {
WHITE, BLACK, COLOR_NB = 2
WHITE,
BLACK,
COLOR_NB = 2
};
enum CastlingRights {
NO_CASTLING,
WHITE_OO,
WHITE_OOO = WHITE_OO << 1,
BLACK_OO = WHITE_OO << 2,
BLACK_OOO = WHITE_OO << 3,
NO_CASTLING,
WHITE_OO,
WHITE_OOO = WHITE_OO << 1,
BLACK_OO = WHITE_OO << 2,
BLACK_OOO = WHITE_OO << 3,
KING_SIDE = WHITE_OO | BLACK_OO,
QUEEN_SIDE = WHITE_OOO | BLACK_OOO,
WHITE_CASTLING = WHITE_OO | WHITE_OOO,
BLACK_CASTLING = BLACK_OO | BLACK_OOO,
ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING,
KING_SIDE = WHITE_OO | BLACK_OO,
QUEEN_SIDE = WHITE_OOO | BLACK_OOO,
WHITE_CASTLING = WHITE_OO | WHITE_OOO,
BLACK_CASTLING = BLACK_OO | BLACK_OOO,
ANY_CASTLING = WHITE_CASTLING | BLACK_CASTLING,
CASTLING_RIGHT_NB = 16
};
enum Phase {
PHASE_ENDGAME,
PHASE_MIDGAME = 128,
MG = 0, EG = 1, PHASE_NB = 2
};
enum ScaleFactor {
SCALE_FACTOR_DRAW = 0,
SCALE_FACTOR_NORMAL = 64,
SCALE_FACTOR_MAX = 128,
SCALE_FACTOR_NONE = 255
CASTLING_RIGHT_NB = 16
};
enum Bound {
BOUND_NONE,
BOUND_UPPER,
BOUND_LOWER,
BOUND_EXACT = BOUND_UPPER | BOUND_LOWER
BOUND_NONE,
BOUND_UPPER,
BOUND_LOWER,
BOUND_EXACT = BOUND_UPPER | BOUND_LOWER
};
enum Value : int {
VALUE_ZERO = 0,
VALUE_DRAW = 0,
VALUE_KNOWN_WIN = 10000,
VALUE_MATE = 32000,
VALUE_INFINITE = 32001,
VALUE_NONE = 32002,
// Value is used as an alias for int16_t, this is done to differentiate between
// a search value and any other integer value. The values used in search are always
// supposed to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range.
using Value = int;
VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY,
VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY,
VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY,
VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY,
constexpr Value VALUE_ZERO = 0;
constexpr Value VALUE_DRAW = 0;
constexpr Value VALUE_NONE = 32002;
constexpr Value VALUE_INFINITE = 32001;
PawnValueMg = 126, PawnValueEg = 208,
KnightValueMg = 781, KnightValueEg = 854,
BishopValueMg = 825, BishopValueEg = 915,
RookValueMg = 1276, RookValueEg = 1380,
QueenValueMg = 2538, QueenValueEg = 2682,
constexpr Value VALUE_MATE = 32000;
constexpr Value VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY;
constexpr Value VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY;
MidgameLimit = 15258, EndgameLimit = 3915
};
constexpr Value VALUE_TB = VALUE_MATE_IN_MAX_PLY - 1;
constexpr Value VALUE_TB_WIN_IN_MAX_PLY = VALUE_TB - MAX_PLY;
constexpr Value VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_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.
constexpr Value PawnValue = 208;
constexpr Value KnightValue = 781;
constexpr Value BishopValue = 825;
constexpr Value RookValue = 1276;
constexpr Value QueenValue = 2538;
// clang-format off
enum PieceType {
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
ALL_PIECES = 0,
PIECE_TYPE_NB = 8
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
ALL_PIECES = 0,
PIECE_TYPE_NB = 8
};
enum Piece {
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,
PIECE_NB = 16
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,
PIECE_NB = 16
};
// clang-format on
constexpr Value PieceValue[PHASE_NB][PIECE_NB] = {
{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO,
VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO },
{ VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO,
VALUE_ZERO, PawnValueEg, KnightValueEg, BishopValueEg, RookValueEg, QueenValueEg, VALUE_ZERO, VALUE_ZERO }
};
constexpr Value PieceValue[PIECE_NB] = {
VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO,
VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO};
typedef int Depth;
using Depth = int;
enum : int {
DEPTH_QS_CHECKS = 0,
DEPTH_QS_NO_CHECKS = -1,
DEPTH_QS_RECAPTURES = -5,
DEPTH_QS_CHECKS = 0,
DEPTH_QS_NO_CHECKS = -1,
DEPTH_NONE = -6,
DEPTH_NONE = -6,
DEPTH_OFFSET = -7 // value used only for TT entry occupancy check
DEPTH_OFFSET = -7 // value used only for TT entry occupancy check
};
// clang-format off
enum Square : int {
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,
SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4,
SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5,
SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6,
SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7,
SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8,
SQ_NONE,
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,
SQ_A4, SQ_B4, SQ_C4, SQ_D4, SQ_E4, SQ_F4, SQ_G4, SQ_H4,
SQ_A5, SQ_B5, SQ_C5, SQ_D5, SQ_E5, SQ_F5, SQ_G5, SQ_H5,
SQ_A6, SQ_B6, SQ_C6, SQ_D6, SQ_E6, SQ_F6, SQ_G6, SQ_H6,
SQ_A7, SQ_B7, SQ_C7, SQ_D7, SQ_E7, SQ_F7, SQ_G7, SQ_H7,
SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8,
SQ_NONE,
SQUARE_ZERO = 0,
SQUARE_NB = 64
SQUARE_ZERO = 0,
SQUARE_NB = 64
};
// clang-format on
enum Direction : int {
NORTH = 8,
EAST = 1,
SOUTH = -NORTH,
WEST = -EAST,
NORTH = 8,
EAST = 1,
SOUTH = -NORTH,
WEST = -EAST,
NORTH_EAST = NORTH + EAST,
SOUTH_EAST = SOUTH + EAST,
SOUTH_WEST = SOUTH + WEST,
NORTH_WEST = NORTH + WEST
NORTH_EAST = NORTH + EAST,
SOUTH_EAST = SOUTH + EAST,
SOUTH_WEST = SOUTH + WEST,
NORTH_WEST = NORTH + WEST
};
enum File : int {
FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB
FILE_A,
FILE_B,
FILE_C,
FILE_D,
FILE_E,
FILE_F,
FILE_G,
FILE_H,
FILE_NB
};
enum Rank : int {
RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB
RANK_1,
RANK_2,
RANK_3,
RANK_4,
RANK_5,
RANK_6,
RANK_7,
RANK_8,
RANK_NB
};
// Keep track of what a move changes on the board (used by NNUE)
struct DirtyPiece {
// Number of changed pieces
int dirty_num;
// Number of changed pieces
int dirty_num;
// Max 3 pieces can change in one move. A promotion with capture moves
// both the pawn and the captured piece to SQ_NONE and the piece promoted
// to from SQ_NONE to the capture square.
Piece piece[3];
// Max 3 pieces can change in one move. A promotion with capture moves
// both the pawn and the captured piece to SQ_NONE and the piece promoted
// to from SQ_NONE to the capture square.
Piece piece[3];
// From and to squares, which may be SQ_NONE
Square from[3];
Square to[3];
// From and to squares, which may be SQ_NONE
Square from[3];
Square to[3];
};
/// Score enum stores a middlegame and an endgame value in a single integer (enum).
/// The least significant 16 bits are used to store the middlegame value and the
/// upper 16 bits are used to store the endgame value. We have to take care to
/// avoid left-shifting a signed int to avoid undefined behavior.
enum Score : int { SCORE_ZERO };
#define ENABLE_INCR_OPERATORS_ON(T) \
inline T& operator++(T& d) { return d = T(int(d) + 1); } \
inline T& operator--(T& d) { return d = T(int(d) - 1); }
constexpr Score make_score(int mg, int eg) {
return Score((int)((unsigned int)eg << 16) + mg);
}
/// Extracting the signed lower and upper 16 bits is not so trivial because
/// according to the standard a simple cast to short is implementation defined
/// and so is a right shift of a signed integer.
inline Value eg_value(Score s) {
union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) };
return Value(eg.s);
}
inline Value mg_value(Score s) {
union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) };
return Value(mg.s);
}
#define ENABLE_BASE_OPERATORS_ON(T) \
constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \
constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \
constexpr T operator-(T d) { return T(-int(d)); } \
inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \
inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; }
#define ENABLE_INCR_OPERATORS_ON(T) \
inline T& operator++(T& d) { return d = T(int(d) + 1); } \
inline T& operator--(T& d) { return d = T(int(d) - 1); }
#define ENABLE_FULL_OPERATORS_ON(T) \
ENABLE_BASE_OPERATORS_ON(T) \
constexpr T operator*(int i, T d) { return T(i * int(d)); } \
constexpr T operator*(T d, int i) { return T(int(d) * i); } \
constexpr T operator/(T d, int i) { return T(int(d) / i); } \
constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \
inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \
inline T& operator/=(T& d, int i) { return d = T(int(d) / i); }
ENABLE_FULL_OPERATORS_ON(Value)
ENABLE_FULL_OPERATORS_ON(Direction)
ENABLE_INCR_OPERATORS_ON(Piece)
ENABLE_INCR_OPERATORS_ON(PieceType)
ENABLE_INCR_OPERATORS_ON(Square)
ENABLE_INCR_OPERATORS_ON(File)
ENABLE_INCR_OPERATORS_ON(Rank)
ENABLE_BASE_OPERATORS_ON(Score)
#undef ENABLE_INCR_OPERATORS_ON
#undef ENABLE_FULL_OPERATORS_ON
#undef ENABLE_INCR_OPERATORS_ON
#undef ENABLE_BASE_OPERATORS_ON
constexpr Direction operator+(Direction d1, Direction d2) { return Direction(int(d1) + int(d2)); }
constexpr Direction operator*(int i, Direction d) { return Direction(i * int(d)); }
/// Additional operators to add a Direction to a Square
// Additional operators to add a Direction to a Square
constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
constexpr Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); }
inline Square& operator+=(Square& s, Direction d) { return s = s + d; }
inline Square& operator-=(Square& s, Direction d) { return s = s - d; }
inline Square& operator+=(Square& s, Direction d) { return s = s + d; }
inline Square& operator-=(Square& s, Direction d) { return s = s - d; }
/// Only declared but not defined. We don't want to multiply two scores due to
/// a very high risk of overflow. So user should explicitly convert to integer.
Score operator*(Score, Score) = delete;
// Toggle color
constexpr Color operator~(Color c) { return Color(c ^ BLACK); }
/// Division of a Score must be handled separately for each term
inline Score operator/(Score s, int i) {
return make_score(mg_value(s) / i, eg_value(s) / i);
}
// Swap A1 <-> A8
constexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); }
/// Multiplication of a Score by an integer. We check for overflow in debug mode.
inline Score operator*(Score s, int i) {
// Swap A1 <-> H1
constexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); }
Score result = Score(int(s) * i);
assert(eg_value(result) == (i * eg_value(s)));
assert(mg_value(result) == (i * mg_value(s)));
assert((i == 0) || (result / i) == s);
return result;
}
/// Multiplication of a Score by a boolean
inline Score operator*(Score s, bool b) {
return b ? s : SCORE_ZERO;
}
constexpr Color operator~(Color c) {
return Color(c ^ BLACK); // Toggle color
}
constexpr Square flip_rank(Square s) { // Swap A1 <-> A8
return Square(s ^ SQ_A8);
}
constexpr Square flip_file(Square s) { // Swap A1 <-> H1
return Square(s ^ SQ_H1);
}
constexpr Piece operator~(Piece pc) {
return Piece(pc ^ 8); // Swap color of piece B_KNIGHT <-> W_KNIGHT
}
// Swap color of piece B_KNIGHT <-> W_KNIGHT
constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); }
constexpr CastlingRights operator&(Color c, CastlingRights cr) {
return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr);
return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr);
}
constexpr Value mate_in(int ply) { return VALUE_MATE - ply; }
constexpr Value mate_in(int ply) {
Value zero = Value(0);
return zero - (VALUE_MATE - ply);
}
constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; }
constexpr Value mated_in(int ply) {
Value zero = Value(0);
return zero - (-VALUE_MATE + ply);
}
constexpr Square make_square(File f, Rank r) {
return Square((r << 3) + f);
}
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);
}
constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); }
constexpr PieceType type_of(Piece pc) {
return PieceType(pc & 7);
}
constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); }
inline Color color_of(Piece pc) {
assert(pc != NO_PIECE);
return Color(pc >> 3);
assert(pc != NO_PIECE);
return Color(pc >> 3);
}
constexpr bool is_ok(Square s) {
return s >= SQ_A1 && s <= SQ_H8;
}
constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; }
constexpr File file_of(Square s) {
return File(s & 7);
}
constexpr File file_of(Square s) { return File(s & 7); }
constexpr Rank rank_of(Square s) {
return Rank(s >> 3);
}
constexpr Rank rank_of(Square s) { return Rank(s >> 3); }
constexpr Square relative_square(Color c, Square s) {
return Square(s ^ (c * 56));
}
constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); }
constexpr Rank relative_rank(Color c, Rank r) {
return Rank(r ^ (c * 7));
}
constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); }
constexpr Rank relative_rank(Color c, Square s) {
return relative_rank(c, rank_of(s));
}
constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); }
constexpr Direction pawn_push(Color c) {
return c == WHITE ? NORTH : SOUTH;
}
constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; }
constexpr Square from_sq(Move m) {
return Square((m >> 6) & 0x3F);
}
constexpr Square to_sq(Move m) {
return Square(m & 0x3F);
}
constexpr int from_to(Move m) {
return m & 0xFFF;
}
constexpr MoveType type_of(Move m) {
return MoveType(m & (3 << 14));
}
constexpr PieceType promotion_type(Move m) {
return PieceType(((m >> 12) & 3) + KNIGHT);
}
constexpr Move make_move(Square from, Square to) {
return Move((from << 6) + to);
}
template<MoveType T>
constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {
return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);
}
constexpr bool is_ok(Move m) {
return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE
}
/// Based on a congruential pseudo random number generator
// Based on a congruential pseudo-random number generator
constexpr Key make_key(uint64_t seed) {
return seed * 6364136223846793005ULL + 1442695040888963407ULL;
return seed * 6364136223846793005ULL + 1442695040888963407ULL;
}
} // namespace Stockfish
#endif // #ifndef TYPES_H_INCLUDED
enum MoveType {
NORMAL,
PROMOTION = 1 << 14,
EN_PASSANT = 2 << 14,
CASTLING = 3 << 14
};
#include "tune.h" // Global visibility to tuning setup
// A move needs 16 bits to be stored
//
// bit 0- 5: destination square (from 0 to 63)
// bit 6-11: origin square (from 0 to 63)
// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2)
// bit 14-15: special move flag: promotion (1), en passant (2), castling (3)
// NOTE: en passant bit is set only when a pawn can be captured
//
// Special cases are Move::none() and Move::null(). We can sneak these in because in
// any normal move destination square is always different from origin square
// while Move::none() and Move::null() have the same origin and destination square.
class Move {
public:
Move() = default;
constexpr explicit Move(std::uint16_t d) :
data(d) {}
constexpr Move(Square from, Square to) :
data((from << 6) + to) {}
template<MoveType T>
static constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {
return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to);
}
constexpr Square from_sq() const {
assert(is_ok());
return Square((data >> 6) & 0x3F);
}
constexpr Square to_sq() const {
assert(is_ok());
return Square(data & 0x3F);
}
constexpr int from_to() const { return data & 0xFFF; }
constexpr MoveType type_of() const { return MoveType(data & (3 << 14)); }
constexpr PieceType promotion_type() const { return PieceType(((data >> 12) & 3) + KNIGHT); }
constexpr bool is_ok() const { return none().data != data && null().data != data; }
static constexpr Move null() { return Move(65); }
static constexpr Move none() { return Move(0); }
constexpr bool operator==(const Move& m) const { return data == m.data; }
constexpr bool operator!=(const Move& m) const { return data != m.data; }
constexpr explicit operator bool() const { return data != 0; }
constexpr std::uint16_t raw() const { return data; }
struct MoveHash {
std::size_t operator()(const Move& m) const { return make_key(m.data); }
};
protected:
std::uint16_t data;
};
} // namespace Stockfish
#endif // #ifndef TYPES_H_INCLUDED
#include "tune.h" // Global visibility to tuning setup

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,50 +16,305 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cassert>
#include <cmath>
#include <iostream>
#include <sstream>
#include <string>
#include "uci.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <optional>
#include <sstream>
#include <string_view>
#include <utility>
#include <vector>
#include "benchmark.h"
#include "engine.h"
#include "evaluate.h"
#include "movegen.h"
#include "position.h"
#include "score.h"
#include "search.h"
#include "thread.h"
#include "timeman.h"
#include "tt.h"
#include "uci.h"
#include "syzygy/tbprobe.h"
using namespace std;
#include "types.h"
#include "ucioption.h"
namespace Stockfish {
extern vector<string> setup_bench(const Position&, istream&);
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
namespace {
template<typename... Ts>
struct overload: Ts... {
using Ts::operator()...;
};
// FEN string of the initial position, normal chess
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
template<typename... Ts>
overload(Ts...) -> overload<Ts...>;
UCIEngine::UCIEngine(int argc, char** argv) :
engine(argv[0]),
cli(argc, argv) {
auto& options = engine.get_options();
options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); });
options["Threads"] << Option(1, 1, 1024, [this](const Option&) { engine.resize_threads(); });
options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) { engine.set_tt_size(o); });
options["Clear Hash"] << Option([this](const Option&) { engine.search_clear(); });
options["Ponder"] << Option(false);
options["MultiPV"] << Option(1, 1, MAX_MOVES);
options["Skill Level"] << Option(20, 0, 20);
options["Move Overhead"] << Option(10, 0, 5000);
options["nodestime"] << Option(0, 0, 10000);
options["UCI_Chess960"] << Option(false);
options["UCI_LimitStrength"] << Option(false);
options["UCI_Elo"] << Option(1320, 1320, 3190);
options["UCI_ShowWDL"] << Option(false);
options["SyzygyPath"] << Option("<empty>", [](const Option& o) { Tablebases::init(o); });
options["SyzygyProbeDepth"] << Option(1, 1, 100);
options["Syzygy50MoveRule"] << Option(true);
options["SyzygyProbeLimit"] << Option(7, 0, 7);
options["EvalFile"] << Option(EvalFileDefaultNameBig,
[this](const Option& o) { engine.load_big_network(o); });
options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall,
[this](const Option& o) { engine.load_small_network(o); });
// position() is called when engine receives the "position" UCI command.
// The function sets up the position described in the given FEN string ("fen")
// or the starting position ("startpos") and then makes the moves given in the
// following move list ("moves").
engine.set_on_iter([](const auto& i) { on_iter(i); });
engine.set_on_update_no_moves([](const auto& i) { on_update_no_moves(i); });
engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); });
engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); });
void position(Position& pos, istringstream& is, StateListPtr& states) {
engine.load_networks();
engine.resize_threads();
engine.search_clear(); // After threads are up
}
Move m;
string token, fen;
void UCIEngine::loop() {
std::string token, cmd;
for (int i = 1; i < cli.argc; ++i)
cmd += std::string(cli.argv[i]) + " ";
do
{
if (cli.argc == 1
&& !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
cmd = "quit";
std::istringstream is(cmd);
token.clear(); // Avoid a stale if getline() returns nothing or a blank line
is >> std::skipws >> token;
if (token == "quit" || token == "stop")
engine.stop();
// The GUI sends 'ponderhit' to tell that the user has played the expected move.
// So, 'ponderhit' is sent if pondering was done on the same move that the user
// has played. The search should continue, but should also switch from pondering
// to the normal search.
else if (token == "ponderhit")
engine.set_ponderhit(false);
else if (token == "uci")
sync_cout << "id name " << engine_info(true) << "\n"
<< engine.get_options() << "\nuciok" << sync_endl;
else if (token == "setoption")
setoption(is);
else if (token == "go")
go(is);
else if (token == "position")
position(is);
else if (token == "ucinewgame")
engine.search_clear();
else if (token == "isready")
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();
else if (token == "bench")
bench(is);
else if (token == "d")
sync_cout << engine.visualize() << sync_endl;
else if (token == "eval")
engine.trace_eval();
else if (token == "compiler")
sync_cout << compiler_info() << sync_endl;
else if (token == "export_net")
{
std::pair<std::optional<std::string>, std::string> files[2];
if (is >> std::skipws >> files[0].second)
files[0].first = files[0].second;
if (is >> std::skipws >> files[1].second)
files[1].first = files[1].second;
engine.save_network(files);
}
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
sync_cout
<< "\nStockfish is a powerful chess engine for playing and analyzing."
"\nIt is released as free software licensed under the GNU GPLv3 License."
"\nStockfish is normally used with a graphical user interface (GUI) and implements"
"\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc."
"\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme"
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n"
<< sync_endl;
else if (!token.empty() && token[0] != '#')
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
}
Search::LimitsType UCIEngine::parse_limits(std::istream& is) {
Search::LimitsType limits;
std::string token;
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));
else if (token == "wtime")
is >> limits.time[WHITE];
else if (token == "btime")
is >> limits.time[BLACK];
else if (token == "winc")
is >> limits.inc[WHITE];
else if (token == "binc")
is >> limits.inc[BLACK];
else if (token == "movestogo")
is >> limits.movestogo;
else if (token == "depth")
is >> limits.depth;
else if (token == "nodes")
is >> limits.nodes;
else if (token == "movetime")
is >> limits.movetime;
else if (token == "mate")
is >> limits.mate;
else if (token == "perft")
is >> limits.perft;
else if (token == "infinite")
limits.infinite = 1;
else if (token == "ponder")
limits.ponderMode = true;
return limits;
}
void UCIEngine::go(std::istringstream& is) {
Search::LimitsType limits = parse_limits(is);
if (limits.perft)
perft(limits);
else
engine.go(limits);
}
void UCIEngine::bench(std::istream& args) {
std::string token;
uint64_t num, nodes = 0, cnt = 1;
uint64_t nodesSearched = 0;
const auto& options = engine.get_options();
engine.set_on_update_full([&](const auto& i) {
nodesSearched = i.nodes;
on_update_full(i, options["UCI_ShowWDL"]);
});
std::vector<std::string> list = Benchmark::setup_bench(engine.fen(), args);
num = count_if(list.begin(), list.end(),
[](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; });
TimePoint elapsed = now();
for (const auto& cmd : list)
{
std::istringstream is(cmd);
is >> std::skipws >> token;
if (token == "go" || token == "eval")
{
std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << engine.fen() << ")"
<< std::endl;
if (token == "go")
{
Search::LimitsType limits = parse_limits(is);
if (limits.perft)
nodes = perft(limits);
else
{
engine.go(limits);
engine.wait_for_search_finished();
}
nodes += nodesSearched;
nodesSearched = 0;
}
else
engine.trace_eval();
}
else if (token == "setoption")
setoption(is);
else if (token == "position")
position(is);
else if (token == "ucinewgame")
{
engine.search_clear(); // search_clear may take a while
elapsed = now();
}
}
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
dbg_print();
std::cerr << "\n==========================="
<< "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes
<< "\nNodes/second : " << 1000 * nodes / elapsed << std::endl;
// reset callback, to not capture a dangling reference to nodesSearched
engine.set_on_update_full([&](const auto& i) { on_update_full(i, options["UCI_ShowWDL"]); });
}
void UCIEngine::setoption(std::istringstream& is) {
engine.wait_for_search_finished();
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"]);
sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
return nodes;
}
void UCIEngine::position(std::istringstream& is) {
std::string token, fen;
is >> token;
if (token == "startpos")
{
fen = StartFEN;
is >> token; // Consume "moves" token if any
is >> token; // Consume the "moves" token, if any
}
else if (token == "fen")
while (is >> token && token != "moves")
@ -67,317 +322,181 @@ namespace {
else
return;
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop old and create a new one
pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main());
// Parse move list (if any)
while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
{
states->emplace_back();
pos.do_move(m, states->back());
}
}
// trace_eval() prints the evaluation for the current position, consistent with the UCI
// options set so far.
void trace_eval(Position& pos) {
StateListPtr states(new std::deque<StateInfo>(1));
Position p;
p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
Eval::NNUE::verify();
sync_cout << "\n" << Eval::trace(p) << sync_endl;
}
// setoption() is called when engine receives the "setoption" UCI command. The
// function updates the UCI option ("name") to the given value ("value").
void setoption(istringstream& is) {
string token, name, value;
is >> token; // Consume "name" token
// Read option name (can contain spaces)
while (is >> token && token != "value")
name += (name.empty() ? "" : " ") + token;
// Read option value (can contain spaces)
while (is >> token)
value += (value.empty() ? "" : " ") + token;
if (Options.count(name))
Options[name] = value;
else
sync_cout << "No such option: " << name << sync_endl;
}
// go() is called when engine receives the "go" UCI command. The function sets
// the thinking time and other parameters from the input string, then starts
// the search.
void go(Position& pos, istringstream& is, StateListPtr& states) {
Search::LimitsType limits;
string token;
bool ponderMode = false;
limits.startTime = now(); // As early as possible!
std::vector<std::string> moves;
while (is >> token)
if (token == "searchmoves") // Needs to be the last command on the line
while (is >> token)
limits.searchmoves.push_back(UCI::to_move(pos, token));
else if (token == "wtime") is >> limits.time[WHITE];
else if (token == "btime") is >> limits.time[BLACK];
else if (token == "winc") is >> limits.inc[WHITE];
else if (token == "binc") is >> limits.inc[BLACK];
else if (token == "movestogo") is >> limits.movestogo;
else if (token == "depth") is >> limits.depth;
else if (token == "nodes") is >> limits.nodes;
else if (token == "movetime") is >> limits.movetime;
else if (token == "mate") is >> limits.mate;
else if (token == "perft") is >> limits.perft;
else if (token == "infinite") limits.infinite = 1;
else if (token == "ponder") ponderMode = true;
Threads.start_thinking(pos, states, limits, ponderMode);
}
// bench() is called when engine receives the "bench" command. Firstly
// a list of UCI commands is setup according to bench parameters, then
// it is run one by one printing a summary at the end.
void bench(Position& pos, istream& args, StateListPtr& states) {
string token;
uint64_t num, nodes = 0, cnt = 1;
vector<string> list = setup_bench(pos, args);
num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0 || s.find("eval") == 0; });
TimePoint elapsed = now();
for (const auto& cmd : list)
{
istringstream is(cmd);
is >> skipws >> token;
if (token == "go" || token == "eval")
{
cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl;
if (token == "go")
{
go(pos, is, states);
Threads.main()->wait_for_search_finished();
nodes += Threads.nodes_searched();
}
else
trace_eval(pos);
}
else if (token == "setoption") setoption(is);
else if (token == "position") position(pos, is, states);
else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take some while
moves.push_back(token);
}
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
engine.set_position(fen, moves);
}
dbg_print(); // Just before exiting
namespace {
cerr << "\n==========================="
<< "\nTotal time (ms) : " << elapsed
<< "\nNodes searched : " << nodes
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl;
}
struct WinRateParams {
double a;
double b;
};
// The win rate model returns the probability (per mille) of winning given an eval
// and a game-ply. The model fits rather accurately the LTC fishtest statistics.
int win_rate_model(Value v, int ply) {
WinRateParams win_rate_params(const Position& pos) {
// The model captures only up to 240 plies, so limit input (and rescale)
double m = std::min(240, ply) / 64.0;
int material = pos.count<PAWN>() + 3 * pos.count<KNIGHT>() + 3 * pos.count<BISHOP>()
+ 5 * pos.count<ROOK>() + 9 * pos.count<QUEEN>();
// Coefficients of a 3rd order polynomial fit based on fishtest data
// for two parameters needed to transform eval to the argument of a
// logistic function.
double as[] = {-3.68389304, 30.07065921, -60.52878723, 149.53378557};
double bs[] = {-2.0181857, 15.85685038, -29.83452023, 47.59078827};
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
// The fitted model only uses data for material counts in [10, 78], and is anchored at count 58.
double m = std::clamp(material, 10, 78) / 58.0;
// Transform eval to centipawns with limited range
double x = std::clamp(double(100 * v) / PawnValueEg, -2000.0, 2000.0);
// Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model
constexpr double as[] = {-150.77043883, 394.96159472, -321.73403766, 406.15850091};
constexpr double bs[] = {62.33245393, -91.02264855, 45.88486850, 51.63461272};
// Return win rate in per mille (rounded to nearest)
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
}
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
} // namespace
return {a, b};
}
// The win rate model is 1 / (1 + exp((a - eval) / b)), where a = p_a(material) and b = p_b(material).
// It fits the LTC fishtest statistics rather accurately.
int win_rate_model(Value v, const Position& pos) {
/// UCI::loop() waits for a command from stdin, parses it and calls the appropriate
/// function. Also intercepts EOF from stdin to ensure gracefully exiting if the
/// GUI dies unexpectedly. When called with some command line arguments, e.g. to
/// run 'bench', once the command is executed the function returns immediately.
/// In addition to the UCI ones, also some additional debug commands are supported.
auto [a, b] = win_rate_params(pos);
void UCI::loop(int argc, char* argv[]) {
// Return the win rate in per mille units, rounded to the nearest integer.
return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b)));
}
}
Position pos;
string token, cmd;
StateListPtr states(new std::deque<StateInfo>(1));
std::string UCIEngine::format_score(const Score& s) {
constexpr int TB_CP = 20000;
const auto format =
overload{[](Score::Mate mate) -> std::string {
auto m = (mate.plies > 0 ? (mate.plies + 1) : mate.plies) / 2;
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));
},
[](Score::InternalUnits units) -> std::string {
return std::string("cp ") + std::to_string(units.value);
}};
pos.set(StartFEN, false, &states->back(), Threads.main());
return s.visit(format);
}
for (int i = 1; i < argc; ++i)
cmd += std::string(argv[i]) + " ";
// Turns a Value to an integer centipawn number,
// without treatment of mate and similar special scores.
int UCIEngine::to_cp(Value v, const Position& pos) {
do {
if (argc == 1 && !getline(cin, cmd)) // Block here waiting for input or EOF
cmd = "quit";
// In general, the score can be defined via the the WDL as
// (log(1/L - 1) - log(1/W - 1)) / ((log(1/L - 1) + log(1/W - 1))
// Based on our win_rate_model, this simply yields v / a.
istringstream is(cmd);
auto [a, b] = win_rate_params(pos);
token.clear(); // Avoid a stale if getline() returns empty or blank line
is >> skipws >> token;
return std::round(100 * int(v) / a);
}
if ( token == "quit"
|| token == "stop")
Threads.stop = true;
std::string UCIEngine::wdl(Value v, const Position& pos) {
std::stringstream ss;
// The GUI sends 'ponderhit' to tell us the user has played the expected move.
// So 'ponderhit' will be sent if we were told to ponder on the same move the
// user has played. We should continue searching but switch from pondering to
// normal search.
else if (token == "ponderhit")
Threads.main()->ponder = false; // Switch to normal search
int wdl_w = win_rate_model(v, pos);
int wdl_l = win_rate_model(-v, pos);
int wdl_d = 1000 - wdl_w - wdl_l;
ss << wdl_w << " " << wdl_d << " " << wdl_l;
else if (token == "uci")
sync_cout << "id name " << engine_info(true)
<< "\n" << Options
<< "\nuciok" << sync_endl;
return ss.str();
}
else if (token == "setoption") setoption(is);
else if (token == "go") go(pos, is, states);
else if (token == "position") position(pos, is, states);
else if (token == "ucinewgame") Search::clear();
else if (token == "isready") sync_cout << "readyok" << sync_endl;
std::string UCIEngine::square(Square s) {
return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};
}
// Additional custom non-UCI commands, mainly for debugging.
// Do not use these commands during a search!
else if (token == "flip") pos.flip();
else if (token == "bench") bench(pos, is, states);
else if (token == "d") sync_cout << pos << sync_endl;
else if (token == "eval") trace_eval(pos);
else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
else if (token == "export_net")
{
std::optional<std::string> filename;
std::string f;
if (is >> skipws >> f)
filename = f;
Eval::NNUE::save_eval(filename);
}
else if (!token.empty() && token[0] != '#')
sync_cout << "Unknown command: " << cmd << sync_endl;
std::string UCIEngine::move(Move m, bool chess960) {
if (m == Move::none())
return "(none)";
} while (token != "quit" && argc == 1); // Command line args are one-shot
if (m == Move::null())
return "0000";
Square from = m.from_sq();
Square to = m.to_sq();
if (m.type_of() == CASTLING && !chess960)
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
std::string move = square(from) + square(to);
if (m.type_of() == PROMOTION)
move += " pnbrqk"[m.promotion_type()];
return move;
}
/// UCI::value() converts a Value to a string suitable for use with the UCI
/// protocol specification:
///
/// cp <x> The score from the engine's point of view in centipawns.
/// mate <y> Mate in y moves, not plies. If the engine is getting mated
/// use negative values for y.
std::string UCIEngine::to_lower(std::string str) {
std::transform(str.begin(), str.end(), str.begin(), [](auto c) { return std::tolower(c); });
string UCI::value(Value v) {
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
stringstream ss;
if (abs(v) < VALUE_MATE_IN_MAX_PLY)
ss << "cp " << v * 100 / PawnValueEg;
else
ss << "mate " << (v > 0 ? VALUE_MATE - v + 1 : -VALUE_MATE - v) / 2;
return ss.str();
return str;
}
Move UCIEngine::to_move(const Position& pos, std::string str) {
str = to_lower(str);
/// UCI::wdl() report WDL statistics given an evaluation and a game ply, based on
/// data gathered for fishtest LTC games.
for (const auto& m : MoveList<LEGAL>(pos))
if (str == move(m, pos.is_chess960()))
return m;
string UCI::wdl(Value v, int ply) {
stringstream ss;
int wdl_w = win_rate_model( v, ply);
int wdl_l = win_rate_model(-v, ply);
int wdl_d = 1000 - wdl_w - wdl_l;
ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l;
return ss.str();
return Move::none();
}
/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.)
std::string UCI::square(Square s) {
return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) };
void UCIEngine::on_update_no_moves(const Engine::InfoShort& info) {
sync_cout << "info depth " << info.depth << " score " << format_score(info.score) << sync_endl;
}
void UCIEngine::on_update_full(const Engine::InfoFull& info, bool showWDL) {
std::stringstream ss;
/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q).
/// The only special case is castling, where we print in the e1g1 notation in
/// normal chess mode, and in e1h1 notation in chess960 mode. Internally all
/// castling moves are always encoded as 'king captures rook'.
ss << "info";
ss << " depth " << info.depth //
<< " seldepth " << info.selDepth //
<< " multipv " << info.multiPV //
<< " score " << format_score(info.score); //
string UCI::move(Move m, bool chess960) {
if (showWDL)
ss << " wdl " << info.wdl;
Square from = from_sq(m);
Square to = to_sq(m);
if (!info.bound.empty())
ss << " " << info.bound;
if (m == MOVE_NONE)
return "(none)";
ss << " nodes " << info.nodes //
<< " nps " << info.nps //
<< " hashfull " << info.hashfull //
<< " tbhits " << info.tbHits //
<< " time " << info.timeMs //
<< " pv " << info.pv; //
if (m == MOVE_NULL)
return "0000";
if (type_of(m) == CASTLING && !chess960)
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
string move = UCI::square(from) + UCI::square(to);
if (type_of(m) == PROMOTION)
move += " pnbrqk"[promotion_type(m)];
return move;
sync_cout << ss.str() << sync_endl;
}
void UCIEngine::on_iter(const Engine::InfoIter& info) {
std::stringstream ss;
/// UCI::to_move() converts a string representing a move in coordinate notation
/// (g1f3, a7a8q) to the corresponding legal Move, if any.
ss << "info";
ss << " depth " << info.depth //
<< " currmove " << info.currmove //
<< " currmovenumber " << info.currmovenumber; //
Move UCI::to_move(const Position& pos, string& str) {
if (str.length() == 5) // Junior could send promotion piece in uppercase
str[4] = char(tolower(str[4]));
for (const auto& m : MoveList<LEGAL>(pos))
if (str == UCI::move(m, pos.is_chess960()))
return m;
return MOVE_NONE;
sync_cout << ss.str() << sync_endl;
}
} // namespace Stockfish
void UCIEngine::on_bestmove(std::string_view bestmove, std::string_view ponder) {
sync_cout << "bestmove " << bestmove;
if (!ponder.empty())
std::cout << " ponder " << ponder;
std::cout << sync_endl;
}
} // namespace Stockfish

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -19,67 +19,57 @@
#ifndef UCI_H_INCLUDED
#define UCI_H_INCLUDED
#include <map>
#include <iostream>
#include <string>
#include <string_view>
#include <cstdint>
#include "types.h"
#include "engine.h"
#include "misc.h"
#include "search.h"
namespace Stockfish {
class Position;
class Move;
class Score;
enum Square : int;
using Value = int;
namespace UCI {
class UCIEngine {
public:
UCIEngine(int argc, char** argv);
class Option;
void loop();
/// Custom comparator because UCI options should be case insensitive
struct CaseInsensitiveLess {
bool operator() (const std::string&, const std::string&) const;
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 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);
auto& engine_options() { return engine.get_options(); }
private:
Engine engine;
CommandLine cli;
void go(std::istringstream& is);
void bench(std::istream& args);
void position(std::istringstream& is);
void setoption(std::istringstream& is);
std::uint64_t 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);
static void on_iter(const Engine::InfoIter& info);
static void on_bestmove(std::string_view bestmove, std::string_view ponder);
};
/// Our options container is actually a std::map
typedef std::map<std::string, Option, CaseInsensitiveLess> OptionsMap;
} // namespace Stockfish
/// Option class implements an option as defined by UCI protocol
class Option {
typedef void (*OnChange)(const Option&);
public:
Option(OnChange = nullptr);
Option(bool v, OnChange = nullptr);
Option(const char* v, OnChange = nullptr);
Option(double v, int minv, int maxv, OnChange = nullptr);
Option(const char* v, const char* cur, OnChange = nullptr);
Option& operator=(const std::string&);
void operator<<(const Option&);
operator double() const;
operator std::string() const;
bool operator==(const char*) const;
private:
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
std::string defaultValue, currentValue, type;
int min, max;
size_t idx;
OnChange on_change;
};
void init(OptionsMap&);
void loop(int argc, char* argv[]);
std::string value(Value v);
std::string square(Square s);
std::string move(Move m, bool chess960);
std::string pv(const Position& pos, Depth depth, Value alpha, Value beta);
std::string wdl(Value v, int ply);
Move to_move(const Position& pos, std::string& str);
} // namespace UCI
extern UCI::OptionsMap Options;
} // namespace Stockfish
#endif // #ifndef UCI_H_INCLUDED
#endif // #ifndef UCI_H_INCLUDED

View file

@ -1,6 +1,6 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2022 The Stockfish developers (see AUTHORS file)
Copyright (C) 2004-2024 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
@ -16,179 +16,170 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ucioption.h"
#include <algorithm>
#include <cassert>
#include <ostream>
#include <cctype>
#include <iostream>
#include <sstream>
#include <utility>
#include "evaluate.h"
#include "misc.h"
#include "search.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
#include "syzygy/tbprobe.h"
using std::string;
namespace Stockfish {
UCI::OptionsMap Options; // Global object
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const {
namespace UCI {
/// 'On change' actions, triggered by an option's value change
void on_clear_hash(const Option&) { Search::clear(); }
void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
void on_logger(const Option& o) { start_logger(o); }
void on_threads(const Option& o) { Threads.set(size_t(o)); }
void on_tb_path(const Option& o) { Tablebases::init(o); }
void on_use_NNUE(const Option& ) { Eval::NNUE::init(); }
void on_eval_file(const Option& ) { Eval::NNUE::init(); }
/// Our case insensitive less() function as required by UCI protocol
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(),
[](char c1, char c2) { return tolower(c1) < tolower(c2); });
return std::lexicographical_compare(
s1.begin(), s1.end(), s2.begin(), s2.end(),
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
}
void OptionsMap::setoption(std::istringstream& is) {
std::string token, name, value;
/// UCI::init() initializes the UCI options to their hard-coded default values
is >> token; // Consume the "name" token
void init(OptionsMap& o) {
// Read the option name (can contain spaces)
while (is >> token && token != "value")
name += (name.empty() ? "" : " ") + token;
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
// Read the option value (can contain spaces)
while (is >> token)
value += (value.empty() ? "" : " ") + token;
o["Debug Log File"] << Option("", on_logger);
o["Threads"] << Option(1, 1, 512, on_threads);
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
o["Clear Hash"] << Option(on_clear_hash);
o["Ponder"] << Option(false);
o["MultiPV"] << Option(1, 1, 500);
o["Skill Level"] << Option(20, 0, 20);
o["Move Overhead"] << Option(10, 0, 5000);
o["Slow Mover"] << Option(100, 10, 1000);
o["nodestime"] << Option(0, 0, 10000);
o["UCI_Chess960"] << Option(false);
o["UCI_AnalyseMode"] << Option(false);
o["UCI_LimitStrength"] << Option(false);
o["UCI_Elo"] << Option(1350, 1350, 2850);
o["UCI_ShowWDL"] << Option(false);
o["SyzygyPath"] << Option("<empty>", on_tb_path);
o["SyzygyProbeDepth"] << Option(1, 1, 100);
o["Syzygy50MoveRule"] << Option(true);
o["SyzygyProbeLimit"] << Option(7, 0, 7);
o["Use NNUE"] << Option(true, on_use_NNUE);
o["EvalFile"] << Option(EvalFileDefaultName, on_eval_file);
if (options_map.count(name))
options_map[name] = value;
else
sync_cout << "No such option: " << name << sync_endl;
}
/// operator<<() is used to print all the options default values in chronological
/// insertion order (the idx field) and in the format defined by the UCI protocol.
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
for (size_t idx = 0; idx < om.size(); ++idx)
for (const auto& it : om)
if (it.second.idx == idx)
{
const Option& o = it.second;
os << "\noption name " << it.first << " type " << o.type;
if (o.type == "string" || o.type == "check" || o.type == "combo")
os << " default " << o.defaultValue;
if (o.type == "spin")
os << " default " << int(stof(o.defaultValue))
<< " min " << o.min
<< " max " << o.max;
break;
}
return os;
Option OptionsMap::operator[](const std::string& name) const {
auto it = options_map.find(name);
return it != options_map.end() ? it->second : Option();
}
Option& OptionsMap::operator[](const std::string& name) { return options_map[name]; }
/// Option class constructors and conversion operators
std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); }
Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f)
{ defaultValue = currentValue = v; }
Option::Option(const char* v, OnChange f) :
type("string"),
min(0),
max(0),
on_change(std::move(f)) {
defaultValue = currentValue = v;
}
Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f)
{ defaultValue = currentValue = (v ? "true" : "false"); }
Option::Option(bool v, OnChange f) :
type("check"),
min(0),
max(0),
on_change(std::move(f)) {
defaultValue = currentValue = (v ? "true" : "false");
}
Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f)
{}
Option::Option(OnChange f) :
type("button"),
min(0),
max(0),
on_change(std::move(f)) {}
Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f)
{ defaultValue = currentValue = std::to_string(v); }
Option::Option(double v, int minv, int maxv, OnChange f) :
type("spin"),
min(minv),
max(maxv),
on_change(std::move(f)) {
defaultValue = currentValue = std::to_string(v);
}
Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f)
{ defaultValue = v; currentValue = cur; }
Option::Option(const char* v, const char* cur, OnChange f) :
type("combo"),
min(0),
max(0),
on_change(std::move(f)) {
defaultValue = v;
currentValue = cur;
}
Option::operator double() const {
assert(type == "check" || type == "spin");
return (type == "spin" ? stof(currentValue) : currentValue == "true");
Option::operator int() const {
assert(type == "check" || type == "spin");
return (type == "spin" ? std::stoi(currentValue) : currentValue == "true");
}
Option::operator std::string() const {
assert(type == "string");
return currentValue;
assert(type == "string");
return currentValue;
}
bool Option::operator==(const char* s) const {
assert(type == "combo");
return !CaseInsensitiveLess()(currentValue, s)
&& !CaseInsensitiveLess()(s, currentValue);
assert(type == "combo");
return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue);
}
/// operator<<() inits options and assigns idx in the correct printing order
// Inits options and assigns idx in the correct printing order
void Option::operator<<(const Option& o) {
static size_t insert_order = 0;
static size_t insert_order = 0;
*this = o;
idx = insert_order++;
*this = o;
idx = insert_order++;
}
/// operator=() 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
/// from the user by console window, so let's check the bounds anyway.
// 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
// from the user by console window, so let's check the bounds anyway.
Option& Option::operator=(const std::string& v) {
Option& Option::operator=(const string& v) {
assert(!type.empty());
assert(!type.empty());
if ((type != "button" && type != "string" && v.empty())
|| (type == "check" && v != "true" && v != "false")
|| (type == "spin" && (std::stof(v) < min || std::stof(v) > max)))
return *this;
if ( (type != "button" && type != "string" && v.empty())
|| (type == "check" && v != "true" && v != "false")
|| (type == "spin" && (stof(v) < min || stof(v) > max)))
return *this;
if (type == "combo")
{
OptionsMap comboMap; // To have case insensitive compare
std::string token;
std::istringstream ss(defaultValue);
while (ss >> token)
comboMap[token] << Option();
if (!comboMap.count(v) || v == "var")
return *this;
}
if (type == "combo")
{
OptionsMap comboMap; // To have case insensitive compare
string token;
std::istringstream ss(defaultValue);
while (ss >> token)
comboMap[token] << Option();
if (!comboMap.count(v) || v == "var")
return *this;
}
if (type != "button")
currentValue = v;
if (type != "button")
currentValue = v;
if (on_change)
on_change(*this);
if (on_change)
on_change(*this);
return *this;
return *this;
}
} // namespace UCI
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
for (size_t idx = 0; idx < om.options_map.size(); ++idx)
for (const auto& it : om.options_map)
if (it.second.idx == idx)
{
const Option& o = it.second;
os << "\noption name " << it.first << " type " << o.type;
} // namespace Stockfish
if (o.type == "string" || o.type == "check" || o.type == "combo")
os << " default " << o.defaultValue;
if (o.type == "spin")
os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max "
<< o.max;
break;
}
return os;
}
}

81
src/ucioption.h Normal file
View file

@ -0,0 +1,81 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2024 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 UCIOPTION_H_INCLUDED
#define UCIOPTION_H_INCLUDED
#include <cstddef>
#include <functional>
#include <iosfwd>
#include <map>
#include <string>
namespace Stockfish {
// Define a custom comparator, because the UCI options should be case-insensitive
struct CaseInsensitiveLess {
bool operator()(const std::string&, const std::string&) const;
};
class Option;
class OptionsMap {
public:
void setoption(std::istringstream&);
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
Option operator[](const std::string&) const;
Option& operator[](const std::string&);
std::size_t count(const std::string&) const;
private:
// The options container is defined as a std::map
using OptionsStore = std::map<std::string, Option, CaseInsensitiveLess>;
OptionsStore options_map;
};
// The Option class implements each option as specified by the UCI protocol
class Option {
public:
using OnChange = std::function<void(const Option&)>;
Option(OnChange = nullptr);
Option(bool v, OnChange = nullptr);
Option(const char* v, OnChange = nullptr);
Option(double v, int minv, int maxv, OnChange = nullptr);
Option(const char* v, const char* cur, OnChange = nullptr);
Option& operator=(const std::string&);
void operator<<(const Option&);
operator int() const;
operator std::string() const;
bool operator==(const char*) const;
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
private:
std::string defaultValue, currentValue, type;
int min, max;
size_t idx;
OnChange on_change;
};
}
#endif // #ifndef UCIOPTION_H_INCLUDED

View file

@ -1,5 +1,5 @@
#!/bin/bash
# check for errors under valgrind or sanitizers.
# check for errors under Valgrind or sanitizers.
error()
{
@ -14,14 +14,14 @@ case $1 in
echo "valgrind testing started"
prefix=''
exeprefix='valgrind --error-exitcode=42 --errors-for-leak-kinds=all --leak-check=full'
postfix='1>/dev/null'
postfix=''
threads="1"
;;
--valgrind-thread)
echo "valgrind-thread testing started"
prefix=''
exeprefix='valgrind --fair-sched=try --error-exitcode=42'
postfix='1>/dev/null'
postfix=''
threads="2"
;;
--sanitizer-undefined)
@ -64,13 +64,32 @@ EOF
;;
esac
cat << EOF > bench_tmp.epd
Rn6/1rbq1bk1/2p2n1p/2Bp1p2/3Pp1pP/1N2P1P1/2Q1NPB1/6K1 w - - 2 26
rnbqkb1r/ppp1pp2/5n1p/3p2p1/P2PP3/5P2/1PP3PP/RNBQKBNR w KQkq - 0 3
3qnrk1/4bp1p/1p2p1pP/p2bN3/1P1P1B2/P2BQ3/5PP1/4R1K1 w - - 9 28
r4rk1/1b2ppbp/pq4pn/2pp1PB1/1p2P3/1P1P1NN1/1PP3PP/R2Q1RK1 w - - 0 13
EOF
# simple command line testing
for args in "eval" \
"go nodes 1000" \
"go depth 10" \
"go perft 4" \
"go movetime 1000" \
"go wtime 8000 btime 8000 winc 500 binc 500" \
"bench 128 $threads 8 default depth"
"go wtime 1000 btime 1000 winc 0 binc 0" \
"go wtime 1000 btime 1000 winc 0 binc 0" \
"go wtime 1000 btime 1000 winc 0 binc 0 movestogo 5" \
"go movetime 200" \
"go nodes 20000 searchmoves e2e4 d2d4" \
"bench 128 $threads 8 default depth" \
"bench 128 $threads 3 bench_tmp.epd depth" \
"export_net verify.nnue" \
"d" \
"compiler" \
"license" \
"uci"
do
echo "$prefix $exeprefix ./stockfish $args $postfix"
@ -78,14 +97,25 @@ do
done
# verify the generated net equals the base net
network=`./stockfish uci | grep 'option name EvalFile type string default' | awk '{print $NF}'`
echo "Comparing $network to the written verify.nnue"
diff $network verify.nnue
# more general testing, following an uci protocol exchange
cat << EOF > game.exp
set timeout 240
# to correctly catch eof we need the following line
# expect_before timeout { exit 2 } eof { exit 3 }
expect_before timeout { exit 2 }
spawn $exeprefix ./stockfish
expect "Stockfish"
send "uci\n"
expect "uciok"
# send "setoption name Debug Log File value debug.log\n"
send "setoption name Threads value $threads\n"
send "ucinewgame\n"
@ -93,18 +123,121 @@ cat << EOF > game.exp
send "go nodes 1000\n"
expect "bestmove"
send "ucinewgame\n"
send "position startpos moves e2e4 e7e6\n"
send "go nodes 1000\n"
expect "bestmove"
send "ucinewgame\n"
send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n"
send "go depth 10\n"
expect "bestmove"
send "ucinewgame\n"
send "position fen 5rk1/1K4p1/8/8/3B4/8/8/8 b - - 0 1\n"
send "flip\n"
send "go depth 10\n"
expect "bestmove"
send "ucinewgame\n"
send "position startpos\n"
send "go depth 5\n"
expect -re {info depth \d+ seldepth \d+ multipv \d+ score cp \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect "bestmove"
send "ucinewgame\n"
send "setoption name UCI_ShowWDL value true\n"
send "position startpos\n"
send "go depth 9\n"
expect -re {info depth 1 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect -re {info depth 2 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect -re {info depth 3 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect -re {info depth 4 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect -re {info depth 5 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect -re {info depth 6 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect -re {info depth 7 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect -re {info depth 8 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect -re {info depth 9 seldepth \d+ multipv \d+ score cp \d+ wdl \d+ \d+ \d+ nodes \d+ nps \d+ hashfull \d+ tbhits \d+ time \d+ pv}
expect "bestmove"
send "setoption name Clear Hash\n"
send "ucinewgame\n"
send "position fen 5K2/8/2qk4/2nPp3/3r4/6B1/B7/3R4 w - e6\n"
send "go depth 18\n"
expect "score mate 1"
expect "pv d5e6"
expect "bestmove d5e6"
send "ucinewgame\n"
send "position fen 2brrb2/8/p7/Q7/1p1kpPp1/1P1pN1K1/3P4/8 b - -\n"
send "go depth 18\n"
expect "score mate -1"
expect "bestmove"
send "ucinewgame\n"
send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n"
send "go depth 18\n"
expect "score mate 2 * pv c6d7 * f7f5"
expect "bestmove c6d7"
send "ucinewgame\n"
send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n"
send "go mate 2\n"
expect "score mate 2 * pv c6d7"
expect "bestmove c6d7"
send "ucinewgame\n"
send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n"
send "go nodes 10000\n"
expect "score mate 2 * pv c6d7 * f7f5"
expect "bestmove c6d7"
send "ucinewgame\n"
send "position fen 1NR2B2/5p2/5p2/1p1kpp2/1P2rp2/2P1pB2/2P1P1K1/8 b - - \n"
send "go depth 18\n"
expect "score mate -2"
expect "pv d5e6 c8d8"
expect "bestmove d5e6"
send "ucinewgame\n"
send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7 f2f1q\n"
send "go depth 18\n"
expect "score mate 1 * pv f7f5"
expect "bestmove f7f5"
send "ucinewgame\n"
send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - -\n"
send "go depth 18 searchmoves c6d7\n"
expect "score mate 2 * pv c6d7 * f7f5"
expect "bestmove c6d7"
send "ucinewgame\n"
send "position fen 8/5R2/2K1P3/4k3/8/b1PPpp1B/5p2/8 w - - moves c6d7\n"
send "go depth 18 searchmoves e3e2\n"
expect "score mate -1 * pv e3e2 f7f5"
expect "bestmove e3e2"
send "setoption name EvalFile value verify.nnue\n"
send "position startpos\n"
send "go depth 5\n"
expect "bestmove"
send "setoption name MultiPV value 4\n"
send "position startpos\n"
send "go depth 5\n"
expect "bestmove"
send "setoption name Skill Level value 10\n"
send "position startpos\n"
send "go depth 5\n"
expect "bestmove"
send "setoption name Skill Level value 20\n"
send "quit\n"
expect eof
# return error code of the spawned program, useful for valgrind
# return error code of the spawned program, useful for Valgrind
lassign [wait] pid spawnid os_error_flag value
exit \$value
EOF
@ -117,15 +250,35 @@ fi
cat << EOF > syzygy.exp
set timeout 240
# to correctly catch eof we need the following line
# expect_before timeout { exit 2 } eof { exit 3 }
expect_before timeout { exit 2 }
spawn $exeprefix ./stockfish
expect "Stockfish"
send "uci\n"
send "setoption name SyzygyPath value ../tests/syzygy/\n"
expect "info string Found 35 tablebases" {} timeout {exit 1}
expect "info string Found 35 tablebases"
send "bench 128 1 8 default depth\n"
expect "Nodes searched :"
send "ucinewgame\n"
send "position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1\n"
send "go depth 5\n"
expect -re {score cp 20000|score mate}
expect "bestmove"
send "ucinewgame\n"
send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1\n"
send "go depth 5\n"
expect -re {score cp 20000|score mate}
expect "bestmove"
send "ucinewgame\n"
send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 b - - 0 1\n"
send "go depth 5\n"
expect -re {score cp -20000|score mate}
expect "bestmove"
send "quit\n"
expect eof
# return error code of the spawned program, useful for valgrind
# return error code of the spawned program, useful for Valgrind
lassign [wait] pid spawnid os_error_flag value
exit \$value
EOF
@ -133,6 +286,9 @@ EOF
for exp in game.exp syzygy.exp
do
echo "======== $exp =============="
cat $exp
echo "============================"
echo "$prefix expect $exp $postfix"
eval "$prefix expect $exp $postfix"
@ -140,6 +296,6 @@ do
done
rm -f tsan.supp
rm -f tsan.supp bench_tmp.epd
echo "instrumented testing OK"

View file

@ -11,7 +11,7 @@ trap 'error ${LINENO}' ERR
# obtain
signature=`./stockfish bench 2>&1 | grep "Nodes searched : " | awk '{print $4}'`
signature=`eval "$WINE_PATH ./stockfish bench 2>&1" | grep "Nodes searched : " | awk '{print $4}'`
if [ $# -gt 0 ]; then
# compare to given reference