mirror of
https://github.com/opelly27/Stockfish.git
synced 2026-05-20 13:17:44 +00:00
Compare commits
381 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4ac3d6b96 | |||
| 6c19bec86e | |||
| 0716b845fd | |||
| c964942da2 | |||
| ec598b380d | |||
| 68d58d94da | |||
| e13e4cfb83 | |||
| 0ef5d05102 | |||
| e636f73ab8 | |||
| ed24e3a0a6 | |||
| d49b3738bc | |||
| 7998570414 | |||
| 5001d49f42 | |||
| d99f89506b | |||
| 7e427639ce | |||
| 8e61d70499 | |||
| 1a6c22c511 | |||
| 9b92ada935 | |||
| 117e08c264 | |||
| 134e6d7bb4 | |||
| ed60460004 | |||
| fb07281f55 | |||
| abd82396a1 | |||
| 23493de082 | |||
| ee2ee6bdc4 | |||
| 55df0ee009 | |||
| 627974c99f | |||
| daa3ef9148 | |||
| 1a26d698de | |||
| f072634e24 | |||
| 10e2732978 | |||
| b6dfd6bd54 | |||
| 632f1c21cd | |||
| 0f01a516d2 | |||
| 748791f80d | |||
| 6136d094c5 | |||
| 1db969e620 | |||
| bd579ab5d1 | |||
| a96b0d4609 | |||
| a615efb19f | |||
| b0ac8a4e3b | |||
| 6d0d430860 | |||
| 7831131591 | |||
| 0a3eb1d8fa | |||
| f77eddfa2f | |||
| 0c22d5bb1a | |||
| c83c7f4e71 | |||
| abcc090a62 | |||
| e67cc979fd | |||
| 5c2b385957 | |||
| bec83a1869 | |||
| d07033d5da | |||
| fc41f64dfd | |||
| 8e75548f2a | |||
| f3df0cfb84 | |||
| 9d61822b5d | |||
| bf2c7306ac | |||
| f4f0b32d55 | |||
| 5c03883107 | |||
| 7ccde25baf | |||
| c115e5171e | |||
| 531747ee78 | |||
| 91a4cea437 | |||
| 9068fdc57b | |||
| 3d5b16df7c | |||
| 21dff6c276 | |||
| 9699f4f79a | |||
| 96837bc439 | |||
| 15093d43c4 | |||
| c0107b3c27 | |||
| f2984471c9 | |||
| a20726eb0b | |||
| 59691d46a1 | |||
| ededadcd6f | |||
| e815227c30 | |||
| f2b6b5cfc9 | |||
| 56b342f9b2 | |||
| 3cce4c4cf4 | |||
| 16afec0582 | |||
| 13eb023fc0 | |||
| fcbb02ffde | |||
| c17ec9524d | |||
| 37bd1e774e | |||
| 1dfbde2d10 | |||
| 3d49a99aaf | |||
| 2b62c4452d | |||
| a6fd17f27d | |||
| a901474bf9 | |||
| ad9fcbc496 | |||
| e860f620aa | |||
| aa15a9179b | |||
| 856e60d12f | |||
| c8bc2ce4fa | |||
| 9a9702d668 | |||
| 0fbad56c50 | |||
| 6c02329860 | |||
| 0c7f56dea6 | |||
| f15e4f50aa | |||
| a5675f19d8 | |||
| 32e46fc47f | |||
| b5e8169a85 | |||
| 88331add0d | |||
| 12e97701b2 | |||
| cf5b070913 | |||
| eec361f64c | |||
| 3372ee9c26 | |||
| a107910951 | |||
| 6deb88728f | |||
| 99cdb920fc | |||
| 7c5e3f2865 | |||
| f09adaa4a4 | |||
| 584d9efedc | |||
| a5a76a6370 | |||
| 19f9a197be | |||
| 6f9071c643 | |||
| a5f7386efb | |||
| 8b4583bce7 | |||
| b987d4f033 | |||
| 4930892985 | |||
| cafbe8e8e8 | |||
| 28f8663f39 | |||
| 5546bc0a26 | |||
| 444f03ee95 | |||
| a25f48a236 | |||
| 154abb337e | |||
| 0fca5605fa | |||
| 3cfaef7431 | |||
| b4d995d0d9 | |||
| 4ff297a6df | |||
| 1fe562fdf3 | |||
| 833a2e2bc0 | |||
| 4f99dfcae2 | |||
| 1a69efbb40 | |||
| f12035c88c | |||
| bab1cc300c | |||
| f388e41809 | |||
| 3f5adc037e | |||
| fbdf5d94a9 | |||
| 358a853790 | |||
| 9be0360aa4 | |||
| a069a1bbbf | |||
| 07a2619b62 | |||
| d9ec82e743 | |||
| c53d2ec253 | |||
| 536d692a30 | |||
| 9fc064e872 | |||
| cdfafb3426 | |||
| 7885fa5bd3 | |||
| 282e15bf75 | |||
| 36db936e76 | |||
| 8724503d9c | |||
| 53ad6d23b0 | |||
| dadff46986 | |||
| 7a8bcfc229 | |||
| 0ff2ea6549 | |||
| 8f65953583 | |||
| 08cdbca56f | |||
| 15d47a2b38 | |||
| 85403a89ba | |||
| 7dc40ac643 | |||
| 883163395e | |||
| f17db4641e | |||
| 757ae2ff53 | |||
| 13426a93c1 | |||
| b4e9ee72e3 | |||
| b59786e750 | |||
| 504bf0e8b8 | |||
| 7970236e9e | |||
| d89217766b | |||
| f9d8717844 | |||
| 863a1f2b4c | |||
| fbc6b27505 | |||
| 80b0e37543 | |||
| d0e87104aa | |||
| 442c294a07 | |||
| d4b46ea6db | |||
| 791419aab5 | |||
| 7f97ba775e | |||
| b4b704e686 | |||
| 1cb9afbdc0 | |||
| 101d2bb8ea | |||
| e277dda716 | |||
| 908811c24a | |||
| 38aa70adcf | |||
| 347d613b0e | |||
| 08ed4c90db | |||
| d30af4f669 | |||
| b0658f09b9 | |||
| 871ab55f01 | |||
| 0024133b08 | |||
| ec02714b62 | |||
| d6a5c2b085 | |||
| 49ece9f791 | |||
| cf3dbcb5ac | |||
| a105978bbd | |||
| b187622233 | |||
| 40c6a84434 | |||
| b7b7800e2b | |||
| 2d0237db3f | |||
| 8366ec48ae | |||
| e18619d078 | |||
| 90c18b0b50 | |||
| d3d0c69dc1 | |||
| 057046cc9a | |||
| edb4ab924f | |||
| fe53a18f7a | |||
| a4fedd8152 | |||
| 38e830af4b | |||
| 002636362e | |||
| 7a4de96159 | |||
| f7fbc6880e | |||
| 25d444ed60 | |||
| 008d59512a | |||
| c17a657b04 | |||
| 040dfedb34 | |||
| f1ce1cd475 | |||
| 8a912951de | |||
| afe7f4d9b0 | |||
| 660da1ca7b | |||
| 4f0fecad8a | |||
| 31d0b7fe93 | |||
| 243f7b264a | |||
| 9739ed7a97 | |||
| ce99b4b2ef | |||
| 22cdb6c1ea | |||
| 70ba9de85c | |||
| 154b8d3ecb | |||
| 95fe2b9a9d | |||
| fce4cc1829 | |||
| 952740b36c | |||
| e594aa7429 | |||
| 708319a433 | |||
| 97f706ecc1 | |||
| 0e32287af4 | |||
| 3f7fb5ac1d | |||
| b9319c4fa4 | |||
| 3d1b067d85 | |||
| ef22829616 | |||
| 6d85f43e26 | |||
| 46a5cedc11 | |||
| 1461d861c8 | |||
| a8b4fd1671 | |||
| b25d68f6ee | |||
| adf29b3fd6 | |||
| 1f7ff8406d | |||
| 4f7fe255c7 | |||
| 3c0e86a91e | |||
| 8cd5cbf693 | |||
| 4c4cb185aa | |||
| 440feecb4d | |||
| 030b87182a | |||
| c6f62363a6 | |||
| 4c5919fa95 | |||
| fe7353f702 | |||
| 9abef246a9 | |||
| a9a0dbbcd0 | |||
| 46756996e7 | |||
| fe0dca12f1 | |||
| 3e5a817fd2 | |||
| a77a8448ff | |||
| 9b80897657 | |||
| 3322349c1a | |||
| 495852fecd | |||
| c02ee70927 | |||
| 796d9df643 | |||
| 84e97a38a3 | |||
| b7b7a3f3fa | |||
| 222f3ea55b | |||
| d97a02ea2b | |||
| 4be94f41a6 | |||
| 8192945870 | |||
| 0d2ddb81ef | |||
| e64b817e0a | |||
| 5c2111cc30 | |||
| a26f8d37e1 | |||
| 0ad9b51dea | |||
| a6d9a302b8 | |||
| 4c43e1e27c | |||
| 002a50457c | |||
| 65ece7d985 | |||
| f84eb1f3ef | |||
| cb22520a9c | |||
| 2667316ffc | |||
| 027713c4b4 | |||
| 76e1e8fd39 | |||
| 78e3d2ad78 | |||
| 4b2979760f | |||
| 5ea1cbc778 | |||
| 1444837887 | |||
| 3fe0d5c533 | |||
| 6abd0bd9fb | |||
| 42d28424bc | |||
| 6a31f54d3c | |||
| 34d0c1b527 | |||
| d70a905ce3 | |||
| 913574f421 | |||
| 18fdc2df3c | |||
| e8a5c64988 | |||
| e89469925d | |||
| a42ab95e1f | |||
| ee53f8ed2f | |||
| a3a91f3f9f | |||
| acdbf45171 | |||
| f5ab5832c6 | |||
| 529d3be8e2 | |||
| f972947492 | |||
| af110e02ec | |||
| 6a8767a0d5 | |||
| ee023d7fd7 | |||
| e699fee513 | |||
| 9ba24912c1 | |||
| f8e65d82eb | |||
| 19e2a88504 | |||
| 8c4ac26c8e | |||
| a2b24e0030 | |||
| e87e103ca9 | |||
| ca5d9a5ff0 | |||
| 95ce443aaa | |||
| 9cd563cb54 | |||
| 5f8480a730 | |||
| eb9aaf9489 | |||
| fa143922ae | |||
| 80564bcfcd | |||
| 8634717c64 | |||
| 9a2d50eccc | |||
| 915532181f | |||
| e551964ef6 | |||
| e355c70594 | |||
| ef94f77f8c | |||
| 0fd186fb28 | |||
| 66b4e7f080 | |||
| 04a0be956d | |||
| f327096cfb | |||
| e592dcb8e3 | |||
| 80eae02603 | |||
| 0ad7de3182 | |||
| e0b4dc24c8 | |||
| b79ec2e0b2 | |||
| 43c887d367 | |||
| 3a187b863b | |||
| 5fcd0e6f2a | |||
| 19129473f2 | |||
| b706b91bb1 | |||
| 8ec5faa46e | |||
| 8a9d269855 | |||
| 0b3c13107a | |||
| 669074672c | |||
| 0fd0e4e849 | |||
| 85327828c9 | |||
| 4cdb6386d8 | |||
| 982880bd70 | |||
| bf17a410ec | |||
| 5e7777e9d0 | |||
| 10a920d7d7 | |||
| 21819b7bf8 | |||
| 8c4338ae49 | |||
| 8a3f8e21ae | |||
| 267ca781cd | |||
| ac43bef5c5 | |||
| 7a32d26d5f | |||
| fb5c1f5bf5 | |||
| 87f0fa55a0 | |||
| 2f882309d5 | |||
| 86953b9392 | |||
| ba1c639836 | |||
| e526c5aa52 | |||
| 9cd2c817db | |||
| 54a0a228f6 | |||
| 1cd2c7861a | |||
| 7af3f4da7a | |||
| 271181bb31 | |||
| 66b2c6b9f1 | |||
| 2559c20c6e | |||
| 2659c407c4 | |||
| 3730ae1efb | |||
| 0d6cdc0c6d | |||
| 80afeb0d3b | |||
| 2405b38165 | |||
| 8a95d269eb | |||
| 3b7b632aa5 | |||
| 29c166a072 |
@@ -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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# .git-blame-ignore-revs
|
||||||
|
# Ignore commit which added clang-format
|
||||||
|
2d0237db3f0e596fb06e3ffbadba84dcc4e018f6
|
||||||
|
|
||||||
|
# Post commit formatting fixes
|
||||||
|
0fca5605fa2e5e7240fde5e1aae50952b2612231
|
||||||
|
08ed4c90db31959521b7ef3186c026edd1e90307
|
||||||
@@ -2,7 +2,7 @@ blank_issues_enabled: false
|
|||||||
contact_links:
|
contact_links:
|
||||||
- name: Discord server
|
- name: Discord server
|
||||||
url: https://discord.gg/GWDRS3kU6R
|
url: https://discord.gg/GWDRS3kU6R
|
||||||
about: Feel free to ask for support or have a chat with us in our Discord server!
|
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...
|
- name: Discussions, Q&A, ideas, show us something...
|
||||||
url: https://github.com/official-stockfish/Stockfish/discussions/new
|
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!
|
about: Do you have an idea for Stockfish? Do you want to show something that you made? Please open a discussion about it!
|
||||||
|
|||||||
@@ -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++"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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 ] },
|
||||||
|
]
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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:
|
||||||
|
COMPILER: ${{ 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
|
||||||
|
$COMPILER -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: .
|
||||||
@@ -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
|
||||||
@@ -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}}"
|
||||||
@@ -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:
|
||||||
|
COMPILER: ${{ 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: $COMPILER -v
|
||||||
|
|
||||||
|
- name: Test help target
|
||||||
|
run: make help
|
||||||
|
|
||||||
|
- name: Check git
|
||||||
|
run: git --version
|
||||||
|
|
||||||
|
- name: Check compiler
|
||||||
|
run: $COMPILER -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: .
|
||||||
@@ -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"
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
name: Stockfish
|
name: Sanitizers
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
jobs:
|
jobs:
|
||||||
Stockfish:
|
Test-under-sanitizers:
|
||||||
name: ${{ matrix.sanitizers.name }}
|
name: ${{ matrix.sanitizers.name }}
|
||||||
runs-on: ${{ matrix.config.os }}
|
runs-on: ${{ matrix.config.os }}
|
||||||
env:
|
env:
|
||||||
@@ -10,13 +10,14 @@ jobs:
|
|||||||
COMP: ${{ matrix.config.comp }}
|
COMP: ${{ matrix.config.comp }}
|
||||||
CXXFLAGS: "-Werror"
|
CXXFLAGS: "-Werror"
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
config:
|
config:
|
||||||
- name: Ubuntu 20.04 GCC
|
- name: Ubuntu 22.04 GCC
|
||||||
os: ubuntu-20.04
|
os: ubuntu-22.04
|
||||||
compiler: g++
|
compiler: g++
|
||||||
comp: gcc
|
comp: gcc
|
||||||
shell: bash {0}
|
shell: bash
|
||||||
sanitizers:
|
sanitizers:
|
||||||
- name: Run with thread sanitizer
|
- name: Run with thread sanitizer
|
||||||
make_option: sanitize=thread
|
make_option: sanitize=thread
|
||||||
@@ -35,9 +36,7 @@ jobs:
|
|||||||
working-directory: src
|
working-directory: src
|
||||||
shell: ${{ matrix.config.shell }}
|
shell: ${{ matrix.config.shell }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download required linux packages
|
- name: Download required linux packages
|
||||||
run: |
|
run: |
|
||||||
@@ -62,5 +61,5 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
export CXXFLAGS="-O1 -fno-inline"
|
export CXXFLAGS="-O1 -fno-inline"
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-64-modern ${{ matrix.sanitizers.make_option }} debug=yes optimize=no build > /dev/null
|
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 }}
|
../tests/instrumented.sh --${{ matrix.sanitizers.instrumented_option }}
|
||||||
@@ -2,7 +2,7 @@ name: Stockfish
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- "*"
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
- tools
|
- tools
|
||||||
@@ -13,12 +13,10 @@ on:
|
|||||||
- tools
|
- tools
|
||||||
jobs:
|
jobs:
|
||||||
Prerelease:
|
Prerelease:
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'))
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
# returns null if no pre-release exists
|
# returns null if no pre-release exists
|
||||||
- name: Get Commit SHA of Latest Pre-release
|
- name: Get Commit SHA of Latest Pre-release
|
||||||
@@ -27,25 +25,80 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y curl jq
|
sudo apt-get install -y curl jq
|
||||||
|
|
||||||
echo "COMMIT_SHA=$(jq -r 'map(select(.prerelease)) | first | .tag_name' <<< $(curl -s https://api.github.com/repos/${{ github.repository_owner }}/Stockfish/releases))" >> $GITHUB_ENV
|
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
|
# delete old previous pre-release and tag
|
||||||
- uses: dev-drprasad/delete-tag-and-release@v0.2.1
|
- run: gh release delete ${{ env.COMMIT_SHA_TAG }} --cleanup-tag
|
||||||
if: env.COMMIT_SHA != 'null'
|
if: env.COMMIT_SHA_TAG != 'null'
|
||||||
with:
|
|
||||||
tag_name: ${{ env.COMMIT_SHA }}
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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:
|
||||||
|
name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
|
||||||
|
tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
|
||||||
|
prerelease: true
|
||||||
|
|
||||||
|
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: |
|
||||||
|
TASKS=$(echo $(cat .github/ci/matrix.json) )
|
||||||
|
echo "MATRIX=$TASKS" >> $GITHUB_OUTPUT
|
||||||
|
- id: set-arm-matrix
|
||||||
|
run: |
|
||||||
|
TASKS_ARM=$(echo $(cat .github/ci/arm_matrix.json) )
|
||||||
|
echo "ARM_MATRIX=$TASKS_ARM" >> $GITHUB_OUTPUT
|
||||||
|
Compilation:
|
||||||
|
needs: [Matrix]
|
||||||
|
uses: ./.github/workflows/compilation.yml
|
||||||
|
with:
|
||||||
|
matrix: ${{ needs.Matrix.outputs.matrix }}
|
||||||
|
ARMCompilation:
|
||||||
|
needs: [Matrix]
|
||||||
|
uses: ./.github/workflows/arm_compilation.yml
|
||||||
|
with:
|
||||||
|
matrix: ${{ needs.Matrix.outputs.arm_matrix }}
|
||||||
|
IWYU:
|
||||||
|
uses: ./.github/workflows/iwyu.yml
|
||||||
Sanitizers:
|
Sanitizers:
|
||||||
uses: ./.github/workflows/stockfish_sanitizers.yml
|
uses: ./.github/workflows/sanitizers.yml
|
||||||
Tests:
|
Tests:
|
||||||
uses: ./.github/workflows/stockfish_test.yml
|
uses: ./.github/workflows/tests.yml
|
||||||
Compiles:
|
|
||||||
uses: ./.github/workflows/stockfish_compile_test.yml
|
|
||||||
Binaries:
|
Binaries:
|
||||||
if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')
|
if: github.repository == 'official-stockfish/Stockfish'
|
||||||
uses: ./.github/workflows/stockfish_binaries.yml
|
needs: [Matrix, Prerelease, Compilation]
|
||||||
|
uses: ./.github/workflows/upload_binaries.yml
|
||||||
|
with:
|
||||||
|
matrix: ${{ needs.Matrix.outputs.matrix }}
|
||||||
ARM_Binaries:
|
ARM_Binaries:
|
||||||
if: github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag')
|
if: github.repository == 'official-stockfish/Stockfish'
|
||||||
uses: ./.github/workflows/stockfish_arm_binaries.yml
|
needs: [Matrix, Prerelease, ARMCompilation]
|
||||||
|
uses: ./.github/workflows/upload_binaries.yml
|
||||||
|
with:
|
||||||
|
matrix: ${{ needs.Matrix.outputs.arm_matrix }}
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
name: Stockfish
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
Stockfish:
|
|
||||||
name: ${{ matrix.config.name }} ${{ matrix.binaries }}
|
|
||||||
runs-on: ${{ matrix.config.os }}
|
|
||||||
env:
|
|
||||||
COMPILER: ${{ matrix.config.compiler }}
|
|
||||||
COMP: ${{ matrix.config.comp }}
|
|
||||||
EMU: ${{ matrix.config.emu }}
|
|
||||||
EXT: ${{ matrix.config.ext }}
|
|
||||||
OS: ${{ matrix.config.os }}
|
|
||||||
BINARY: ${{ matrix.binaries }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
config:
|
|
||||||
- name: Android NDK aarch64
|
|
||||||
os: ubuntu-22.04
|
|
||||||
compiler: aarch64-linux-android21-clang++
|
|
||||||
emu: qemu-aarch64
|
|
||||||
comp: ndk
|
|
||||||
shell: bash {0}
|
|
||||||
- name: Android NDK arm
|
|
||||||
os: ubuntu-22.04
|
|
||||||
compiler: armv7a-linux-androideabi21-clang++
|
|
||||||
emu: qemu-arm
|
|
||||||
comp: ndk
|
|
||||||
shell: bash {0}
|
|
||||||
binaries:
|
|
||||||
- armv8
|
|
||||||
- armv7
|
|
||||||
- armv7-neon
|
|
||||||
exclude:
|
|
||||||
- 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++}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: src
|
|
||||||
shell: ${{ matrix.config.shell }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
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: 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
|
|
||||||
$COMPILER -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 -j2 profile-build ARCH=$BINARY COMP=$COMP WINE_PATH=$EMU
|
|
||||||
make strip ARCH=$BINARY COMP=$COMP
|
|
||||||
mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT
|
|
||||||
|
|
||||||
- name: Remove non src files
|
|
||||||
run: rm -f *.o .depend *.nnue
|
|
||||||
|
|
||||||
- name: Download wiki
|
|
||||||
run: |
|
|
||||||
git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki
|
|
||||||
cd ../wiki
|
|
||||||
rm -rf .git
|
|
||||||
|
|
||||||
- name: Create tar archive.
|
|
||||||
run: |
|
|
||||||
cd ..
|
|
||||||
mkdir stockfish
|
|
||||||
cp -r wiki stockfish/
|
|
||||||
cp -r src stockfish/
|
|
||||||
cp stockfish-android-$BINARY$EXT stockfish/
|
|
||||||
cp "Top CPU Contributors.txt" stockfish/
|
|
||||||
cp Copying.txt stockfish/
|
|
||||||
cp AUTHORS stockfish/
|
|
||||||
cp CITATION.cff stockfish/
|
|
||||||
cp README.md stockfish/
|
|
||||||
tar -cvf stockfish-android-$BINARY.tar stockfish
|
|
||||||
|
|
||||||
- name: Upload binaries
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: stockfish-android-${{ matrix.binaries }}
|
|
||||||
path: stockfish-android-${{ matrix.binaries }}.tar
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
files: stockfish-android-${{ matrix.binaries }}.tar
|
|
||||||
|
|
||||||
- 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 which 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@v1
|
|
||||||
with:
|
|
||||||
name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
|
|
||||||
tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
|
|
||||||
prerelease: true
|
|
||||||
files: stockfish-android-${{ matrix.binaries }}.tar
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
name: Stockfish
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
Stockfish:
|
|
||||||
name: ${{ matrix.config.name }} ${{ matrix.binaries }}
|
|
||||||
runs-on: ${{ matrix.config.os }}
|
|
||||||
env:
|
|
||||||
COMPILER: ${{ matrix.config.compiler }}
|
|
||||||
COMP: ${{ matrix.config.comp }}
|
|
||||||
EXT: ${{ matrix.config.ext }}
|
|
||||||
NAME: ${{ matrix.config.simple_name }}
|
|
||||||
BINARY: ${{ matrix.binaries }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
config:
|
|
||||||
- name: Ubuntu 20.04 GCC
|
|
||||||
os: ubuntu-20.04
|
|
||||||
simple_name: ubuntu
|
|
||||||
compiler: g++
|
|
||||||
comp: gcc
|
|
||||||
shell: bash {0}
|
|
||||||
archive_ext: tar
|
|
||||||
- name: MacOS 12 Apple Clang
|
|
||||||
os: macos-12
|
|
||||||
simple_name: macos
|
|
||||||
compiler: clang++
|
|
||||||
comp: clang
|
|
||||||
shell: bash {0}
|
|
||||||
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
|
|
||||||
archive_ext: zip
|
|
||||||
binaries:
|
|
||||||
- x86-64
|
|
||||||
- x86-64-modern
|
|
||||||
- x86-64-avx2
|
|
||||||
exclude:
|
|
||||||
- binaries: x86-64-avx2
|
|
||||||
config: {os: macos-12}
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: src
|
|
||||||
shell: ${{ matrix.config.shell }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Download required linux packages
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
run: sudo apt update
|
|
||||||
|
|
||||||
- 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 the used network from the fishtest framework
|
|
||||||
run: make net
|
|
||||||
|
|
||||||
- name: Check compiler
|
|
||||||
run: $COMPILER -v
|
|
||||||
|
|
||||||
- name: Test help target
|
|
||||||
run: make help
|
|
||||||
|
|
||||||
- name: Check git
|
|
||||||
run: git --version
|
|
||||||
|
|
||||||
# Compile profile guided builds
|
|
||||||
|
|
||||||
- name: Compile ${{ matrix.binaries }} build
|
|
||||||
run: |
|
|
||||||
make -j2 profile-build ARCH=$BINARY COMP=$COMP
|
|
||||||
make strip ARCH=$BINARY COMP=$COMP
|
|
||||||
mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT
|
|
||||||
|
|
||||||
- name: Remove non src files
|
|
||||||
run: git clean -fx
|
|
||||||
|
|
||||||
- name: Download wiki
|
|
||||||
run: |
|
|
||||||
git clone https://github.com/official-stockfish/Stockfish.wiki.git ../wiki
|
|
||||||
rm -rf ../wiki/.git
|
|
||||||
|
|
||||||
- name: Create directory.
|
|
||||||
run: |
|
|
||||||
cd ..
|
|
||||||
mkdir stockfish
|
|
||||||
cp -r wiki stockfish/
|
|
||||||
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/
|
|
||||||
|
|
||||||
- name: Create tar
|
|
||||||
if: runner.os != 'Windows'
|
|
||||||
run: |
|
|
||||||
cd ..
|
|
||||||
tar -cvf stockfish-$NAME-$BINARY.tar stockfish
|
|
||||||
|
|
||||||
- name: Create zip
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
run: |
|
|
||||||
cd ..
|
|
||||||
zip -r stockfish-$NAME-$BINARY.zip stockfish
|
|
||||||
|
|
||||||
- name: Upload binaries
|
|
||||||
if: runner.os != 'Windows'
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }}
|
|
||||||
path: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.tar
|
|
||||||
|
|
||||||
# Artifacts automatically get zipped
|
|
||||||
# to avoid double zipping, we use the unzipped directory
|
|
||||||
- name: Upload binaries
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: stockfish-${{ matrix.config.os }}-${{ matrix.binaries }}
|
|
||||||
path: stockfish
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
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 which 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@v1
|
|
||||||
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 }}
|
|
||||||
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
name: Stockfish
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
jobs:
|
|
||||||
Stockfish:
|
|
||||||
name: ${{ matrix.config.name }}
|
|
||||||
runs-on: ${{ matrix.config.os }}
|
|
||||||
env:
|
|
||||||
COMPILER: ${{ matrix.config.compiler }}
|
|
||||||
COMP: ${{ matrix.config.comp }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
config:
|
|
||||||
- name: Ubuntu 20.04 GCC
|
|
||||||
os: ubuntu-20.04
|
|
||||||
compiler: g++
|
|
||||||
comp: gcc
|
|
||||||
shell: bash {0}
|
|
||||||
- name: Ubuntu 20.04 Clang
|
|
||||||
os: ubuntu-20.04
|
|
||||||
compiler: clang++
|
|
||||||
comp: clang
|
|
||||||
shell: bash {0}
|
|
||||||
- name: MacOS 12 Apple Clang
|
|
||||||
os: macos-12
|
|
||||||
compiler: clang++
|
|
||||||
comp: clang
|
|
||||||
shell: bash {0}
|
|
||||||
- name: MacOS 12 GCC 11
|
|
||||||
os: macos-12
|
|
||||||
compiler: g++-11
|
|
||||||
comp: gcc
|
|
||||||
shell: bash {0}
|
|
||||||
- name: Windows 2022 Mingw-w64 GCC x86_64
|
|
||||||
os: windows-2022
|
|
||||||
compiler: g++
|
|
||||||
comp: mingw
|
|
||||||
msys_sys: mingw64
|
|
||||||
msys_env: x86_64-gcc
|
|
||||||
shell: msys2 {0}
|
|
||||||
- name: Windows 2022 Mingw-w64 Clang x86_64
|
|
||||||
os: windows-2022
|
|
||||||
compiler: clang++
|
|
||||||
comp: clang
|
|
||||||
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@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
- name: Download the used network from the fishtest framework
|
|
||||||
run: make net
|
|
||||||
|
|
||||||
- name: Check compiler
|
|
||||||
run: $COMPILER -v
|
|
||||||
|
|
||||||
- name: Test help target
|
|
||||||
run: make help
|
|
||||||
|
|
||||||
- name: Check git
|
|
||||||
run: git --version
|
|
||||||
|
|
||||||
# x86-64 with newer extensions tests
|
|
||||||
|
|
||||||
- name: Compile x86-64-avx2 build
|
|
||||||
run: |
|
|
||||||
make clean
|
|
||||||
make -j2 ARCH=x86-64-avx2 build
|
|
||||||
|
|
||||||
- name: Compile x86-64-bmi2 build
|
|
||||||
run: |
|
|
||||||
make clean
|
|
||||||
make -j2 ARCH=x86-64-bmi2 build
|
|
||||||
|
|
||||||
- name: Compile x86-64-avx512 build
|
|
||||||
run: |
|
|
||||||
make clean
|
|
||||||
make -j2 ARCH=x86-64-avx512 build
|
|
||||||
|
|
||||||
- name: Compile x86-64-vnni512 build
|
|
||||||
run: |
|
|
||||||
make clean
|
|
||||||
make -j2 ARCH=x86-64-vnni512 build
|
|
||||||
|
|
||||||
- name: Compile x86-64-vnni256 build
|
|
||||||
run: |
|
|
||||||
make clean
|
|
||||||
make -j2 ARCH=x86-64-vnni256 build
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
name: Stockfish
|
name: Tests
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
jobs:
|
jobs:
|
||||||
Stockfish:
|
Test-Targets:
|
||||||
name: ${{ matrix.config.name }}
|
name: ${{ matrix.config.name }}
|
||||||
runs-on: ${{ matrix.config.os }}
|
runs-on: ${{ matrix.config.os }}
|
||||||
env:
|
env:
|
||||||
@@ -10,6 +10,7 @@ jobs:
|
|||||||
COMP: ${{ matrix.config.comp }}
|
COMP: ${{ matrix.config.comp }}
|
||||||
CXXFLAGS: "-Werror"
|
CXXFLAGS: "-Werror"
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
config:
|
config:
|
||||||
- name: Ubuntu 20.04 GCC
|
- name: Ubuntu 20.04 GCC
|
||||||
@@ -18,38 +19,61 @@ jobs:
|
|||||||
comp: gcc
|
comp: gcc
|
||||||
run_32bit_tests: true
|
run_32bit_tests: true
|
||||||
run_64bit_tests: true
|
run_64bit_tests: true
|
||||||
shell: bash {0}
|
shell: bash
|
||||||
- name: Ubuntu 20.04 Clang
|
- name: Ubuntu 20.04 Clang
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
compiler: clang++
|
compiler: clang++
|
||||||
comp: clang
|
comp: clang
|
||||||
run_32bit_tests: true
|
run_32bit_tests: true
|
||||||
run_64bit_tests: true
|
run_64bit_tests: true
|
||||||
shell: bash {0}
|
shell: bash
|
||||||
- name: Android NDK aarch64
|
- name: Android NDK aarch64
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
compiler: aarch64-linux-android21-clang++
|
compiler: aarch64-linux-android21-clang++
|
||||||
comp: ndk
|
comp: ndk
|
||||||
run_armv8_tests: true
|
run_armv8_tests: true
|
||||||
shell: bash {0}
|
shell: bash
|
||||||
- name: Android NDK arm
|
- name: Android NDK arm
|
||||||
os: ubuntu-22.04
|
os: ubuntu-22.04
|
||||||
compiler: armv7a-linux-androideabi21-clang++
|
compiler: armv7a-linux-androideabi21-clang++
|
||||||
comp: ndk
|
comp: ndk
|
||||||
run_armv7_tests: true
|
run_armv7_tests: true
|
||||||
shell: bash {0}
|
shell: bash
|
||||||
- name: MacOS 12 Apple Clang
|
- name: Linux GCC riscv64
|
||||||
os: macos-12
|
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++
|
compiler: clang++
|
||||||
comp: clang
|
comp: clang
|
||||||
run_64bit_tests: true
|
run_64bit_tests: true
|
||||||
shell: bash {0}
|
shell: bash
|
||||||
- name: MacOS 12 GCC 11
|
- name: MacOS 14 Apple Clang M1
|
||||||
os: macos-12
|
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
|
compiler: g++-11
|
||||||
comp: gcc
|
comp: gcc
|
||||||
run_64bit_tests: true
|
run_64bit_tests: true
|
||||||
shell: bash {0}
|
shell: bash
|
||||||
- name: Windows 2022 Mingw-w64 GCC x86_64
|
- name: Windows 2022 Mingw-w64 GCC x86_64
|
||||||
os: windows-2022
|
os: windows-2022
|
||||||
compiler: g++
|
compiler: g++
|
||||||
@@ -79,7 +103,7 @@ jobs:
|
|||||||
working-directory: src
|
working-directory: src
|
||||||
shell: ${{ matrix.config.shell }}
|
shell: ${{ matrix.config.shell }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -87,7 +111,7 @@ jobs:
|
|||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install expect valgrind g++-multilib qemu-user
|
sudo apt install expect valgrind g++-multilib qemu-user-static
|
||||||
|
|
||||||
- name: Install NDK
|
- name: Install NDK
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
@@ -103,6 +127,28 @@ jobs:
|
|||||||
echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV
|
echo "ANDROID_NDK_BIN=$ANDROID_NDK_BIN" >> $GITHUB_ENV
|
||||||
fi
|
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
|
- name: Setup msys and install required packages
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
uses: msys2/setup-msys2@v2
|
uses: msys2/setup-msys2@v2
|
||||||
@@ -115,15 +161,22 @@ jobs:
|
|||||||
|
|
||||||
- name: Extract the bench number from the commit history
|
- name: Extract the bench number from the commit history
|
||||||
run: |
|
run: |
|
||||||
git log HEAD | grep -o "\b[Bb]ench[ :]\+[1-9][0-9]\{5,9\}\b" | head -n 1 | sed "s/[^0-9]//g" > git_sig
|
for hash in $(git rev-list -100 HEAD); do
|
||||||
[ -s git_sig ] && echo "benchref=$(cat git_sig)" >> $GITHUB_ENV && echo "Reference bench:" $(cat git_sig) || echo "No bench found"
|
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
|
- name: Check compiler
|
||||||
run: |
|
run: |
|
||||||
|
if [ -z "${{ matrix.config.base_image }}" ]; then
|
||||||
if [ $COMP == ndk ]; then
|
if [ $COMP == ndk ]; then
|
||||||
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
||||||
fi
|
fi
|
||||||
$COMPILER -v
|
$COMPILER -v
|
||||||
|
else
|
||||||
|
echo "$COMPILER -v" > script.sh
|
||||||
|
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}/src:/app sf_builder
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Test help target
|
- name: Test help target
|
||||||
run: make help
|
run: make help
|
||||||
@@ -134,123 +187,179 @@ jobs:
|
|||||||
# x86-32 tests
|
# x86-32 tests
|
||||||
|
|
||||||
- name: Test debug x86-32 build
|
- name: Test debug x86-32 build
|
||||||
if: ${{ matrix.config.run_32bit_tests }}
|
if: matrix.config.run_32bit_tests
|
||||||
run: |
|
run: |
|
||||||
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
|
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-32 optimize=no debug=yes build
|
make -j4 ARCH=x86-32 optimize=no debug=yes build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test x86-32 build
|
- name: Test x86-32 build
|
||||||
if: ${{ matrix.config.run_32bit_tests }}
|
if: matrix.config.run_32bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-32 build
|
make -j4 ARCH=x86-32 build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test x86-32-sse41-popcnt build
|
- name: Test x86-32-sse41-popcnt build
|
||||||
if: ${{ matrix.config.run_32bit_tests }}
|
if: matrix.config.run_32bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-32-sse41-popcnt build
|
make -j4 ARCH=x86-32-sse41-popcnt build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test x86-32-sse2 build
|
- name: Test x86-32-sse2 build
|
||||||
if: ${{ matrix.config.run_32bit_tests }}
|
if: matrix.config.run_32bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-32-sse2 build
|
make -j4 ARCH=x86-32-sse2 build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test general-32 build
|
- name: Test general-32 build
|
||||||
if: ${{ matrix.config.run_32bit_tests }}
|
if: matrix.config.run_32bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=general-32 build
|
make -j4 ARCH=general-32 build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
# x86-64 tests
|
# x86-64 tests
|
||||||
|
|
||||||
- name: Test debug x86-64-modern build
|
- name: Test debug x86-64-avx2 build
|
||||||
if: ${{ matrix.config.run_64bit_tests }}
|
if: matrix.config.run_64bit_tests
|
||||||
run: |
|
run: |
|
||||||
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
|
export CXXFLAGS="-Werror -D_GLIBCXX_DEBUG"
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-64-modern optimize=no debug=yes build
|
make -j4 ARCH=x86-64-avx2 optimize=no debug=yes build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test x86-64-modern build
|
- name: Test x86-64-bmi2 build
|
||||||
if: ${{ matrix.config.run_64bit_tests }}
|
if: matrix.config.run_64bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-64-modern build
|
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
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test x86-64-ssse3 build
|
- name: Test x86-64-ssse3 build
|
||||||
if: ${{ matrix.config.run_64bit_tests }}
|
if: matrix.config.run_64bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-64-ssse3 build
|
make -j4 ARCH=x86-64-ssse3 build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test x86-64-sse3-popcnt build
|
- name: Test x86-64-sse3-popcnt build
|
||||||
if: ${{ matrix.config.run_64bit_tests }}
|
if: matrix.config.run_64bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-64-sse3-popcnt build
|
make -j4 ARCH=x86-64-sse3-popcnt build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test x86-64 build
|
- name: Test x86-64 build
|
||||||
if: ${{ matrix.config.run_64bit_tests }}
|
if: matrix.config.run_64bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-64 build
|
make -j4 ARCH=x86-64 build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test general-64 build
|
- name: Test general-64 build
|
||||||
if: matrix.config.run_64bit_tests
|
if: matrix.config.run_64bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=general-64 build
|
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
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
# armv8 tests
|
# armv8 tests
|
||||||
|
|
||||||
- name: Test armv8 build
|
- name: Test armv8 build
|
||||||
if: ${{ matrix.config.run_armv8_tests }}
|
if: matrix.config.run_armv8_tests
|
||||||
run: |
|
run: |
|
||||||
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
||||||
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=armv8 build
|
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
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
# armv7 tests
|
# armv7 tests
|
||||||
|
|
||||||
- name: Test armv7 build
|
- name: Test armv7 build
|
||||||
if: ${{ matrix.config.run_armv7_tests }}
|
if: matrix.config.run_armv7_tests
|
||||||
run: |
|
run: |
|
||||||
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
||||||
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=armv7 build
|
make -j4 ARCH=armv7 build
|
||||||
../tests/signature.sh $benchref
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
- name: Test armv7-neon build
|
- name: Test armv7-neon build
|
||||||
if: ${{ matrix.config.run_armv7_tests }}
|
if: matrix.config.run_armv7_tests
|
||||||
run: |
|
run: |
|
||||||
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
export PATH=${{ env.ANDROID_NDK_BIN }}:$PATH
|
||||||
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
export LDFLAGS="-static -Wno-unused-command-line-argument"
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=armv7-neon build
|
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
|
../tests/signature.sh $benchref
|
||||||
|
|
||||||
# Other tests
|
# Other tests
|
||||||
|
|
||||||
- name: Check perft and search reproducibility
|
- name: Check perft and search reproducibility
|
||||||
if: ${{ matrix.config.run_64bit_tests }}
|
if: matrix.config.run_64bit_tests
|
||||||
run: |
|
run: |
|
||||||
make clean
|
make clean
|
||||||
make -j2 ARCH=x86-64-modern build
|
make -j4 ARCH=x86-64-avx2 build
|
||||||
../tests/perft.sh
|
../tests/perft.sh
|
||||||
../tests/reprosearch.sh
|
../tests/reprosearch.sh
|
||||||
@@ -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:
|
||||||
|
COMPILER: ${{ 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 }}
|
||||||
@@ -12,6 +12,7 @@ Hisayori Noda (nodchip)
|
|||||||
# All other authors of Stockfish code (in alphabetical order)
|
# All other authors of Stockfish code (in alphabetical order)
|
||||||
Aditya (absimaldata)
|
Aditya (absimaldata)
|
||||||
Adrian Petrescu (apetresc)
|
Adrian Petrescu (apetresc)
|
||||||
|
Ahmed Kerimov (wcdbmv)
|
||||||
Ajith Chandy Jose (ajithcj)
|
Ajith Chandy Jose (ajithcj)
|
||||||
Alain Savard (Rocky640)
|
Alain Savard (Rocky640)
|
||||||
Alayan Feh (Alayan-stk-2)
|
Alayan Feh (Alayan-stk-2)
|
||||||
@@ -29,6 +30,7 @@ Aram Tumanian (atumanian)
|
|||||||
Arjun Temurnikar
|
Arjun Temurnikar
|
||||||
Artem Solopiy (EntityFX)
|
Artem Solopiy (EntityFX)
|
||||||
Auguste Pop
|
Auguste Pop
|
||||||
|
Balazs Szilagyi
|
||||||
Balint Pfliegel
|
Balint Pfliegel
|
||||||
Ben Chaney (Chaneybenjamini)
|
Ben Chaney (Chaneybenjamini)
|
||||||
Ben Koshy (BKSpurgeon)
|
Ben Koshy (BKSpurgeon)
|
||||||
@@ -45,9 +47,12 @@ candirufish
|
|||||||
Chess13234
|
Chess13234
|
||||||
Chris Cain (ceebo)
|
Chris Cain (ceebo)
|
||||||
clefrks
|
clefrks
|
||||||
|
Clemens L. (rn5f107s2)
|
||||||
|
Cody Ho (aesrentai)
|
||||||
Dale Weiler (graphitemaster)
|
Dale Weiler (graphitemaster)
|
||||||
Daniel Axtens (daxtens)
|
Daniel Axtens (daxtens)
|
||||||
Daniel Dugovic (ddugovic)
|
Daniel Dugovic (ddugovic)
|
||||||
|
Daniel Monroe (Ergodice)
|
||||||
Dan Schmidt (dfannius)
|
Dan Schmidt (dfannius)
|
||||||
Dariusz Orzechowski (dorzechowski)
|
Dariusz Orzechowski (dorzechowski)
|
||||||
David (dav1312)
|
David (dav1312)
|
||||||
@@ -69,9 +74,11 @@ Fabian Beuke (madnight)
|
|||||||
Fabian Fichter (ianfab)
|
Fabian Fichter (ianfab)
|
||||||
Fanael Linithien (Fanael)
|
Fanael Linithien (Fanael)
|
||||||
fanon
|
fanon
|
||||||
Fauzi Akram Dabat (FauziAkram)
|
Fauzi Akram Dabat (fauzi2)
|
||||||
Felix Wittmann
|
Felix Wittmann
|
||||||
gamander
|
gamander
|
||||||
|
Gabriele Lombardo (gabe)
|
||||||
|
Gahtan Nahdi
|
||||||
Gary Heckman (gheckman)
|
Gary Heckman (gheckman)
|
||||||
George Sobala (gsobala)
|
George Sobala (gsobala)
|
||||||
gguliash
|
gguliash
|
||||||
@@ -94,6 +101,7 @@ Jake Senne (w1wwwwww)
|
|||||||
Jan Ondruš (hxim)
|
Jan Ondruš (hxim)
|
||||||
Jared Kish (Kurtbusch, kurt22i)
|
Jared Kish (Kurtbusch, kurt22i)
|
||||||
Jarrod Torriero (DU-jdto)
|
Jarrod Torriero (DU-jdto)
|
||||||
|
Jasper Shovelton (Beanie496)
|
||||||
Jean-Francois Romang (jromang)
|
Jean-Francois Romang (jromang)
|
||||||
Jean Gauthier (OuaisBla)
|
Jean Gauthier (OuaisBla)
|
||||||
Jekaa
|
Jekaa
|
||||||
@@ -178,6 +186,7 @@ Raminder Singh
|
|||||||
renouve
|
renouve
|
||||||
Reuven Peleg (R-Peleg)
|
Reuven Peleg (R-Peleg)
|
||||||
Richard Lloyd (Richard-Lloyd)
|
Richard Lloyd (Richard-Lloyd)
|
||||||
|
Robert Nürnberg (robertnurnberg)
|
||||||
Rodrigo Exterckötter Tjäder
|
Rodrigo Exterckötter Tjäder
|
||||||
Rodrigo Roim (roim)
|
Rodrigo Roim (roim)
|
||||||
Ronald de Man (syzygy1, syzygy)
|
Ronald de Man (syzygy1, syzygy)
|
||||||
@@ -195,16 +204,22 @@ sf-x
|
|||||||
Shahin M. Shahin (peregrine)
|
Shahin M. Shahin (peregrine)
|
||||||
Shane Booth (shane31)
|
Shane Booth (shane31)
|
||||||
Shawn Varghese (xXH4CKST3RXx)
|
Shawn Varghese (xXH4CKST3RXx)
|
||||||
|
Shawn Xu (xu-shawn)
|
||||||
Siad Daboul (Topologist)
|
Siad Daboul (Topologist)
|
||||||
Stefan Geschwentner (locutus2)
|
Stefan Geschwentner (locutus2)
|
||||||
Stefano Cardanobile (Stefano80)
|
Stefano Cardanobile (Stefano80)
|
||||||
Stefano Di Martino (StefanoD)
|
Stefano Di Martino (StefanoD)
|
||||||
Steinar Gunderson (sesse)
|
Steinar Gunderson (sesse)
|
||||||
Stéphane Nicolet (snicolet)
|
Stéphane Nicolet (snicolet)
|
||||||
|
Stephen Touset (stouset)
|
||||||
Syine Mineta (MinetaS)
|
Syine Mineta (MinetaS)
|
||||||
|
Taras Vuk (TarasVuk)
|
||||||
Thanar2
|
Thanar2
|
||||||
thaspel
|
thaspel
|
||||||
theo77186
|
theo77186
|
||||||
|
TierynnB
|
||||||
|
Ting-Hsuan Huang (fffelix-huang)
|
||||||
|
Tobias Steinmann
|
||||||
Tomasz Sobczyk (Sopel97)
|
Tomasz Sobczyk (Sopel97)
|
||||||
Tom Truscott
|
Tom Truscott
|
||||||
Tom Vijlbrief (tomtor)
|
Tom Vijlbrief (tomtor)
|
||||||
@@ -218,9 +233,10 @@ Vince Negri (cuddlestmonkey)
|
|||||||
Viren
|
Viren
|
||||||
windfishballad
|
windfishballad
|
||||||
xefoci7612
|
xefoci7612
|
||||||
|
Xiang Wang (KatyushaScarlet)
|
||||||
zz4032
|
zz4032
|
||||||
|
|
||||||
# Additionally, we acknowledge the authors and maintainers of fishtest,
|
# Additionally, we acknowledge the authors and maintainers of fishtest,
|
||||||
# an amazing and essential framework for Stockfish development!
|
# 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
|
||||||
|
|||||||
@@ -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
|
||||||
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
|
|||||||
You should also get your employer (if you work as a programmer) or school,
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
For more information on this, and how to apply and follow the GNU GPL, see
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
<http://www.gnu.org/licenses/>.
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
The GNU General Public License does not permit incorporating your program
|
The GNU General Public License does not permit incorporating your program
|
||||||
into proprietary programs. If your program is a subroutine library, you
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
may consider it more useful to permit linking proprietary applications with
|
may consider it more useful to permit linking proprietary applications with
|
||||||
the library. If this is what you want to do, use the GNU Lesser General
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
Public License instead of this License. But first, please read
|
Public License instead of this License. But first, please read
|
||||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
|
|||||||
@@ -59,39 +59,37 @@ This distribution of Stockfish consists of the following files:
|
|||||||
* a file with the .nnue extension, storing the neural network for the NNUE
|
* a file with the .nnue extension, storing the neural network for the NNUE
|
||||||
evaluation. Binary distributions will have this file embedded.
|
evaluation. Binary distributions will have this file embedded.
|
||||||
|
|
||||||
## The UCI protocol
|
## Stockfish on distributed memory systems
|
||||||
|
|
||||||
The [Universal Chess Interface][uci-link] (UCI) is a standard text-based 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 its options.
|
|
||||||
|
|
||||||
Developers can see the default values for the UCI options available in Stockfish
|
|
||||||
by typing `./stockfish uci` in a terminal, but most users should typically use a
|
|
||||||
chess GUI to interact with Stockfish.
|
|
||||||
|
|
||||||
For more information on UCI or debug commands, see our [documentation][wiki-commands-link].
|
|
||||||
|
|
||||||
## 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.
|
|
||||||
|
|
||||||
|
The cluster branch allows for running Stockfish on a cluster of servers (nodes)
|
||||||
|
that are connected with a high-speed and low-latency network, using the message
|
||||||
|
passing interface (MPI). In this case, one MPI process should be run per node,
|
||||||
|
and UCI options can be used to set the number of threads/hash per node as usual.
|
||||||
|
Typically, the engine will be invoked as
|
||||||
```
|
```
|
||||||
cd src
|
mpirun -np N /path/to/stockfish
|
||||||
make -j build ARCH=x86-64-modern
|
|
||||||
```
|
```
|
||||||
|
where ```N``` stands for the number of MPI processes used (alternatives to ```mpirun```,
|
||||||
Detailed compilation instructions for all platforms can be found in our
|
include ```mpiexec```, ```srun```). Use 1 mpi rank per node, and employ threading
|
||||||
[documentation][wiki-compile-link].
|
according to the cores per node. To build the cluster
|
||||||
|
branch, it is sufficient to specify ```COMPCXX=mpicxx``` (or e.g. CC depending on the name
|
||||||
|
of the compiler providing MPI support) on the make command line, and do a clean build:
|
||||||
|
```
|
||||||
|
make -j ARCH=x86-64-modern clean build COMPCXX=mpicxx mpi=yes
|
||||||
|
```
|
||||||
|
Make sure that the MPI installation is configured to support ```MPI_THREAD_MULTIPLE```,
|
||||||
|
this might require adding system specific compiler options to the Makefile. Stockfish employs
|
||||||
|
non-blocking (asynchronous) communication, and benefits from an MPI
|
||||||
|
implementation that efficiently supports this. Some MPI implentations might benefit
|
||||||
|
from leaving 1 core/thread free for these asynchronous communications, and might require
|
||||||
|
setting additional environment variables. ```mpirun``` should forward stdin/stdout
|
||||||
|
to ```rank 0``` only (e.g. ```srun --input=0 --output=0```).
|
||||||
|
Refer to your MPI documentation for more info.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
__See [Contributing Guide](CONTRIBUTING.md).__
|
||||||
|
|
||||||
### Donating hardware
|
### Donating hardware
|
||||||
|
|
||||||
Improving Stockfish requires a massive amount of testing. You can donate your
|
Improving Stockfish requires a massive amount of testing. You can donate your
|
||||||
@@ -114,6 +112,25 @@ Discussions about Stockfish take place these days mainly in the Stockfish
|
|||||||
[Discord server][discord-link]. This is also the best place to ask questions
|
[Discord server][discord-link]. This is also the best place to ask questions
|
||||||
about the codebase and how to improve it.
|
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
|
## Terms of use
|
||||||
|
|
||||||
Stockfish is free and distributed under the
|
Stockfish is free and distributed under the
|
||||||
@@ -138,7 +155,7 @@ also be made available under GPL v3.
|
|||||||
[issue-link]: https://github.com/official-stockfish/Stockfish/issues/new?assignees=&labels=&template=BUG-REPORT.yml
|
[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
|
[discussions-link]: https://github.com/official-stockfish/Stockfish/discussions/new
|
||||||
[fishtest-link]: https://tests.stockfishchess.org/tests
|
[fishtest-link]: https://tests.stockfishchess.org/tests
|
||||||
[guideline-link]: https://github.com/glinscott/fishtest/wiki/Creating-my-first-test
|
[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
|
[license-link]: https://github.com/official-stockfish/Stockfish/blob/master/Copying.txt
|
||||||
[programming-link]: https://www.chessprogramming.org/Main_Page
|
[programming-link]: https://www.chessprogramming.org/Main_Page
|
||||||
[programmingsf-link]: https://www.chessprogramming.org/Stockfish
|
[programmingsf-link]: https://www.chessprogramming.org/Stockfish
|
||||||
@@ -150,10 +167,10 @@ also be made available under GPL v3.
|
|||||||
[website-link]: https://stockfishchess.org
|
[website-link]: https://stockfishchess.org
|
||||||
[website-blog-link]: https://stockfishchess.org/blog/
|
[website-blog-link]: https://stockfishchess.org/blog/
|
||||||
[wiki-link]: https://github.com/official-stockfish/Stockfish/wiki
|
[wiki-link]: https://github.com/official-stockfish/Stockfish/wiki
|
||||||
[wiki-usage-link]: https://github.com/official-stockfish/Stockfish/wiki/Download-and-usage
|
|
||||||
[wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source
|
[wiki-compile-link]: https://github.com/official-stockfish/Stockfish/wiki/Compiling-from-source
|
||||||
[wiki-commands-link]: https://github.com/official-stockfish/Stockfish/wiki/Commands
|
[wiki-uci-link]: https://github.com/official-stockfish/Stockfish/wiki/UCI-&-Commands
|
||||||
[worker-link]: https://github.com/glinscott/fishtest/wiki/Running-the-worker
|
[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
|
[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
|
[commits-badge]: https://img.shields.io/github/commits-since/official-stockfish/Stockfish/latest?style=for-the-badge
|
||||||
|
|||||||
+104
-85
@@ -1,139 +1,146 @@
|
|||||||
Contributors to Fishtest with >10,000 CPU hours, as of 2023-06-20.
|
Contributors to Fishtest with >10,000 CPU hours, as of 2024-02-24.
|
||||||
Thank you!
|
Thank you!
|
||||||
|
|
||||||
Username CPU Hours Games played
|
Username CPU Hours Games played
|
||||||
------------------------------------------------------------------
|
------------------------------------------------------------------
|
||||||
noobpwnftw 37457426 2850540907
|
noobpwnftw 39302472 3055513453
|
||||||
technologov 14135647 742892808
|
technologov 20845762 994893444
|
||||||
linrock 4423514 303254809
|
linrock 8616428 560281417
|
||||||
mlang 3026000 200065824
|
mlang 3026000 200065824
|
||||||
|
okrout 2332151 222639518
|
||||||
|
pemo 1800019 60274069
|
||||||
dew 1689162 100033738
|
dew 1689162 100033738
|
||||||
okrout 1578136 148855886
|
TueRens 1474943 75121774
|
||||||
pemo 1508508 48814305
|
grandphish2 1463002 91616949
|
||||||
grandphish2 1461406 91540343
|
JojoM 1109702 72927902
|
||||||
TueRens 1194790 70400852
|
olafm 978631 71037944
|
||||||
JojoM 947612 61773190
|
sebastronomy 939955 44920556
|
||||||
tvijlbrief 796125 51897690
|
tvijlbrief 796125 51897690
|
||||||
sebastronomy 742434 38218524
|
gvreuls 711320 49142318
|
||||||
mibere 703840 46867607
|
mibere 703840 46867607
|
||||||
gvreuls 651026 42988582
|
oz 646268 46293638
|
||||||
oz 543438 39314736
|
rpngn 572571 38928563
|
||||||
cw 517858 34869755
|
leszek 531858 39316505
|
||||||
|
cw 518116 34894291
|
||||||
fastgm 503862 30260818
|
fastgm 503862 30260818
|
||||||
leszek 467278 33514883
|
CSU_Dynasty 468784 31385034
|
||||||
CSU_Dynasty 464940 31177118
|
ctoks 434591 28520597
|
||||||
ctoks 434416 28506889
|
maximmasiutin 429983 27066286
|
||||||
crunchy 427035 27344275
|
crunchy 427414 27371625
|
||||||
maximmasiutin 424795 26577722
|
bcross 415724 29061187
|
||||||
bcross 415722 29060963
|
velislav 342588 22140902
|
||||||
olafm 395922 32268020
|
mgrabiak 338763 23999170
|
||||||
rpngn 348378 24560289
|
|
||||||
velislav 342567 22138992
|
|
||||||
Fisherman 327231 21829379
|
Fisherman 327231 21829379
|
||||||
mgrabiak 300612 20608380
|
robal 299836 20213182
|
||||||
Dantist 296386 18031762
|
Dantist 296386 18031762
|
||||||
nordlandia 246201 16189678
|
ncfish1 267604 17881149
|
||||||
robal 241300 15656382
|
nordlandia 249322 16420192
|
||||||
marrco 234581 17714473
|
marrco 234581 17714473
|
||||||
ncfish1 227517 15233777
|
tolkki963 233490 19773930
|
||||||
glinscott 208125 13277240
|
glinscott 208125 13277240
|
||||||
drabel 204167 13930674
|
drabel 204167 13930674
|
||||||
mhoram 202894 12601997
|
mhoram 202894 12601997
|
||||||
bking_US 198894 11876016
|
bking_US 198894 11876016
|
||||||
|
Calis007 188631 12795784
|
||||||
Thanar 179852 12365359
|
Thanar 179852 12365359
|
||||||
|
Fifis 176209 10638245
|
||||||
vdv 175544 9904472
|
vdv 175544 9904472
|
||||||
spams 157128 10319326
|
spams 157128 10319326
|
||||||
|
DesolatedDodo 156659 10210328
|
||||||
|
armo9494 155355 10566898
|
||||||
sqrt2 147963 9724586
|
sqrt2 147963 9724586
|
||||||
DesolatedDodo 146350 9536172
|
jcAEie 140086 10603658
|
||||||
Calis007 143165 9478764
|
vdbergh 139746 9172061
|
||||||
vdbergh 138650 9064413
|
|
||||||
CoffeeOne 137100 5024116
|
CoffeeOne 137100 5024116
|
||||||
armo9494 136191 9460264
|
|
||||||
malala 136182 8002293
|
malala 136182 8002293
|
||||||
xoto 133759 9159372
|
xoto 133759 9159372
|
||||||
davar 129023 8376525
|
davar 129023 8376525
|
||||||
DMBK 122960 8980062
|
DMBK 122960 8980062
|
||||||
dsmith 122059 7570238
|
dsmith 122059 7570238
|
||||||
|
javran 121564 10144656
|
||||||
amicic 119661 7938029
|
amicic 119661 7938029
|
||||||
|
sschnee 118107 7389266
|
||||||
|
Wolfgang 114616 8070494
|
||||||
Data 113305 8220352
|
Data 113305 8220352
|
||||||
BrunoBanani 112960 7436849
|
BrunoBanani 112960 7436849
|
||||||
|
Wencey 111502 5991676
|
||||||
|
cuistot 108503 7006992
|
||||||
CypressChess 108331 7759788
|
CypressChess 108331 7759788
|
||||||
skiminki 107583 7218170
|
skiminki 107583 7218170
|
||||||
jcAEie 105675 8238962
|
|
||||||
MaZePallas 102823 6633619
|
MaZePallas 102823 6633619
|
||||||
sterni1971 100532 5880772
|
sterni1971 100532 5880772
|
||||||
sunu 100167 7040199
|
sunu 100167 7040199
|
||||||
zeryl 99331 6221261
|
zeryl 99331 6221261
|
||||||
thirdlife 99124 2242380
|
thirdlife 99156 2245320
|
||||||
ElbertoOne 99028 7023771
|
ElbertoOne 99028 7023771
|
||||||
cuistot 98853 6069816
|
Dubslow 98600 6903242
|
||||||
|
markkulix 97010 7643900
|
||||||
bigpen0r 94809 6529203
|
bigpen0r 94809 6529203
|
||||||
brabos 92118 6186135
|
brabos 92118 6186135
|
||||||
Wolfgang 91939 6105872
|
Maxim 90818 3283364
|
||||||
psk 89957 5984901
|
psk 89957 5984901
|
||||||
sschnee 88235 5268000
|
megaman7de 88822 6052132
|
||||||
racerschmacer 85805 6122790
|
racerschmacer 85805 6122790
|
||||||
Fifis 85722 5709729
|
maposora 85710 7778146
|
||||||
Dubslow 84986 6042456
|
|
||||||
Vizvezdenec 83761 5344740
|
Vizvezdenec 83761 5344740
|
||||||
0x3C33 82614 5271253
|
0x3C33 82614 5271253
|
||||||
BRAVONE 81239 5054681
|
BRAVONE 81239 5054681
|
||||||
nssy 76497 5259388
|
nssy 76497 5259388
|
||||||
jromang 76106 5236025
|
jromang 76106 5236025
|
||||||
teddybaer 75125 5407666
|
teddybaer 75125 5407666
|
||||||
tolkki963 74762 5149662
|
|
||||||
megaman7de 74351 4940352
|
|
||||||
Wencey 74181 4711488
|
|
||||||
Pking_cda 73776 5293873
|
Pking_cda 73776 5293873
|
||||||
yurikvelo 73150 5004382
|
yurikvelo 73516 5036928
|
||||||
markkulix 72607 5304642
|
MarcusTullius 71053 4803477
|
||||||
Bobo1239 70579 4794999
|
Bobo1239 70579 4794999
|
||||||
solarlight 70517 5028306
|
solarlight 70517 5028306
|
||||||
dv8silencer 70287 3883992
|
dv8silencer 70287 3883992
|
||||||
|
Spprtr 69646 4806763
|
||||||
|
Mineta 66325 4537742
|
||||||
manap 66273 4121774
|
manap 66273 4121774
|
||||||
|
szupaw 65468 5669742
|
||||||
tinker 64333 4268790
|
tinker 64333 4268790
|
||||||
qurashee 61208 3429862
|
qurashee 61208 3429862
|
||||||
Mineta 59357 4418202
|
woutboat 59496 4906352
|
||||||
Spprtr 58723 3911011
|
AGI 58195 4329580
|
||||||
AGI 58147 4325994
|
|
||||||
robnjr 57262 4053117
|
robnjr 57262 4053117
|
||||||
Freja 56938 3733019
|
Freja 56938 3733019
|
||||||
MaxKlaxxMiner 56879 3423958
|
MaxKlaxxMiner 56879 3423958
|
||||||
MarcusTullius 56746 3762951
|
|
||||||
ttruscott 56010 3680085
|
ttruscott 56010 3680085
|
||||||
rkl 55132 4164467
|
rkl 55132 4164467
|
||||||
|
jmdana 54697 4012593
|
||||||
renouve 53811 3501516
|
renouve 53811 3501516
|
||||||
javran 53785 4627608
|
notchris 52433 4044590
|
||||||
finfish 51360 3370515
|
finfish 51360 3370515
|
||||||
eva42 51272 3599691
|
eva42 51272 3599691
|
||||||
eastorwest 51117 3454811
|
eastorwest 51117 3454811
|
||||||
|
Goatminola 51004 4432492
|
||||||
rap 49985 3219146
|
rap 49985 3219146
|
||||||
pb00067 49733 3298934
|
pb00067 49733 3298934
|
||||||
|
GPUex 48686 3684998
|
||||||
OuaisBla 48626 3445134
|
OuaisBla 48626 3445134
|
||||||
ronaldjerum 47654 3240695
|
ronaldjerum 47654 3240695
|
||||||
biffhero 46564 3111352
|
biffhero 46564 3111352
|
||||||
|
oryx 45533 3539290
|
||||||
VoyagerOne 45476 3452465
|
VoyagerOne 45476 3452465
|
||||||
jmdana 44893 3065205
|
|
||||||
maposora 44597 4039578
|
|
||||||
oryx 44570 3454238
|
|
||||||
speedycpu 43842 3003273
|
speedycpu 43842 3003273
|
||||||
jbwiebe 43305 2805433
|
jbwiebe 43305 2805433
|
||||||
GPUex 42378 3133332
|
|
||||||
Antihistamine 41788 2761312
|
Antihistamine 41788 2761312
|
||||||
mhunt 41735 2691355
|
mhunt 41735 2691355
|
||||||
homyur 39893 2850481
|
homyur 39893 2850481
|
||||||
gri 39871 2515779
|
gri 39871 2515779
|
||||||
Garf 37741 2999686
|
Garf 37741 2999686
|
||||||
SC 37299 2731694
|
SC 37299 2731694
|
||||||
|
Sylvain27 36520 1467082
|
||||||
csnodgrass 36207 2688994
|
csnodgrass 36207 2688994
|
||||||
|
Gaster319 35655 3149442
|
||||||
strelock 34716 2074055
|
strelock 34716 2074055
|
||||||
szupaw 34102 2880346
|
|
||||||
EthanOConnor 33370 2090311
|
EthanOConnor 33370 2090311
|
||||||
slakovv 32915 2021889
|
slakovv 32915 2021889
|
||||||
|
gopeto 31884 2076712
|
||||||
Gelma 31771 1551204
|
Gelma 31771 1551204
|
||||||
gopeto 31671 2060990
|
|
||||||
kdave 31157 2198362
|
kdave 31157 2198362
|
||||||
manapbk 30987 1810399
|
manapbk 30987 1810399
|
||||||
|
ZacHFX 30551 2238078
|
||||||
Prcuvu 30377 2170122
|
Prcuvu 30377 2170122
|
||||||
anst 30301 2190091
|
anst 30301 2190091
|
||||||
jkiiski 30136 1904470
|
jkiiski 30136 1904470
|
||||||
@@ -142,27 +149,31 @@ hyperbolic.tom 29840 2017394
|
|||||||
chuckstablers 29659 2093438
|
chuckstablers 29659 2093438
|
||||||
Pyafue 29650 1902349
|
Pyafue 29650 1902349
|
||||||
belzedar94 28846 1811530
|
belzedar94 28846 1811530
|
||||||
|
votoanthuan 27978 2285818
|
||||||
|
shawnxu 27438 2465810
|
||||||
chriswk 26902 1868317
|
chriswk 26902 1868317
|
||||||
xwziegtm 26897 2124586
|
xwziegtm 26897 2124586
|
||||||
achambord 26582 1767323
|
achambord 26582 1767323
|
||||||
Patrick_G 26276 1801617
|
Patrick_G 26276 1801617
|
||||||
yorkman 26193 1992080
|
yorkman 26193 1992080
|
||||||
Ulysses 25288 1689730
|
Ulysses 25397 1701264
|
||||||
|
Jopo12321 25227 1652482
|
||||||
SFTUser 25182 1675689
|
SFTUser 25182 1675689
|
||||||
nabildanial 24942 1519409
|
nabildanial 25068 1531665
|
||||||
Sharaf_DG 24765 1786697
|
Sharaf_DG 24765 1786697
|
||||||
Maxim 24705 1502062
|
|
||||||
rodneyc 24376 1416402
|
rodneyc 24376 1416402
|
||||||
|
jsys14 24297 1721230
|
||||||
agg177 23890 1395014
|
agg177 23890 1395014
|
||||||
Goatminola 23763 1956036
|
srowen 23842 1342508
|
||||||
Ente 23639 1671638
|
Ente 23752 1678188
|
||||||
Jopo12321 23467 1483172
|
jojo2357 23479 2061238
|
||||||
JanErik 23408 1703875
|
JanErik 23408 1703875
|
||||||
Isidor 23388 1680691
|
Isidor 23388 1680691
|
||||||
Norabor 23371 1603244
|
Norabor 23371 1603244
|
||||||
cisco2015 22920 1763301
|
cisco2015 22920 1763301
|
||||||
jsys14 22824 1591906
|
|
||||||
Zirie 22542 1472937
|
Zirie 22542 1472937
|
||||||
|
Nullvalue 22490 1970374
|
||||||
|
AndreasKrug 22485 1769491
|
||||||
team-oh 22272 1636708
|
team-oh 22272 1636708
|
||||||
Roady 22220 1465606
|
Roady 22220 1465606
|
||||||
MazeOfGalious 21978 1629593
|
MazeOfGalious 21978 1629593
|
||||||
@@ -173,79 +184,83 @@ dex 21612 1467203
|
|||||||
nesoneg 21494 1463031
|
nesoneg 21494 1463031
|
||||||
user213718 21454 1404128
|
user213718 21454 1404128
|
||||||
sphinx 21211 1384728
|
sphinx 21211 1384728
|
||||||
AndreasKrug 21097 1634811
|
qoo_charly_cai 21135 1514907
|
||||||
jjoshua2 21001 1423089
|
jjoshua2 21001 1423089
|
||||||
Zake9298 20938 1565848
|
Zake9298 20938 1565848
|
||||||
horst.prack 20878 1465656
|
horst.prack 20878 1465656
|
||||||
0xB00B1ES 20590 1208666
|
0xB00B1ES 20590 1208666
|
||||||
|
Serpensin 20487 1729674
|
||||||
|
Dinde 20440 1292390
|
||||||
j3corre 20405 941444
|
j3corre 20405 941444
|
||||||
Adrian.Schmidt123 20316 1281436
|
Adrian.Schmidt123 20316 1281436
|
||||||
wei 19973 1745989
|
wei 19973 1745989
|
||||||
notchris 19958 1800128
|
|
||||||
Serpensin 19840 1697528
|
|
||||||
Gaster319 19712 1677310
|
|
||||||
fishtester 19617 1257388
|
fishtester 19617 1257388
|
||||||
rstoesser 19569 1293588
|
rstoesser 19569 1293588
|
||||||
eudhan 19274 1283717
|
eudhan 19274 1283717
|
||||||
votoanthuan 19108 1609992
|
|
||||||
vulcan 18871 1729392
|
vulcan 18871 1729392
|
||||||
Karpovbot 18766 1053178
|
Karpovbot 18766 1053178
|
||||||
qoo_charly_cai 18543 1284937
|
WoodMan777 18556 1628264
|
||||||
jundery 18445 1115855
|
jundery 18445 1115855
|
||||||
ville 17883 1384026
|
ville 17883 1384026
|
||||||
chris 17698 1487385
|
chris 17698 1487385
|
||||||
purplefishies 17595 1092533
|
purplefishies 17595 1092533
|
||||||
dju 17414 981289
|
dju 17414 981289
|
||||||
|
ols 17291 1042003
|
||||||
iisiraider 17275 1049015
|
iisiraider 17275 1049015
|
||||||
|
Skiff84 17111 950248
|
||||||
DragonLord 17014 1162790
|
DragonLord 17014 1162790
|
||||||
redstone59 16842 1461780
|
redstone59 16842 1461780
|
||||||
Alb11747 16787 1213926
|
Karby 16839 1010124
|
||||||
|
Alb11747 16787 1213990
|
||||||
|
pirt 16493 1237199
|
||||||
|
Naven94 16414 951718
|
||||||
|
wizardassassin 16392 1148672
|
||||||
IgorLeMasson 16064 1147232
|
IgorLeMasson 16064 1147232
|
||||||
Karby 15982 979610
|
|
||||||
scuzzi 15757 968735
|
scuzzi 15757 968735
|
||||||
ako027ako 15671 1173203
|
ako027ako 15671 1173203
|
||||||
Nikolay.IT 15154 1068349
|
Nikolay.IT 15154 1068349
|
||||||
Andrew Grant 15114 895539
|
Andrew Grant 15114 895539
|
||||||
Naven94 15054 834762
|
|
||||||
OssumOpossum 14857 1007129
|
OssumOpossum 14857 1007129
|
||||||
ZacHFX 14783 1021842
|
LunaticBFF57 14525 1190310
|
||||||
enedene 14476 905279
|
enedene 14476 905279
|
||||||
|
IslandLambda 14393 958196
|
||||||
bpfliegel 14233 882523
|
bpfliegel 14233 882523
|
||||||
|
YELNAMRON 14230 1128094
|
||||||
mpx86 14019 759568
|
mpx86 14019 759568
|
||||||
jpulman 13982 870599
|
jpulman 13982 870599
|
||||||
Skiff84 13826 721996
|
getraideBFF 13871 1172846
|
||||||
|
Nesa92 13806 1116101
|
||||||
crocogoat 13803 1117422
|
crocogoat 13803 1117422
|
||||||
Nesa92 13786 1114691
|
|
||||||
joster 13710 946160
|
joster 13710 946160
|
||||||
mbeier 13650 1044928
|
mbeier 13650 1044928
|
||||||
Hjax 13535 915487
|
Hjax 13535 915487
|
||||||
Nullvalue 13468 1140498
|
|
||||||
Dark_wizzie 13422 1007152
|
Dark_wizzie 13422 1007152
|
||||||
Rudolphous 13244 883140
|
Rudolphous 13244 883140
|
||||||
pirt 13100 1009897
|
|
||||||
Machariel 13010 863104
|
Machariel 13010 863104
|
||||||
infinigon 12991 943216
|
infinigon 12991 943216
|
||||||
mabichito 12903 749391
|
mabichito 12903 749391
|
||||||
thijsk 12886 722107
|
thijsk 12886 722107
|
||||||
AdrianSA 12860 804972
|
AdrianSA 12860 804972
|
||||||
Flopzee 12698 894821
|
Flopzee 12698 894821
|
||||||
|
mschmidt 12644 863193
|
||||||
korposzczur 12606 838168
|
korposzczur 12606 838168
|
||||||
|
tsim67 12570 890180
|
||||||
|
Jackfish 12553 836958
|
||||||
fatmurphy 12547 853210
|
fatmurphy 12547 853210
|
||||||
|
Oakwen 12503 853105
|
||||||
SapphireBrand 12416 969604
|
SapphireBrand 12416 969604
|
||||||
Oakwen 12399 844109
|
|
||||||
deflectooor 12386 579392
|
deflectooor 12386 579392
|
||||||
modolief 12386 896470
|
modolief 12386 896470
|
||||||
|
TataneSan 12358 609332
|
||||||
Farseer 12249 694108
|
Farseer 12249 694108
|
||||||
Jackfish 12213 805008
|
|
||||||
pgontarz 12151 848794
|
pgontarz 12151 848794
|
||||||
dbernier 12103 860824
|
dbernier 12103 860824
|
||||||
getraideBFF 12072 1024966
|
FormazChar 11989 907809
|
||||||
stocky 11954 699440
|
stocky 11954 699440
|
||||||
mschmidt 11941 803401
|
somethingintheshadows 11940 989472
|
||||||
MooTheCow 11870 773598
|
MooTheCow 11892 776126
|
||||||
FormazChar 11766 885707
|
3cho 11842 1036786
|
||||||
whelanh 11557 245188
|
whelanh 11557 245188
|
||||||
3cho 11494 1031076
|
|
||||||
infinity 11470 727027
|
infinity 11470 727027
|
||||||
aga 11412 695127
|
aga 11412 695127
|
||||||
torbjo 11395 729145
|
torbjo 11395 729145
|
||||||
@@ -253,15 +268,19 @@ Thomas A. Anderson 11372 732094
|
|||||||
savage84 11358 670860
|
savage84 11358 670860
|
||||||
d64 11263 789184
|
d64 11263 789184
|
||||||
ali-al-zhrani 11245 779246
|
ali-al-zhrani 11245 779246
|
||||||
|
ckaz 11170 680866
|
||||||
snicolet 11106 869170
|
snicolet 11106 869170
|
||||||
dapper 11032 771402
|
dapper 11032 771402
|
||||||
ols 10947 624903
|
Ethnikoi 10993 945906
|
||||||
Karmatron 10828 677458
|
Snuuka 10938 435504
|
||||||
|
Karmatron 10859 678058
|
||||||
basepi 10637 744851
|
basepi 10637 744851
|
||||||
|
jibarbosa 10628 857100
|
||||||
Cubox 10621 826448
|
Cubox 10621 826448
|
||||||
|
mecevdimitar 10609 787318
|
||||||
michaelrpg 10509 739239
|
michaelrpg 10509 739239
|
||||||
|
Def9Infinity 10427 686978
|
||||||
OIVAS7572 10420 995586
|
OIVAS7572 10420 995586
|
||||||
jojo2357 10419 929708
|
wxt9861 10412 1013864
|
||||||
WoodMan777 10380 873720
|
|
||||||
Garruk 10365 706465
|
Garruk 10365 706465
|
||||||
dzjp 10343 732529
|
dzjp 10343 732529
|
||||||
|
|||||||
Executable
+120
@@ -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"
|
||||||
+142
-70
@@ -1,5 +1,5 @@
|
|||||||
# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
# Copyright (C) 2004-2023 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
|
# Stockfish is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
### ==========================================================================
|
### ==========================================================================
|
||||||
|
|
||||||
### Establish the operating system name
|
### Establish the operating system name
|
||||||
KERNEL = $(shell uname -s)
|
KERNEL := $(shell uname -s)
|
||||||
ifeq ($(KERNEL),Linux)
|
ifeq ($(KERNEL),Linux)
|
||||||
OS = $(shell uname -o)
|
OS := $(shell uname -o)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
### Target Windows OS
|
### Target Windows OS
|
||||||
@@ -33,7 +33,7 @@ ifeq ($(OS),Windows_NT)
|
|||||||
else ifeq ($(COMP),mingw)
|
else ifeq ($(COMP),mingw)
|
||||||
target_windows = yes
|
target_windows = yes
|
||||||
ifeq ($(WINE_PATH),)
|
ifeq ($(WINE_PATH),)
|
||||||
WINE_PATH = $(shell which wine)
|
WINE_PATH := $(shell which wine)
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -49,17 +49,21 @@ PREFIX = /usr/local
|
|||||||
BINDIR = $(PREFIX)/bin
|
BINDIR = $(PREFIX)/bin
|
||||||
|
|
||||||
### Built-in benchmark for pgo-builds
|
### Built-in benchmark for pgo-builds
|
||||||
ifeq ($(SDE_PATH),)
|
|
||||||
PGOBENCH = $(WINE_PATH) ./$(EXE) bench
|
PGOBENCH = $(WINE_PATH) ./$(EXE) bench
|
||||||
else
|
|
||||||
PGOBENCH = $(SDE_PATH) -- $(WINE_PATH) ./$(EXE) bench
|
|
||||||
endif
|
|
||||||
|
|
||||||
### Source and object files
|
### Source and object files
|
||||||
SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
|
SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
|
||||||
material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
|
misc.cpp movegen.cpp movepick.cpp position.cpp cluster.cpp \
|
||||||
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.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
|
||||||
|
|
||||||
|
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 cluster.h
|
||||||
|
|
||||||
OBJS = $(notdir $(SRCS:.cpp=.o))
|
OBJS = $(notdir $(SRCS:.cpp=.o))
|
||||||
|
|
||||||
@@ -96,6 +100,7 @@ VPATH = syzygy:nnue:nnue/features
|
|||||||
# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
|
# vnni512 = yes/no --- -mavx512vnni --- Use Intel Vector Neural Network Instructions 512
|
||||||
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
|
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
|
||||||
# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions
|
# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions
|
||||||
|
# mpi = yes/no --- -DUSE_MPI --- Use Message Passing Interface
|
||||||
#
|
#
|
||||||
# Note that Makefile is space sensitive, so when adding new architectures
|
# 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
|
# or modifying existing flags, you have to make sure there are no extra spaces
|
||||||
@@ -108,16 +113,20 @@ VPATH = syzygy:nnue:nnue/features
|
|||||||
### 2.1. General and architecture defaults
|
### 2.1. General and architecture defaults
|
||||||
|
|
||||||
ifeq ($(ARCH),)
|
ifeq ($(ARCH),)
|
||||||
ARCH = x86-64-modern
|
ARCH = native
|
||||||
help_skip_sanity = yes
|
|
||||||
endif
|
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),
|
# explicitly check for the list of supported architectures (as listed with make help),
|
||||||
# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true`
|
# the user can override with `make ARCH=x86-32-vnni256 SUPPORTED_ARCH=true`
|
||||||
ifeq ($(ARCH), $(filter $(ARCH), \
|
ifeq ($(ARCH), $(filter $(ARCH), \
|
||||||
x86-64-vnni512 x86-64-vnni256 x86-64-avx512 x86-64-avxvnni x86-64-bmi2 \
|
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-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 \
|
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-32 e2k \
|
||||||
armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64))
|
armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 loongarch64))
|
||||||
SUPPORTED_ARCH=true
|
SUPPORTED_ARCH=true
|
||||||
else
|
else
|
||||||
SUPPORTED_ARCH=false
|
SUPPORTED_ARCH=false
|
||||||
@@ -141,10 +150,17 @@ avx512 = no
|
|||||||
vnni256 = no
|
vnni256 = no
|
||||||
vnni512 = no
|
vnni512 = no
|
||||||
neon = no
|
neon = no
|
||||||
|
mpi = no
|
||||||
dotprod = no
|
dotprod = no
|
||||||
arm_version = 0
|
arm_version = 0
|
||||||
STRIP = strip
|
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
|
### 2.2 Architecture specific
|
||||||
|
|
||||||
ifeq ($(findstring x86,$(ARCH)),x86)
|
ifeq ($(findstring x86,$(ARCH)),x86)
|
||||||
@@ -193,6 +209,8 @@ ifeq ($(findstring -sse41,$(ARCH)),-sse41)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(findstring -modern,$(ARCH)),-modern)
|
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
|
popcnt = yes
|
||||||
sse = yes
|
sse = yes
|
||||||
sse2 = yes
|
sse2 = yes
|
||||||
@@ -353,6 +371,10 @@ endif
|
|||||||
ifeq ($(ARCH),riscv64)
|
ifeq ($(ARCH),riscv64)
|
||||||
arch = riscv64
|
arch = riscv64
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifeq ($(ARCH),loongarch64)
|
||||||
|
arch = loongarch64
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
@@ -388,6 +410,8 @@ ifeq ($(COMP),gcc)
|
|||||||
ifeq ($(ARCH),riscv64)
|
ifeq ($(ARCH),riscv64)
|
||||||
CXXFLAGS += -latomic
|
CXXFLAGS += -latomic
|
||||||
endif
|
endif
|
||||||
|
else ifeq ($(ARCH),loongarch64)
|
||||||
|
CXXFLAGS += -latomic
|
||||||
else
|
else
|
||||||
CXXFLAGS += -m$(bits)
|
CXXFLAGS += -m$(bits)
|
||||||
LDFLAGS += -m$(bits)
|
LDFLAGS += -m$(bits)
|
||||||
@@ -458,6 +482,8 @@ ifeq ($(COMP),clang)
|
|||||||
ifeq ($(ARCH),riscv64)
|
ifeq ($(ARCH),riscv64)
|
||||||
CXXFLAGS += -latomic
|
CXXFLAGS += -latomic
|
||||||
endif
|
endif
|
||||||
|
else ifeq ($(ARCH),loongarch64)
|
||||||
|
CXXFLAGS += -latomic
|
||||||
else
|
else
|
||||||
CXXFLAGS += -m$(bits)
|
CXXFLAGS += -m$(bits)
|
||||||
LDFLAGS += -m$(bits)
|
LDFLAGS += -m$(bits)
|
||||||
@@ -497,6 +523,14 @@ ifeq ($(COMP),ndk)
|
|||||||
STRIP=llvm-strip
|
STRIP=llvm-strip
|
||||||
endif
|
endif
|
||||||
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
|
LDFLAGS += -static-libstdc++ -pie -lm -latomic
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -526,8 +560,8 @@ endif
|
|||||||
|
|
||||||
### Sometimes gcc is really clang
|
### Sometimes gcc is really clang
|
||||||
ifeq ($(COMP),gcc)
|
ifeq ($(COMP),gcc)
|
||||||
gccversion = $(shell $(CXX) --version 2>/dev/null)
|
gccversion := $(shell $(CXX) --version 2>/dev/null)
|
||||||
gccisclang = $(findstring clang,$(gccversion))
|
gccisclang := $(findstring clang,$(gccversion))
|
||||||
ifneq ($(gccisclang),)
|
ifneq ($(gccisclang),)
|
||||||
profile_make = clang-profile-make
|
profile_make = clang-profile-make
|
||||||
profile_use = clang-profile-use
|
profile_use = clang-profile-use
|
||||||
@@ -564,7 +598,7 @@ endif
|
|||||||
### 3.3 Optimization
|
### 3.3 Optimization
|
||||||
ifeq ($(optimize),yes)
|
ifeq ($(optimize),yes)
|
||||||
|
|
||||||
CXXFLAGS += -O3
|
CXXFLAGS += -O3 -funroll-loops
|
||||||
|
|
||||||
ifeq ($(comp),gcc)
|
ifeq ($(comp),gcc)
|
||||||
ifeq ($(OS), Android)
|
ifeq ($(OS), Android)
|
||||||
@@ -585,7 +619,7 @@ ifeq ($(optimize),yes)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(comp),clang)
|
ifeq ($(comp),clang)
|
||||||
clangmajorversion = $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.)
|
clangmajorversion := $(shell $(CXX) -dumpversion 2>/dev/null | cut -f1 -d.)
|
||||||
ifeq ($(shell expr $(clangmajorversion) \< 16),1)
|
ifeq ($(shell expr $(clangmajorversion) \< 16),1)
|
||||||
CXXFLAGS += -fexperimental-new-pass-manager
|
CXXFLAGS += -fexperimental-new-pass-manager
|
||||||
endif
|
endif
|
||||||
@@ -672,7 +706,6 @@ ifeq ($(sse2),yes)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
ifeq ($(mmx),yes)
|
ifeq ($(mmx),yes)
|
||||||
CXXFLAGS += -DUSE_MMX
|
|
||||||
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
|
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
|
||||||
CXXFLAGS += -mmmx
|
CXXFLAGS += -mmmx
|
||||||
endif
|
endif
|
||||||
@@ -701,19 +734,24 @@ ifeq ($(pext),yes)
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
### 3.7.1 Try to include git commit sha for versioning
|
### 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)
|
GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8)
|
||||||
ifneq ($(GIT_SHA), )
|
ifneq ($(GIT_SHA), )
|
||||||
CXXFLAGS += -DGIT_SHA=$(GIT_SHA)
|
CXXFLAGS += -DGIT_SHA=$(GIT_SHA)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
### 3.7.2 Try to include git commit date for versioning
|
### 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)
|
GIT_DATE := $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null)
|
||||||
ifneq ($(GIT_DATE), )
|
ifneq ($(GIT_DATE), )
|
||||||
CXXFLAGS += -DGIT_DATE=$(GIT_DATE)
|
CXXFLAGS += -DGIT_DATE=$(GIT_DATE)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
### 3.8 Link Time Optimization
|
### 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
|
### This is a mix of compile and link time options because the lto link phase
|
||||||
### needs access to the optimization flags.
|
### needs access to the optimization flags.
|
||||||
ifeq ($(optimize),yes)
|
ifeq ($(optimize),yes)
|
||||||
@@ -748,36 +786,45 @@ ifeq ($(debug), no)
|
|||||||
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.
|
### breaks Android 4.0 and earlier.
|
||||||
ifeq ($(OS), Android)
|
ifeq ($(OS), Android)
|
||||||
CXXFLAGS += -fPIE
|
CXXFLAGS += -fPIE
|
||||||
LDFLAGS += -fPIE -pie
|
LDFLAGS += -fPIE -pie
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
### 3.10 MPI
|
||||||
|
ifneq (,$(findstring mpi, $(CXX)))
|
||||||
|
mpi = yes
|
||||||
|
endif
|
||||||
|
ifeq ($(mpi),yes)
|
||||||
|
CXXFLAGS += -DUSE_MPI -Wno-cast-qual -fexceptions
|
||||||
|
DEPENDFLAGS += -DUSE_MPI
|
||||||
|
endif
|
||||||
|
|
||||||
### ==========================================================================
|
### ==========================================================================
|
||||||
### Section 4. Public Targets
|
### Section 4. Public Targets
|
||||||
### ==========================================================================
|
### ==========================================================================
|
||||||
|
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "To compile stockfish, type: "
|
@echo "To compile stockfish, type: "
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "make target ARCH=arch [COMP=compiler] [COMPCXX=cxx]"
|
@echo "make -j target [ARCH=arch] [COMP=compiler] [COMPCXX=cxx]"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Supported targets:"
|
@echo "Supported targets:"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "help > Display architecture details"
|
@echo "help > Display architecture details"
|
||||||
@echo "profile-build > standard build with profile-guided optimization"
|
@echo "profile-build > standard build with profile-guided optimization"
|
||||||
@echo "build > skip profile-guided optimization"
|
@echo "build > skip profile-guided optimization"
|
||||||
@echo "net > Download the default nnue net"
|
@echo "net > Download the default nnue nets"
|
||||||
@echo "strip > Strip executable"
|
@echo "strip > Strip executable"
|
||||||
@echo "install > Install executable"
|
@echo "install > Install executable"
|
||||||
@echo "clean > Clean up"
|
@echo "clean > Clean up"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Supported archs:"
|
@echo "Supported archs:"
|
||||||
@echo ""
|
@echo ""
|
||||||
|
@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-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-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-avx512 > x86 64-bit with avx512 support"
|
||||||
@@ -785,13 +832,13 @@ help:
|
|||||||
@echo "x86-64-bmi2 > x86 64-bit with bmi2 support"
|
@echo "x86-64-bmi2 > x86 64-bit with bmi2 support"
|
||||||
@echo "x86-64-avx2 > x86 64-bit with avx2 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-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-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-64 > x86 64-bit generic (with sse2 support)"
|
||||||
@echo "x86-32-sse41-popcnt > x86 32-bit with sse41 and popcnt 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-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-64 > PPC 64-bit"
|
||||||
@echo "ppc-32 > PPC 32-bit"
|
@echo "ppc-32 > PPC 32-bit"
|
||||||
@echo "armv7 > ARMv7 32-bit"
|
@echo "armv7 > ARMv7 32-bit"
|
||||||
@@ -803,11 +850,12 @@ help:
|
|||||||
@echo "general-64 > unspecified 64-bit"
|
@echo "general-64 > unspecified 64-bit"
|
||||||
@echo "general-32 > unspecified 32-bit"
|
@echo "general-32 > unspecified 32-bit"
|
||||||
@echo "riscv64 > RISC-V 64-bit"
|
@echo "riscv64 > RISC-V 64-bit"
|
||||||
|
@echo "loongarch64 > LoongArch 64-bit"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Supported compilers:"
|
@echo "Supported compilers:"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "gcc > Gnu compiler (default)"
|
@echo "gcc > GNU compiler (default)"
|
||||||
@echo "mingw > Gnu compiler with MinGW under Windows"
|
@echo "mingw > GNU compiler with MinGW under Windows"
|
||||||
@echo "clang > LLVM Clang compiler"
|
@echo "clang > LLVM Clang compiler"
|
||||||
@echo "icx > Intel oneAPI DPC++/C++ Compiler"
|
@echo "icx > Intel oneAPI DPC++/C++ Compiler"
|
||||||
@echo "ndk > Google NDK to cross-compile for Android"
|
@echo "ndk > Google NDK to cross-compile for Android"
|
||||||
@@ -815,30 +863,30 @@ help:
|
|||||||
@echo "Simple examples. If you don't know what to do, you likely want to run one of: "
|
@echo "Simple examples. If you don't know what to do, you likely want to run one of: "
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems "
|
@echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems "
|
||||||
@echo "make -j profile-build ARCH=x86-64-modern # A more portable compile for 64-bit systems "
|
@echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems "
|
||||||
@echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems "
|
@echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems "
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Advanced examples, for experienced users: "
|
@echo "Advanced examples, for experienced users: "
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "make -j profile-build ARCH=x86-64-bmi2"
|
@echo "make -j profile-build ARCH=x86-64-avxvnni"
|
||||||
@echo "make -j profile-build ARCH=x86-64-bmi2 COMP=gcc COMPCXX=g++-9.0"
|
@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 "make -j build ARCH=x86-64-ssse3 COMP=clang"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "-------------------------------"
|
ifneq ($(SUPPORTED_ARCH), true)
|
||||||
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
|
|
||||||
@echo "Specify a supported architecture with the ARCH option for more details"
|
@echo "Specify a supported architecture with the ARCH option for more details"
|
||||||
@echo ""
|
@echo ""
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
.PHONY: help build profile-build strip install clean net objclean profileclean \
|
.PHONY: help analyze build profile-build strip install clean net \
|
||||||
config-sanity \
|
objclean profileclean config-sanity \
|
||||||
icx-profile-use icx-profile-make \
|
icx-profile-use icx-profile-make \
|
||||||
gcc-profile-use gcc-profile-make \
|
gcc-profile-use gcc-profile-make \
|
||||||
clang-profile-use clang-profile-make FORCE
|
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
|
build: net config-sanity
|
||||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
|
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
|
||||||
@@ -849,7 +897,8 @@ profile-build: net config-sanity objclean profileclean
|
|||||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
|
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make)
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Step 2/4. Running benchmark for pgo-build ..."
|
@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 ""
|
||||||
@echo "Step 3/4. Building optimized executable ..."
|
@echo "Step 3/4. Building optimized executable ..."
|
||||||
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
|
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
|
||||||
@@ -870,23 +919,35 @@ install:
|
|||||||
clean: objclean profileclean
|
clean: objclean profileclean
|
||||||
@rm -f .depend *~ core
|
@rm -f .depend *~ core
|
||||||
|
|
||||||
# evaluation network (nnue)
|
# clean binaries and objects
|
||||||
net:
|
objclean:
|
||||||
$(eval nnuenet := $(shell grep EvalFileDefaultName evaluate.h | grep define | sed 's/.*\(nn-[a-z0-9]\{12\}.nnue\).*/\1/'))
|
@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
|
||||||
|
|
||||||
|
# clean auxiliary profiling files
|
||||||
|
profileclean:
|
||||||
|
@rm -rf profdir
|
||||||
|
@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)"
|
@echo "Default net: $(nnuenet)"
|
||||||
$(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))
|
|
||||||
@if [ "x$(curl_or_wget)" = "x" ]; then \
|
@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"; \
|
echo "Neither curl nor wget is installed. Install one of these tools unless the net has been downloaded manually"; \
|
||||||
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 [ "x$(shasum_command)" = "x" ]; then \
|
||||||
echo "shasum / sha256sum not found, skipping net validation"; \
|
echo "shasum / sha256sum not found, skipping net validation"; \
|
||||||
fi
|
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 \
|
@for nnuedownloadurl in "$(nnuedownloadurl1)" "$(nnuedownloadurl2)"; do \
|
||||||
if test -f "$(nnuenet)"; then \
|
if test -f "$(nnuenet)"; then \
|
||||||
echo "$(nnuenet) available."; \
|
echo "$(nnuenet) available : OK"; break; \
|
||||||
else \
|
else \
|
||||||
if [ "x$(curl_or_wget)" != "x" ]; then \
|
if [ "x$(curl_or_wget)" != "x" ]; then \
|
||||||
echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\
|
echo "Downloading $${nnuedownloadurl}"; $(curl_or_wget) $${nnuedownloadurl} > $(nnuenet);\
|
||||||
@@ -897,29 +958,39 @@ net:
|
|||||||
if [ "x$(shasum_command)" != "x" ]; then \
|
if [ "x$(shasum_command)" != "x" ]; then \
|
||||||
if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
|
if [ "$(nnuenet)" != "nn-"`$(shasum_command) $(nnuenet) | cut -c1-12`".nnue" ]; then \
|
||||||
echo "Removing failed download"; rm -f $(nnuenet); \
|
echo "Removing failed download"; rm -f $(nnuenet); \
|
||||||
else \
|
|
||||||
echo "Network validated"; break; \
|
|
||||||
fi; \
|
fi; \
|
||||||
fi; \
|
fi; \
|
||||||
done
|
done
|
||||||
@if ! test -f "$(nnuenet)"; then \
|
@if ! test -f "$(nnuenet)"; then \
|
||||||
echo "Failed to download $(nnuenet)."; \
|
echo "Failed to download $(nnuenet)."; \
|
||||||
fi
|
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
|
||||||
|
|
||||||
# clean binaries and objects
|
# set up shell variables for the net stuff
|
||||||
objclean:
|
define netvariables
|
||||||
@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
|
$(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
|
||||||
|
|
||||||
# clean auxiliary profiling files
|
# evaluation network (nnue)
|
||||||
profileclean:
|
net:
|
||||||
@rm -rf profdir
|
$(call netvariables, EvalFileDefaultNameBig)
|
||||||
@rm -f bench.txt *.gcda *.gcno ./syzygy/*.gcda ./nnue/*.gcda ./nnue/features/*.gcda *.s
|
$(call fetch_network)
|
||||||
@rm -f stockfish.profdata *.profraw
|
$(call netvariables, EvalFileDefaultNameSmall)
|
||||||
@rm -f stockfish.*args*
|
$(call fetch_network)
|
||||||
@rm -f stockfish.*lt*
|
|
||||||
@rm -f stockfish.res
|
|
||||||
@rm -f ./-lstdc++.res
|
|
||||||
|
|
||||||
|
format:
|
||||||
|
$(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file
|
||||||
|
|
||||||
|
# default target
|
||||||
default:
|
default:
|
||||||
help
|
help
|
||||||
|
|
||||||
@@ -953,6 +1024,7 @@ config-sanity: net
|
|||||||
@echo "vnni256: '$(vnni256)'"
|
@echo "vnni256: '$(vnni256)'"
|
||||||
@echo "vnni512: '$(vnni512)'"
|
@echo "vnni512: '$(vnni512)'"
|
||||||
@echo "neon: '$(neon)'"
|
@echo "neon: '$(neon)'"
|
||||||
|
@echo "mpi: '$(mpi)'"
|
||||||
@echo "dotprod: '$(dotprod)'"
|
@echo "dotprod: '$(dotprod)'"
|
||||||
@echo "arm_version: '$(arm_version)'"
|
@echo "arm_version: '$(arm_version)'"
|
||||||
@echo "target_windows: '$(target_windows)'"
|
@echo "target_windows: '$(target_windows)'"
|
||||||
@@ -969,7 +1041,7 @@ config-sanity: net
|
|||||||
@test "$(SUPPORTED_ARCH)" = "true"
|
@test "$(SUPPORTED_ARCH)" = "true"
|
||||||
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
|
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
|
||||||
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \
|
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \
|
||||||
test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64"
|
test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64"
|
||||||
@test "$(bits)" = "32" || test "$(bits)" = "64"
|
@test "$(bits)" = "32" || test "$(bits)" = "64"
|
||||||
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
|
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
|
||||||
@test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
|
@test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
|
||||||
|
|||||||
+29
-41
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,18 +18,17 @@
|
|||||||
|
|
||||||
#include "benchmark.h"
|
#include "benchmark.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <istream>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const vector<string> Defaults = {
|
// clang-format off
|
||||||
|
const std::vector<std::string> Defaults = {
|
||||||
"setoption name UCI_Chess960 value false",
|
"setoption name UCI_Chess960 value false",
|
||||||
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
|
||||||
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10",
|
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10",
|
||||||
@@ -92,36 +91,34 @@ const vector<string> Defaults = {
|
|||||||
"nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1",
|
"nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1",
|
||||||
"setoption name UCI_Chess960 value false"
|
"setoption name UCI_Chess960 value false"
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
/// setup_bench() builds a list of UCI commands to be run by bench. There
|
// Builds a list of UCI commands to be run by bench. There
|
||||||
/// are five parameters: TT size in MB, number of search threads that
|
// 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
|
// 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:
|
// where to look for positions in FEN format, and the type of the limit:
|
||||||
/// depth, perft, nodes and movetime (in millisecs), and evaluation type
|
// depth, perft, nodes and movetime (in milliseconds). Examples:
|
||||||
/// mixed (default), classical, NNUE.
|
//
|
||||||
///
|
// bench : search default positions up to depth 13
|
||||||
/// bench -> search default positions up to depth 13
|
// bench 64 1 15 : search default positions up to depth 15 (TT = 64MB)
|
||||||
/// 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 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 blah perft : run a perft 5 on positions in file "blah"
|
||||||
/// bench 16 1 5 default perft -> run a perft 5 on default positions
|
std::vector<std::string> setup_bench(const Position& current, 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
|
// Assign default values to missing arguments
|
||||||
string ttSize = (is >> token) ? token : "16";
|
std::string ttSize = (is >> token) ? token : "16";
|
||||||
string threads = (is >> token) ? token : "1";
|
std::string threads = (is >> token) ? token : "1";
|
||||||
string limit = (is >> token) ? token : "13";
|
std::string limit = (is >> token) ? token : "13";
|
||||||
string fenFile = (is >> token) ? token : "default";
|
std::string fenFile = (is >> token) ? token : "default";
|
||||||
string limitType = (is >> token) ? token : "depth";
|
std::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;
|
||||||
|
|
||||||
@@ -133,12 +130,12 @@ vector<string> setup_bench(const Position& current, istream& is) {
|
|||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string fen;
|
std::string fen;
|
||||||
ifstream file(fenFile);
|
std::ifstream file(fenFile);
|
||||||
|
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
{
|
{
|
||||||
cerr << "Unable to open file " << fenFile << endl;
|
std::cerr << "Unable to open file " << fenFile << std::endl;
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,24 +150,15 @@ vector<string> setup_bench(const Position& current, istream& is) {
|
|||||||
list.emplace_back("setoption name Hash value " + ttSize);
|
list.emplace_back("setoption name Hash value " + ttSize);
|
||||||
list.emplace_back("ucinewgame");
|
list.emplace_back("ucinewgame");
|
||||||
|
|
||||||
size_t posCounter = 0;
|
for (const std::string& fen : fens)
|
||||||
|
if (fen.find("setoption") != std::string::npos)
|
||||||
for (const string& fen : fens)
|
|
||||||
if (fen.find("setoption") != string::npos)
|
|
||||||
list.emplace_back(fen);
|
list.emplace_back(fen);
|
||||||
else
|
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("position fen " + fen);
|
||||||
list.emplace_back(go);
|
list.emplace_back(go);
|
||||||
++posCounter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
list.emplace_back("setoption name Use NNUE value true");
|
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
|
|||||||
-172
@@ -1,172 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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
|
|
||||||
+17
-19
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
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/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "bitboard.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
#include "bitboard.h"
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
@@ -42,20 +44,16 @@ namespace {
|
|||||||
|
|
||||||
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
|
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
|
||||||
|
|
||||||
}
|
// Returns the bitboard of target square for the given step
|
||||||
|
// from the given square. If the step is off the board, returns empty bitboard.
|
||||||
/// safe_destination() returns the bitboard of target square for the given step
|
Bitboard safe_destination(Square s, int step) {
|
||||||
/// from the given square. If the step is off the board, returns empty bitboard.
|
|
||||||
|
|
||||||
inline Bitboard safe_destination(Square s, int step) {
|
|
||||||
Square to = Square(s + step);
|
Square to = Square(s + step);
|
||||||
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
|
return is_ok(to) && distance(s, to) <= 2 ? square_bb(to) : Bitboard(0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns an ASCII representation of a bitboard suitable
|
||||||
/// Bitboards::pretty() returns an ASCII representation of a bitboard suitable
|
// to be printed to standard output. Useful for debugging.
|
||||||
/// to be printed to standard output. Useful for debugging.
|
|
||||||
|
|
||||||
std::string Bitboards::pretty(Bitboard b) {
|
std::string Bitboards::pretty(Bitboard b) {
|
||||||
|
|
||||||
std::string s = "+---+---+---+---+---+---+---+---+\n";
|
std::string s = "+---+---+---+---+---+---+---+---+\n";
|
||||||
@@ -73,9 +71,8 @@ std::string Bitboards::pretty(Bitboard b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Bitboards::init() initializes various bitboard tables. It is called at
|
// Initializes various bitboard tables. It is called at
|
||||||
/// startup and relies on global objects to be already zero-initialized.
|
// startup and relies on global objects to be already zero-initialized.
|
||||||
|
|
||||||
void Bitboards::init() {
|
void Bitboards::init() {
|
||||||
|
|
||||||
for (unsigned i = 0; i < (1 << 16); ++i)
|
for (unsigned i = 0; i < (1 << 16); ++i)
|
||||||
@@ -108,7 +105,8 @@ void Bitboards::init() {
|
|||||||
if (PseudoAttacks[pt][s1] & s2)
|
if (PseudoAttacks[pt][s1] & s2)
|
||||||
{
|
{
|
||||||
LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | 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] =
|
||||||
|
(attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
|
||||||
}
|
}
|
||||||
BetweenBB[s1][s2] |= s2;
|
BetweenBB[s1][s2] |= s2;
|
||||||
}
|
}
|
||||||
@@ -134,11 +132,10 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// init_magics() computes all rook and bishop attacks at startup. Magic
|
// Computes all rook and bishop attacks at startup. Magic
|
||||||
// bitboards are used to look up attacks of sliding pieces. As a reference see
|
// 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
|
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
|
||||||
// called "fancy" approach.
|
// called "fancy" approach.
|
||||||
|
|
||||||
void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
|
void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
|
||||||
|
|
||||||
// Optimal PRNG seeds to pick the correct magics in the shortest time
|
// Optimal PRNG seeds to pick the correct magics in the shortest time
|
||||||
@@ -169,7 +166,8 @@ namespace {
|
|||||||
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and
|
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and
|
||||||
// store the corresponding sliding attack bitboard in reference[].
|
// store the corresponding sliding attack bitboard in reference[].
|
||||||
b = size = 0;
|
b = size = 0;
|
||||||
do {
|
do
|
||||||
|
{
|
||||||
occupancy[size] = b;
|
occupancy[size] = b;
|
||||||
reference[size] = sliding_attack(pt, s, b);
|
reference[size] = sliding_attack(pt, s, b);
|
||||||
|
|
||||||
|
|||||||
+136
-215
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,19 +19,17 @@
|
|||||||
#ifndef BITBOARD_H_INCLUDED
|
#ifndef BITBOARD_H_INCLUDED
|
||||||
#define BITBOARD_H_INCLUDED
|
#define BITBOARD_H_INCLUDED
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace Bitbases {
|
|
||||||
|
|
||||||
void init();
|
|
||||||
bool probe(Square wksq, Square wpsq, Square bksq, Color us);
|
|
||||||
|
|
||||||
} // namespace Stockfish::Bitbases
|
|
||||||
|
|
||||||
namespace Bitboards {
|
namespace Bitboards {
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
@@ -39,9 +37,6 @@ std::string pretty(Bitboard b);
|
|||||||
|
|
||||||
} // namespace Stockfish::Bitboards
|
} // namespace Stockfish::Bitboards
|
||||||
|
|
||||||
constexpr Bitboard AllSquares = ~Bitboard(0);
|
|
||||||
constexpr Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL;
|
|
||||||
|
|
||||||
constexpr Bitboard FileABB = 0x0101010101010101ULL;
|
constexpr Bitboard FileABB = 0x0101010101010101ULL;
|
||||||
constexpr Bitboard FileBBB = FileABB << 1;
|
constexpr Bitboard FileBBB = FileABB << 1;
|
||||||
constexpr Bitboard FileCBB = FileABB << 2;
|
constexpr Bitboard FileCBB = FileABB << 2;
|
||||||
@@ -60,17 +55,6 @@ constexpr Bitboard Rank6BB = Rank1BB << (8 * 5);
|
|||||||
constexpr Bitboard Rank7BB = Rank1BB << (8 * 6);
|
constexpr Bitboard Rank7BB = Rank1BB << (8 * 6);
|
||||||
constexpr Bitboard Rank8BB = Rank1BB << (8 * 7);
|
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 PopCnt16[1 << 16];
|
||||||
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
|
||||||
|
|
||||||
@@ -80,7 +64,7 @@ extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB];
|
|||||||
extern Bitboard PawnAttacks[COLOR_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 {
|
struct Magic {
|
||||||
Bitboard mask;
|
Bitboard mask;
|
||||||
Bitboard magic;
|
Bitboard magic;
|
||||||
@@ -105,14 +89,14 @@ struct Magic {
|
|||||||
extern Magic RookMagics[SQUARE_NB];
|
extern Magic RookMagics[SQUARE_NB];
|
||||||
extern Magic BishopMagics[SQUARE_NB];
|
extern Magic BishopMagics[SQUARE_NB];
|
||||||
|
|
||||||
inline Bitboard square_bb(Square s) {
|
constexpr Bitboard square_bb(Square s) {
|
||||||
assert(is_ok(s));
|
assert(is_ok(s));
|
||||||
return (1ULL << s);
|
return (1ULL << s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Overloads of bitwise operators between a Bitboard and a Square for testing
|
// 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.
|
// 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); }
|
||||||
@@ -126,52 +110,40 @@ 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) {
|
constexpr bool more_than_one(Bitboard b) { return b & (b - 1); }
|
||||||
return b & (b - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
constexpr bool opposite_colors(Square s1, Square s2) {
|
// rank_bb() and file_bb() return a bitboard representing all the squares on
|
||||||
return (s1 + rank_of(s1) + s2 + rank_of(s2)) & 1;
|
// 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
|
// Moves a bitboard one or two steps as specified by the direction D
|
||||||
/// 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
|
|
||||||
|
|
||||||
template<Direction D>
|
template<Direction D>
|
||||||
constexpr Bitboard shift(Bitboard b) {
|
constexpr Bitboard shift(Bitboard b) {
|
||||||
return D == NORTH ? b << 8 : D == SOUTH ? b >> 8
|
return D == NORTH ? b << 8
|
||||||
: D == NORTH+NORTH? b <<16 : D == SOUTH+SOUTH? b >>16
|
: D == SOUTH ? b >> 8
|
||||||
: D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1
|
: D == NORTH + NORTH ? b << 16
|
||||||
: D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7
|
: D == SOUTH + SOUTH ? b >> 16
|
||||||
: D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9
|
: 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;
|
: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// pawn_attacks_bb() returns the squares attacked by pawns of the given color
|
// Returns the squares attacked by pawns of the given color
|
||||||
/// from the squares in the given bitboard.
|
// from the squares in the given bitboard.
|
||||||
|
|
||||||
template<Color C>
|
template<Color C>
|
||||||
constexpr Bitboard pawn_attacks_bb(Bitboard b) {
|
constexpr Bitboard pawn_attacks_bb(Bitboard b) {
|
||||||
return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
|
return C == WHITE ? shift<NORTH_WEST>(b) | shift<NORTH_EAST>(b)
|
||||||
@@ -184,125 +156,71 @@ inline Bitboard pawn_attacks_bb(Color c, Square s) {
|
|||||||
return PawnAttacks[c][s];
|
return PawnAttacks[c][s];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a bitboard representing an entire line (from board edge
|
||||||
/// pawn_double_attacks_bb() returns the squares doubly attacked by pawns of the
|
// to board edge) that intersects the two given squares. If the given squares
|
||||||
/// given color from the squares in the given bitboard.
|
// 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.
|
||||||
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.
|
|
||||||
|
|
||||||
inline Bitboard line_bb(Square s1, Square s2) {
|
inline Bitboard line_bb(Square s1, Square s2) {
|
||||||
|
|
||||||
assert(is_ok(s1) && is_ok(s2));
|
assert(is_ok(s1) && is_ok(s2));
|
||||||
|
|
||||||
return LineBB[s1][s2];
|
return LineBB[s1][s2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// between_bb(s1, s2) returns a bitboard representing the squares in the semi-open
|
// Returns a bitboard representing the squares in the semi-open
|
||||||
/// segment between the squares s1 and s2 (excluding s1 but including s2). If the
|
// 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,
|
// 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_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
|
// 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
|
// allows to generate non-king evasion moves faster: the defending piece must either
|
||||||
/// interpose itself to cover the check or capture the checking piece.
|
// interpose itself to cover the check or capture the checking piece.
|
||||||
|
|
||||||
inline Bitboard between_bb(Square s1, Square s2) {
|
inline Bitboard between_bb(Square s1, Square s2) {
|
||||||
|
|
||||||
assert(is_ok(s1) && is_ok(s2));
|
assert(is_ok(s1) && is_ok(s2));
|
||||||
|
|
||||||
return BetweenBB[s1][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) {
|
// distance() functions return the distance between x and y, defined as the
|
||||||
return c == WHITE ? ~Rank1BB << 8 * relative_rank(WHITE, s)
|
// number of steps for a king in x to reach y.
|
||||||
: ~Rank8BB >> 8 * relative_rank(BLACK, s);
|
|
||||||
|
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<>
|
||||||
/// forward_file_bb() returns a bitboard representing all the squares along the
|
inline int distance<Rank>(Square x, Square y) {
|
||||||
/// line in front of the given one, from the point of view of the given color.
|
return std::abs(rank_of(x) - rank_of(y));
|
||||||
|
|
||||||
constexpr Bitboard forward_file_bb(Color c, Square s) {
|
|
||||||
return forward_ranks_bb(c, s) & file_bb(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
/// pawn_attack_span() returns a bitboard representing all the squares that can
|
inline int distance<Square>(Square x, Square y) {
|
||||||
/// be attacked by a pawn of the given color when it moves along its file, starting
|
return SquareDistance[x][y];
|
||||||
/// from the given square.
|
|
||||||
|
|
||||||
constexpr Bitboard pawn_attack_span(Color c, Square s) {
|
|
||||||
return forward_ranks_bb(c, s) & adjacent_files_bb(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 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(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>
|
template<PieceType Pt>
|
||||||
inline Bitboard attacks_bb(Square s) {
|
inline Bitboard attacks_bb(Square s) {
|
||||||
|
|
||||||
assert((Pt != PAWN) && (is_ok(s)));
|
assert((Pt != PAWN) && (is_ok(s)));
|
||||||
|
|
||||||
return PseudoAttacks[Pt][s];
|
return PseudoAttacks[Pt][s];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// attacks_bb(Square, Bitboard) returns the attacks by the given piece
|
// Returns the attacks by the given piece
|
||||||
/// assuming the board is occupied according to the passed Bitboard.
|
// assuming the board is occupied according to the passed Bitboard.
|
||||||
/// Sliding piece attacks do not continue passed an occupied square.
|
// Sliding piece attacks do not continue passed an occupied square.
|
||||||
|
|
||||||
template<PieceType Pt>
|
template<PieceType Pt>
|
||||||
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
|
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
|
||||||
|
|
||||||
@@ -310,39 +228,52 @@ inline Bitboard attacks_bb(Square s, Bitboard occupied) {
|
|||||||
|
|
||||||
switch (Pt)
|
switch (Pt)
|
||||||
{
|
{
|
||||||
case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
|
case BISHOP :
|
||||||
case ROOK : return RookMagics[s].attacks[ RookMagics[s].index(occupied)];
|
return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
|
||||||
case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
case ROOK :
|
||||||
default : return PseudoAttacks[Pt][s];
|
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) {
|
inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
|
||||||
|
|
||||||
assert((pt != PAWN) && (is_ok(s)));
|
assert((pt != PAWN) && (is_ok(s)));
|
||||||
|
|
||||||
switch (pt)
|
switch (pt)
|
||||||
{
|
{
|
||||||
case BISHOP: return attacks_bb<BISHOP>(s, occupied);
|
case BISHOP :
|
||||||
case ROOK : return attacks_bb< ROOK>(s, occupied);
|
return attacks_bb<BISHOP>(s, occupied);
|
||||||
case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
|
case ROOK :
|
||||||
default : return PseudoAttacks[pt][s];
|
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) {
|
inline int popcount(Bitboard b) {
|
||||||
|
|
||||||
#ifndef USE_POPCNT
|
#ifndef USE_POPCNT
|
||||||
|
|
||||||
union { Bitboard bb; uint16_t u[4]; } v = { b };
|
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]];
|
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
|
||||||
|
|
||||||
@@ -351,85 +282,83 @@ inline int popcount(Bitboard b) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the least significant bit in a non-zero bitboard.
|
||||||
/// lsb() and msb() return the least/most significant bit in a non-zero bitboard
|
|
||||||
|
|
||||||
#if defined(__GNUC__) // GCC, Clang, ICC
|
|
||||||
|
|
||||||
inline Square lsb(Bitboard b) {
|
inline Square lsb(Bitboard b) {
|
||||||
assert(b);
|
assert(b);
|
||||||
|
|
||||||
|
#if defined(__GNUC__) // GCC, Clang, ICX
|
||||||
|
|
||||||
return Square(__builtin_ctzll(b));
|
return Square(__builtin_ctzll(b));
|
||||||
}
|
|
||||||
|
|
||||||
inline Square msb(Bitboard b) {
|
|
||||||
assert(b);
|
|
||||||
return Square(63 ^ __builtin_clzll(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(_MSC_VER) // MSVC
|
|
||||||
|
|
||||||
|
#elif defined(_MSC_VER)
|
||||||
#ifdef _WIN64 // MSVC, WIN64
|
#ifdef _WIN64 // MSVC, WIN64
|
||||||
|
|
||||||
inline Square lsb(Bitboard b) {
|
|
||||||
assert(b);
|
|
||||||
unsigned long idx;
|
unsigned long idx;
|
||||||
_BitScanForward64(&idx, b);
|
_BitScanForward64(&idx, b);
|
||||||
return (Square) idx;
|
return Square(idx);
|
||||||
}
|
|
||||||
|
|
||||||
inline Square msb(Bitboard b) {
|
|
||||||
assert(b);
|
|
||||||
unsigned long idx;
|
|
||||||
_BitScanReverse64(&idx, b);
|
|
||||||
return (Square) idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else // MSVC, WIN32
|
#else // MSVC, WIN32
|
||||||
|
|
||||||
inline Square lsb(Bitboard b) {
|
|
||||||
assert(b);
|
|
||||||
unsigned long idx;
|
unsigned long idx;
|
||||||
|
|
||||||
if (b & 0xffffffff) {
|
if (b & 0xffffffff)
|
||||||
|
{
|
||||||
_BitScanForward(&idx, int32_t(b));
|
_BitScanForward(&idx, int32_t(b));
|
||||||
return Square(idx);
|
return Square(idx);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
_BitScanForward(&idx, int32_t(b >> 32));
|
_BitScanForward(&idx, int32_t(b >> 32));
|
||||||
return Square(idx + 32);
|
return Square(idx + 32);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#else // Compiler is neither GCC nor MSVC compatible
|
||||||
|
#error "Compiler not supported."
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the most significant bit in a non-zero bitboard.
|
||||||
inline Square msb(Bitboard b) {
|
inline Square msb(Bitboard b) {
|
||||||
assert(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;
|
unsigned long idx;
|
||||||
|
|
||||||
if (b >> 32) {
|
if (b >> 32)
|
||||||
|
{
|
||||||
_BitScanReverse(&idx, int32_t(b >> 32));
|
_BitScanReverse(&idx, int32_t(b >> 32));
|
||||||
return Square(idx + 32);
|
return Square(idx + 32);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
_BitScanReverse(&idx, int32_t(b));
|
_BitScanReverse(&idx, int32_t(b));
|
||||||
return Square(idx);
|
return Square(idx);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#else // Compiler is neither GCC nor MSVC compatible
|
||||||
|
#error "Compiler not supported."
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
// Returns the bitboard of the least significant
|
||||||
|
// square of a non-zero bitboard. It is equivalent to square_bb(lsb(bb)).
|
||||||
#else // Compiler is neither GCC nor MSVC compatible
|
|
||||||
|
|
||||||
#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)).
|
|
||||||
|
|
||||||
inline Bitboard least_significant_square_bb(Bitboard b) {
|
inline Bitboard least_significant_square_bb(Bitboard b) {
|
||||||
assert(b);
|
assert(b);
|
||||||
return b & -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) {
|
inline Square pop_lsb(Bitboard& b) {
|
||||||
assert(b);
|
assert(b);
|
||||||
const Square s = lsb(b);
|
const Square s = lsb(b);
|
||||||
@@ -437,14 +366,6 @@ inline Square pop_lsb(Bitboard& b) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// 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
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef BITBOARD_H_INCLUDED
|
#endif // #ifndef BITBOARD_H_INCLUDED
|
||||||
|
|||||||
+480
@@ -0,0 +1,480 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef USE_MPI
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <istream>
|
||||||
|
#include <map>
|
||||||
|
#include <mpi.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "cluster.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "timeman.h"
|
||||||
|
#include "tt.h"
|
||||||
|
#include "search.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
namespace Cluster {
|
||||||
|
|
||||||
|
// Total number of ranks and rank within the communicator
|
||||||
|
static int world_rank = MPI_PROC_NULL;
|
||||||
|
static int world_size = 0;
|
||||||
|
|
||||||
|
// Signals between ranks exchange basic info using a dedicated communicator
|
||||||
|
static MPI_Comm signalsComm = MPI_COMM_NULL;
|
||||||
|
static MPI_Request reqSignals = MPI_REQUEST_NULL;
|
||||||
|
static uint64_t signalsCallCounter = 0;
|
||||||
|
|
||||||
|
// Signals are the number of nodes searched, stop, table base hits, transposition table saves
|
||||||
|
enum Signals : int {
|
||||||
|
SIG_NODES = 0,
|
||||||
|
SIG_STOP = 1,
|
||||||
|
SIG_TB = 2,
|
||||||
|
SIG_TTS = 3,
|
||||||
|
SIG_NB = 4
|
||||||
|
};
|
||||||
|
static uint64_t signalsSend[SIG_NB] = {};
|
||||||
|
static uint64_t signalsRecv[SIG_NB] = {};
|
||||||
|
static uint64_t nodesSearchedOthers = 0;
|
||||||
|
static uint64_t tbHitsOthers = 0;
|
||||||
|
static uint64_t TTsavesOthers = 0;
|
||||||
|
static uint64_t stopSignalsPosted = 0;
|
||||||
|
|
||||||
|
// The UCI threads of each rank exchange use a dedicated communicator
|
||||||
|
static MPI_Comm InputComm = MPI_COMM_NULL;
|
||||||
|
|
||||||
|
// bestMove requires MoveInfo communicators and data types
|
||||||
|
static MPI_Comm MoveComm = MPI_COMM_NULL;
|
||||||
|
static MPI_Datatype MIDatatype = MPI_DATATYPE_NULL;
|
||||||
|
|
||||||
|
// TT entries are communicated with a dedicated communicator.
|
||||||
|
// The receive buffer is used to gather information from all ranks.
|
||||||
|
// THe TTCacheCounter tracks the number of local elements that are ready to be sent.
|
||||||
|
static MPI_Comm TTComm = MPI_COMM_NULL;
|
||||||
|
static std::array<std::vector<KeyedTTEntry>, 2> TTSendRecvBuffs;
|
||||||
|
static std::array<MPI_Request, 2> reqsTTSendRecv = {MPI_REQUEST_NULL, MPI_REQUEST_NULL};
|
||||||
|
static uint64_t sendRecvPosted = 0;
|
||||||
|
static std::atomic<uint64_t> TTCacheCounter = {};
|
||||||
|
|
||||||
|
/// Initialize MPI and associated data types. Note that the MPI library must be configured
|
||||||
|
/// to support MPI_THREAD_MULTIPLE, since multiple threads access MPI simultaneously.
|
||||||
|
void init() {
|
||||||
|
|
||||||
|
int thread_support;
|
||||||
|
MPI_Init_thread(nullptr, nullptr, MPI_THREAD_MULTIPLE, &thread_support);
|
||||||
|
if (thread_support < MPI_THREAD_MULTIPLE)
|
||||||
|
{
|
||||||
|
std::cerr << "Stockfish requires support for MPI_THREAD_MULTIPLE." << std::endl;
|
||||||
|
std::exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
|
||||||
|
MPI_Comm_size(MPI_COMM_WORLD, &world_size);
|
||||||
|
|
||||||
|
const std::array<MPI_Aint, 5> MIdisps = {offsetof(MoveInfo, move), offsetof(MoveInfo, ponder),
|
||||||
|
offsetof(MoveInfo, depth), offsetof(MoveInfo, score),
|
||||||
|
offsetof(MoveInfo, rank)};
|
||||||
|
MPI_Type_create_hindexed_block(5, 1, MIdisps.data(), MPI_INT, &MIDatatype);
|
||||||
|
MPI_Type_commit(&MIDatatype);
|
||||||
|
|
||||||
|
MPI_Comm_dup(MPI_COMM_WORLD, &InputComm);
|
||||||
|
MPI_Comm_dup(MPI_COMM_WORLD, &TTComm);
|
||||||
|
MPI_Comm_dup(MPI_COMM_WORLD, &MoveComm);
|
||||||
|
MPI_Comm_dup(MPI_COMM_WORLD, &signalsComm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finalize MPI and free the associated data types.
|
||||||
|
void finalize() {
|
||||||
|
|
||||||
|
MPI_Type_free(&MIDatatype);
|
||||||
|
|
||||||
|
MPI_Comm_free(&InputComm);
|
||||||
|
MPI_Comm_free(&TTComm);
|
||||||
|
MPI_Comm_free(&MoveComm);
|
||||||
|
MPI_Comm_free(&signalsComm);
|
||||||
|
|
||||||
|
MPI_Finalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the total number of ranks
|
||||||
|
int size() { return world_size; }
|
||||||
|
|
||||||
|
/// Return the rank (index) of the process
|
||||||
|
int rank() { return world_rank; }
|
||||||
|
|
||||||
|
/// The receive buffer depends on the number of MPI ranks and threads, resize as needed
|
||||||
|
void ttSendRecvBuff_resize(size_t nThreads) {
|
||||||
|
|
||||||
|
for (int i : {0, 1})
|
||||||
|
{
|
||||||
|
TTSendRecvBuffs[i].resize(TTCacheSize * world_size * nThreads);
|
||||||
|
std::fill(TTSendRecvBuffs[i].begin(), TTSendRecvBuffs[i].end(), KeyedTTEntry());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// As input is only received by the root (rank 0) of the cluster, this input must be relayed
|
||||||
|
/// to the UCI threads of all ranks, in order to setup the position, etc. We do this with a
|
||||||
|
/// dedicated getline implementation, where the root broadcasts to all other ranks the received
|
||||||
|
/// information.
|
||||||
|
bool getline(std::istream& input, std::string& str) {
|
||||||
|
|
||||||
|
int size;
|
||||||
|
std::vector<char> vec;
|
||||||
|
int state;
|
||||||
|
|
||||||
|
if (is_root())
|
||||||
|
{
|
||||||
|
state = static_cast<bool>(std::getline(input, str));
|
||||||
|
vec.assign(str.begin(), str.end());
|
||||||
|
size = vec.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some MPI implementations use busy-wait polling, while we need yielding as otherwise
|
||||||
|
// the UCI thread on the non-root ranks would be consuming resources.
|
||||||
|
static MPI_Request reqInput = MPI_REQUEST_NULL;
|
||||||
|
MPI_Ibcast(&size, 1, MPI_INT, 0, InputComm, &reqInput);
|
||||||
|
if (is_root())
|
||||||
|
MPI_Wait(&reqInput, MPI_STATUS_IGNORE);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int flag;
|
||||||
|
MPI_Test(&reqInput, &flag, MPI_STATUS_IGNORE);
|
||||||
|
if (flag)
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast received string
|
||||||
|
if (!is_root())
|
||||||
|
vec.resize(size);
|
||||||
|
MPI_Bcast(vec.data(), size, MPI_CHAR, 0, InputComm);
|
||||||
|
if (!is_root())
|
||||||
|
str.assign(vec.begin(), vec.end());
|
||||||
|
MPI_Bcast(&state, 1, MPI_INT, 0, InputComm);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sending part of the signal communication loop
|
||||||
|
namespace {
|
||||||
|
void signals_send(const ThreadPool& threads) {
|
||||||
|
|
||||||
|
signalsSend[SIG_NODES] = threads.nodes_searched();
|
||||||
|
signalsSend[SIG_TB] = threads.tb_hits();
|
||||||
|
signalsSend[SIG_TTS] = threads.TT_saves();
|
||||||
|
signalsSend[SIG_STOP] = threads.stop;
|
||||||
|
MPI_Iallreduce(signalsSend, signalsRecv, SIG_NB, MPI_UINT64_T, MPI_SUM, signalsComm,
|
||||||
|
&reqSignals);
|
||||||
|
++signalsCallCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Processing part of the signal communication loop.
|
||||||
|
/// For some counters (e.g. nodes) we only keep their sum on the other nodes
|
||||||
|
/// allowing to add local counters at any time for more fine grained process,
|
||||||
|
/// which is useful to indicate progress during early iterations, and to have
|
||||||
|
/// node counts that exactly match the non-MPI code in the single rank case.
|
||||||
|
/// This call also propagates the stop signal between ranks.
|
||||||
|
void signals_process(ThreadPool& threads) {
|
||||||
|
|
||||||
|
nodesSearchedOthers = signalsRecv[SIG_NODES] - signalsSend[SIG_NODES];
|
||||||
|
tbHitsOthers = signalsRecv[SIG_TB] - signalsSend[SIG_TB];
|
||||||
|
TTsavesOthers = signalsRecv[SIG_TTS] - signalsSend[SIG_TTS];
|
||||||
|
stopSignalsPosted = signalsRecv[SIG_STOP];
|
||||||
|
if (signalsRecv[SIG_STOP] > 0)
|
||||||
|
threads.stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendrecv_post() {
|
||||||
|
|
||||||
|
++sendRecvPosted;
|
||||||
|
MPI_Irecv(TTSendRecvBuffs[sendRecvPosted % 2].data(),
|
||||||
|
TTSendRecvBuffs[sendRecvPosted % 2].size() * sizeof(KeyedTTEntry), MPI_BYTE,
|
||||||
|
(rank() + size() - 1) % size(), 42, TTComm, &reqsTTSendRecv[0]);
|
||||||
|
MPI_Isend(TTSendRecvBuffs[(sendRecvPosted + 1) % 2].data(),
|
||||||
|
TTSendRecvBuffs[(sendRecvPosted + 1) % 2].size() * sizeof(KeyedTTEntry), MPI_BYTE,
|
||||||
|
(rank() + 1) % size(), 42, TTComm, &reqsTTSendRecv[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// During search, most message passing is asynchronous, but at the end of
|
||||||
|
/// search it makes sense to bring them to a common, finalized state.
|
||||||
|
void signals_sync(ThreadPool& threads) {
|
||||||
|
|
||||||
|
while (stopSignalsPosted < uint64_t(size()))
|
||||||
|
signals_poll(threads);
|
||||||
|
|
||||||
|
// Finalize outstanding messages of the signal loops.
|
||||||
|
// We might have issued one call less than needed on some ranks.
|
||||||
|
uint64_t globalCounter;
|
||||||
|
MPI_Allreduce(&signalsCallCounter, &globalCounter, 1, MPI_UINT64_T, MPI_MAX, MoveComm);
|
||||||
|
if (signalsCallCounter < globalCounter)
|
||||||
|
{
|
||||||
|
MPI_Wait(&reqSignals, MPI_STATUS_IGNORE);
|
||||||
|
signals_send(threads);
|
||||||
|
}
|
||||||
|
assert(signalsCallCounter == globalCounter);
|
||||||
|
MPI_Wait(&reqSignals, MPI_STATUS_IGNORE);
|
||||||
|
signals_process(threads);
|
||||||
|
|
||||||
|
// Finalize outstanding messages in the sendRecv loop
|
||||||
|
MPI_Allreduce(&sendRecvPosted, &globalCounter, 1, MPI_UINT64_T, MPI_MAX, MoveComm);
|
||||||
|
while (sendRecvPosted < globalCounter)
|
||||||
|
{
|
||||||
|
MPI_Waitall(reqsTTSendRecv.size(), reqsTTSendRecv.data(), MPI_STATUSES_IGNORE);
|
||||||
|
sendrecv_post();
|
||||||
|
}
|
||||||
|
assert(sendRecvPosted == globalCounter);
|
||||||
|
MPI_Waitall(reqsTTSendRecv.size(), reqsTTSendRecv.data(), MPI_STATUSES_IGNORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize signal counters to zero.
|
||||||
|
void signals_init() {
|
||||||
|
|
||||||
|
stopSignalsPosted = tbHitsOthers = TTsavesOthers = nodesSearchedOthers = 0;
|
||||||
|
|
||||||
|
signalsSend[SIG_NODES] = signalsRecv[SIG_NODES] = 0;
|
||||||
|
signalsSend[SIG_TB] = signalsRecv[SIG_TB] = 0;
|
||||||
|
signalsSend[SIG_TTS] = signalsRecv[SIG_TTS] = 0;
|
||||||
|
signalsSend[SIG_STOP] = signalsRecv[SIG_STOP] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Poll the signal loop, and start next round as needed.
|
||||||
|
void signals_poll(ThreadPool& threads) {
|
||||||
|
|
||||||
|
int flag;
|
||||||
|
MPI_Test(&reqSignals, &flag, MPI_STATUS_IGNORE);
|
||||||
|
if (flag)
|
||||||
|
{
|
||||||
|
signals_process(threads);
|
||||||
|
signals_send(threads);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide basic info related the cluster performance, in particular, the number of signals send,
|
||||||
|
/// signals per sounds (sps), the number of gathers, the number of positions gathered (per node and per second, gpps)
|
||||||
|
/// The number of TT saves and TT saves per second. If gpps equals approximately TTSavesps the gather loop has enough bandwidth.
|
||||||
|
void cluster_info(const ThreadPool& threads, Depth depth, TimePoint elapsed) {
|
||||||
|
|
||||||
|
// TimePoint elapsed = Time.elapsed() + 1;
|
||||||
|
uint64_t TTSaves = TT_saves(threads);
|
||||||
|
|
||||||
|
sync_cout << "info depth " << depth << " cluster "
|
||||||
|
<< " signals " << signalsCallCounter << " sps " << signalsCallCounter * 1000 / elapsed
|
||||||
|
<< " sendRecvs " << sendRecvPosted << " srpps "
|
||||||
|
<< TTSendRecvBuffs[0].size() * sendRecvPosted * 1000 / elapsed << " TTSaves "
|
||||||
|
<< TTSaves << " TTSavesps " << TTSaves * 1000 / elapsed << sync_endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When a TT entry is saved, additional steps are taken if the entry is of sufficient depth.
|
||||||
|
/// If sufficient entries has been collected, a communication is initiated.
|
||||||
|
/// If a communication has been completed, the received results are saved to the TT.
|
||||||
|
void save(TranspositionTable& TT,
|
||||||
|
ThreadPool& threads,
|
||||||
|
Search::Worker* thread,
|
||||||
|
TTEntry* tte,
|
||||||
|
Key k,
|
||||||
|
Value v,
|
||||||
|
bool PvHit,
|
||||||
|
Bound b,
|
||||||
|
Depth d,
|
||||||
|
Move m,
|
||||||
|
Value ev,
|
||||||
|
uint8_t generation8) {
|
||||||
|
|
||||||
|
// Standard save to the TT
|
||||||
|
tte->save(k, v, PvHit, b, d, m, ev, generation8);
|
||||||
|
|
||||||
|
// If the entry is of sufficient depth to be worth communicating, take action.
|
||||||
|
if (d > 3)
|
||||||
|
{
|
||||||
|
// count the TTsaves to information: this should be relatively similar
|
||||||
|
// to the number of entries we can send/recv.
|
||||||
|
thread->TTsaves.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
|
||||||
|
// Add to thread's send buffer, the locking here avoids races when the master thread
|
||||||
|
// prepares the send buffer.
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(thread->ttCache.mutex);
|
||||||
|
thread->ttCache.buffer.replace(KeyedTTEntry(k, *tte));
|
||||||
|
++TTCacheCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t recvBuffPerRankSize = threads.size() * TTCacheSize;
|
||||||
|
|
||||||
|
// Communicate on main search thread, as soon the threads combined have collected
|
||||||
|
// sufficient data to fill the send buffers.
|
||||||
|
if (thread == threads.main_thread()->worker.get() && TTCacheCounter > recvBuffPerRankSize)
|
||||||
|
{
|
||||||
|
// Test communication status
|
||||||
|
int flag;
|
||||||
|
MPI_Testall(reqsTTSendRecv.size(), reqsTTSendRecv.data(), &flag, MPI_STATUSES_IGNORE);
|
||||||
|
|
||||||
|
// Current communication is complete
|
||||||
|
if (flag)
|
||||||
|
{
|
||||||
|
// Save all received entries to TT, and store our TTCaches, ready for the next round of communication
|
||||||
|
for (size_t irank = 0; irank < size_t(size()); ++irank)
|
||||||
|
{
|
||||||
|
if (irank
|
||||||
|
== size_t(
|
||||||
|
rank())) // this is our part, fill the part of the buffer for sending
|
||||||
|
{
|
||||||
|
// Copy from the thread caches to the right spot in the buffer
|
||||||
|
size_t i = irank * recvBuffPerRankSize;
|
||||||
|
for (auto&& th : threads)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(th->worker->ttCache.mutex);
|
||||||
|
|
||||||
|
for (auto&& e : th->worker->ttCache.buffer)
|
||||||
|
TTSendRecvBuffs[sendRecvPosted % 2][i++] = e;
|
||||||
|
|
||||||
|
// Reset thread's send buffer
|
||||||
|
th->worker->ttCache.buffer = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
TTCacheCounter = 0;
|
||||||
|
}
|
||||||
|
else // process data received from the corresponding rank.
|
||||||
|
for (size_t i = irank * recvBuffPerRankSize;
|
||||||
|
i < (irank + 1) * recvBuffPerRankSize; ++i)
|
||||||
|
{
|
||||||
|
auto&& e = TTSendRecvBuffs[sendRecvPosted % 2][i];
|
||||||
|
bool found;
|
||||||
|
TTEntry* replace_tte;
|
||||||
|
replace_tte = TT.probe(e.first, found);
|
||||||
|
replace_tte->save(e.first, e.second.value(), e.second.is_pv(),
|
||||||
|
e.second.bound(), e.second.depth(), e.second.move(),
|
||||||
|
e.second.eval(), TT.generation());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start next communication
|
||||||
|
sendrecv_post();
|
||||||
|
|
||||||
|
// Force check of time on the next occasion, the above actions might have taken some time.
|
||||||
|
thread->main_manager()->callsCnt = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Picks the bestMove across ranks, and send the associated info and PV to the root of the cluster.
|
||||||
|
/// Note that this bestMove and PV must be output by the root, the guarantee proper ordering of output.
|
||||||
|
/// TODO update to the scheme in master.. can this use aggregation of votes?
|
||||||
|
void pick_moves(MoveInfo& mi, std::string& PVLine) {
|
||||||
|
|
||||||
|
MoveInfo* pMoveInfo = NULL;
|
||||||
|
if (is_root())
|
||||||
|
{
|
||||||
|
pMoveInfo = (MoveInfo*) malloc(sizeof(MoveInfo) * size());
|
||||||
|
}
|
||||||
|
MPI_Gather(&mi, 1, MIDatatype, pMoveInfo, 1, MIDatatype, 0, MoveComm);
|
||||||
|
|
||||||
|
if (is_root())
|
||||||
|
{
|
||||||
|
std::map<int, int> votes;
|
||||||
|
int minScore = pMoveInfo[0].score;
|
||||||
|
for (int i = 0; i < size(); ++i)
|
||||||
|
{
|
||||||
|
minScore = std::min(minScore, pMoveInfo[i].score);
|
||||||
|
votes[pMoveInfo[i].move] = 0;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < size(); ++i)
|
||||||
|
{
|
||||||
|
votes[pMoveInfo[i].move] += pMoveInfo[i].score - minScore + pMoveInfo[i].depth;
|
||||||
|
}
|
||||||
|
int bestVote = votes[pMoveInfo[0].move];
|
||||||
|
for (int i = 0; i < size(); ++i)
|
||||||
|
{
|
||||||
|
if (votes[pMoveInfo[i].move] > bestVote)
|
||||||
|
{
|
||||||
|
bestVote = votes[pMoveInfo[i].move];
|
||||||
|
mi = pMoveInfo[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(pMoveInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send around the final result
|
||||||
|
MPI_Bcast(&mi, 1, MIDatatype, 0, MoveComm);
|
||||||
|
|
||||||
|
// Send PV line to root as needed
|
||||||
|
if (mi.rank != 0 && mi.rank == rank())
|
||||||
|
{
|
||||||
|
int size;
|
||||||
|
std::vector<char> vec;
|
||||||
|
vec.assign(PVLine.begin(), PVLine.end());
|
||||||
|
size = vec.size();
|
||||||
|
MPI_Send(&size, 1, MPI_INT, 0, 42, MoveComm);
|
||||||
|
MPI_Send(vec.data(), size, MPI_CHAR, 0, 42, MoveComm);
|
||||||
|
}
|
||||||
|
if (mi.rank != 0 && is_root())
|
||||||
|
{
|
||||||
|
int size;
|
||||||
|
std::vector<char> vec;
|
||||||
|
MPI_Recv(&size, 1, MPI_INT, mi.rank, 42, MoveComm, MPI_STATUS_IGNORE);
|
||||||
|
vec.resize(size);
|
||||||
|
MPI_Recv(vec.data(), size, MPI_CHAR, mi.rank, 42, MoveComm, MPI_STATUS_IGNORE);
|
||||||
|
PVLine.assign(vec.begin(), vec.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return nodes searched (lazily updated cluster wide in the signal loop)
|
||||||
|
uint64_t nodes_searched(const ThreadPool& threads) {
|
||||||
|
return nodesSearchedOthers + threads.nodes_searched();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return table base hits (lazily updated cluster wide in the signal loop)
|
||||||
|
uint64_t tb_hits(const ThreadPool& threads) { return tbHitsOthers + threads.tb_hits(); }
|
||||||
|
|
||||||
|
/// Return the number of saves to the TT buffers, (lazily updated cluster wide in the signal loop)
|
||||||
|
uint64_t TT_saves(const ThreadPool& threads) { return TTsavesOthers + threads.TT_saves(); }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include "cluster.h"
|
||||||
|
#include "thread.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
namespace Cluster {
|
||||||
|
|
||||||
|
uint64_t nodes_searched(const ThreadPool& threads) { return threads.nodes_searched(); }
|
||||||
|
|
||||||
|
uint64_t tb_hits(const ThreadPool& threads) { return threads.tb_hits(); }
|
||||||
|
|
||||||
|
uint64_t TT_saves(const ThreadPool& threads) { return threads.TT_saves(); }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // USE_MPI
|
||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
|
Copyright (C) 2004-2020 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 CLUSTER_H_INCLUDED
|
||||||
|
#define CLUSTER_H_INCLUDED
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <istream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "tt.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
class Thread;
|
||||||
|
class ThreadPool;
|
||||||
|
|
||||||
|
namespace Search {
|
||||||
|
class Worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The Cluster namespace contains functionality required to run on distributed
|
||||||
|
/// memory architectures using MPI as the message passing interface. On a high level,
|
||||||
|
/// a 'lazy SMP'-like scheme is implemented where TT saves of sufficient depth are
|
||||||
|
/// collected on each rank and distributed to, and used by, all other ranks,
|
||||||
|
/// which search essentially independently. The root (MPI rank 0) of the cluster
|
||||||
|
/// is responsible for all I/O and time management, communicating this info to
|
||||||
|
/// the other ranks as needed. UCI options such as Threads and Hash specify these
|
||||||
|
/// quantities per MPI rank. It is recommended to have one rank (MPI process) per node.
|
||||||
|
/// For the non-MPI case, wrappers that will be compiler-optimized away are provided.
|
||||||
|
|
||||||
|
namespace Cluster {
|
||||||
|
|
||||||
|
/// Basic info to find the cluster-wide bestMove
|
||||||
|
struct MoveInfo {
|
||||||
|
int move;
|
||||||
|
int ponder;
|
||||||
|
int depth;
|
||||||
|
int score;
|
||||||
|
int rank;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef USE_MPI
|
||||||
|
|
||||||
|
// store the TTEntry with its full key, so it can be saved on the receiver side
|
||||||
|
using KeyedTTEntry = std::pair<Key, TTEntry>;
|
||||||
|
constexpr std::size_t TTCacheSize = 16;
|
||||||
|
|
||||||
|
// Threads locally cache their high-depth TT entries till a batch can be send by MPI
|
||||||
|
template<std::size_t N>
|
||||||
|
class TTCache: public std::array<KeyedTTEntry, N> {
|
||||||
|
|
||||||
|
struct Compare {
|
||||||
|
inline bool operator()(const KeyedTTEntry& lhs, const KeyedTTEntry& rhs) {
|
||||||
|
return lhs.second.depth() > rhs.second.depth();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Compare compare;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Keep a heap of entries replacing low depth with high depth entries
|
||||||
|
bool replace(const KeyedTTEntry& value) {
|
||||||
|
|
||||||
|
if (compare(value, this->front()))
|
||||||
|
{
|
||||||
|
std::pop_heap(this->begin(), this->end(), compare);
|
||||||
|
this->back() = value;
|
||||||
|
std::push_heap(this->begin(), this->end(), compare);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void init();
|
||||||
|
void finalize();
|
||||||
|
bool getline(std::istream& input, std::string& str);
|
||||||
|
int size();
|
||||||
|
int rank();
|
||||||
|
inline bool is_root() { return rank() == 0; }
|
||||||
|
void save(TranspositionTable&,
|
||||||
|
ThreadPool&,
|
||||||
|
Search::Worker* thread,
|
||||||
|
TTEntry* tte,
|
||||||
|
Key k,
|
||||||
|
Value v,
|
||||||
|
bool PvHit,
|
||||||
|
Bound b,
|
||||||
|
Depth d,
|
||||||
|
Move m,
|
||||||
|
Value ev,
|
||||||
|
uint8_t generation8);
|
||||||
|
void pick_moves(MoveInfo& mi, std::string& PVLine);
|
||||||
|
void ttSendRecvBuff_resize(size_t nThreads);
|
||||||
|
uint64_t nodes_searched(const ThreadPool&);
|
||||||
|
uint64_t tb_hits(const ThreadPool&);
|
||||||
|
uint64_t TT_saves(const ThreadPool&);
|
||||||
|
void cluster_info(const ThreadPool&, Depth depth, TimePoint elapsed);
|
||||||
|
void signals_init();
|
||||||
|
void signals_poll(ThreadPool& threads);
|
||||||
|
void signals_sync(ThreadPool& threads);
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
inline void init() {}
|
||||||
|
inline void finalize() {}
|
||||||
|
inline bool getline(std::istream& input, std::string& str) {
|
||||||
|
return static_cast<bool>(std::getline(input, str));
|
||||||
|
}
|
||||||
|
constexpr int size() { return 1; }
|
||||||
|
constexpr int rank() { return 0; }
|
||||||
|
constexpr bool is_root() { return true; }
|
||||||
|
inline void save(TranspositionTable&,
|
||||||
|
ThreadPool&,
|
||||||
|
Search::Worker*,
|
||||||
|
TTEntry* tte,
|
||||||
|
Key k,
|
||||||
|
Value v,
|
||||||
|
bool PvHit,
|
||||||
|
Bound b,
|
||||||
|
Depth d,
|
||||||
|
Move m,
|
||||||
|
Value ev,
|
||||||
|
uint8_t generation8) {
|
||||||
|
tte->save(k, v, PvHit, b, d, m, ev, generation8);
|
||||||
|
}
|
||||||
|
inline void pick_moves(MoveInfo&, std::string&) {}
|
||||||
|
inline void ttSendRecvBuff_resize(size_t) {}
|
||||||
|
uint64_t nodes_searched(const ThreadPool&);
|
||||||
|
uint64_t tb_hits(const ThreadPool&);
|
||||||
|
uint64_t TT_saves(const ThreadPool&);
|
||||||
|
inline void cluster_info(const ThreadPool&, Depth, TimePoint) {}
|
||||||
|
inline void signals_init() {}
|
||||||
|
inline void signals_poll(ThreadPool& threads) {}
|
||||||
|
inline void signals_sync(ThreadPool& threads) {}
|
||||||
|
|
||||||
|
#endif /* USE_MPI */
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // #ifndef CLUSTER_H_INCLUDED
|
||||||
-747
@@ -1,747 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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
|
|
||||||
-126
@@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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
|
|
||||||
+57
-1096
File diff suppressed because it is too large
Load Diff
+12
-12
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
#define EVALUATE_H_INCLUDED
|
#define EVALUATE_H_INCLUDED
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
@@ -30,23 +29,24 @@ class Position;
|
|||||||
|
|
||||||
namespace Eval {
|
namespace Eval {
|
||||||
|
|
||||||
std::string trace(Position& pos);
|
constexpr inline int SmallNetThreshold = 1165, PsqtOnlyThreshold = 2500;
|
||||||
Value evaluate(const Position& pos);
|
|
||||||
|
|
||||||
extern bool useNNUE;
|
|
||||||
extern std::string currentEvalFileName;
|
|
||||||
|
|
||||||
// The default net name MUST follow the format nn-[SHA256 first 12 digits].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
|
// 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.
|
// name of the macro or the location where this macro is defined, as it is used
|
||||||
#define EvalFileDefaultName "nn-5af11540bbfe.nnue"
|
// in the Makefile/Fishtest.
|
||||||
|
#define EvalFileDefaultNameBig "nn-ae6a388e4a1a.nnue"
|
||||||
|
#define EvalFileDefaultNameSmall "nn-baff1ede1f90.nnue"
|
||||||
|
|
||||||
namespace NNUE {
|
namespace NNUE {
|
||||||
|
struct Networks;
|
||||||
|
}
|
||||||
|
|
||||||
void init();
|
std::string trace(Position& pos, const Eval::NNUE::Networks& networks);
|
||||||
void verify();
|
|
||||||
|
int simple_eval(const Position& pos, Color c);
|
||||||
|
Value evaluate(const NNUE::Networks& networks, const Position& pos, int optimism);
|
||||||
|
|
||||||
} // namespace NNUE
|
|
||||||
|
|
||||||
} // namespace Eval
|
} // namespace Eval
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -3,8 +3,8 @@
|
|||||||
* @author Dale Weiler
|
* @author Dale Weiler
|
||||||
* @brief Utility for including binary files
|
* @brief Utility for including binary files
|
||||||
*
|
*
|
||||||
* Facilities for including binary files into the current translation unit and
|
* Facilities for including binary files into the current translation unit
|
||||||
* making use from them externally in other translation units.
|
* and making use of them externally in other translation units.
|
||||||
*/
|
*/
|
||||||
#ifndef INCBIN_HDR
|
#ifndef INCBIN_HDR
|
||||||
#define INCBIN_HDR
|
#define INCBIN_HDR
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__APPLE__)
|
#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_SECTION INCBIN_OUTPUT_SECTION "\n"
|
||||||
# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
|
# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n"
|
||||||
# define INCBIN_INT ".long "
|
# define INCBIN_INT ".long "
|
||||||
@@ -261,8 +261,8 @@
|
|||||||
INCBIN_STRINGIZE( \
|
INCBIN_STRINGIZE( \
|
||||||
INCBIN_STYLE_IDENT(TYPE)) \
|
INCBIN_STYLE_IDENT(TYPE)) \
|
||||||
|
|
||||||
/* Generate the global labels by indirectly invoking the macro with our style
|
/* Generate the global labels by indirectly invoking the macro
|
||||||
* type and concatenating the name against them. */
|
* with our style type and concatenate the name against them. */
|
||||||
#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
|
#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \
|
||||||
INCBIN_INVOKE( \
|
INCBIN_INVOKE( \
|
||||||
INCBIN_GLOBAL, \
|
INCBIN_GLOBAL, \
|
||||||
|
|||||||
+13
-18
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,35 +19,30 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "endgame.h"
|
#include "misc.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "psqt.h"
|
#include "tune.h"
|
||||||
#include "search.h"
|
#include "types.h"
|
||||||
#include "syzygy/tbprobe.h"
|
|
||||||
#include "thread.h"
|
|
||||||
#include "tt.h"
|
|
||||||
#include "uci.h"
|
#include "uci.h"
|
||||||
|
|
||||||
using namespace Stockfish;
|
using namespace Stockfish;
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
|
||||||
|
Cluster::init();
|
||||||
|
if (Cluster::is_root())
|
||||||
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();
|
Bitboards::init();
|
||||||
Position::init();
|
Position::init();
|
||||||
Bitbases::init();
|
|
||||||
Endgames::init();
|
|
||||||
Threads.set(size_t(Options["Threads"]));
|
|
||||||
Search::clear(); // After threads are up
|
|
||||||
Eval::NNUE::init();
|
|
||||||
|
|
||||||
UCI::loop(argc, argv);
|
UCI uci(argc, argv);
|
||||||
|
|
||||||
|
Tune::init(uci.options);
|
||||||
|
|
||||||
|
uci.loop();
|
||||||
|
|
||||||
|
Cluster::finalize();
|
||||||
|
|
||||||
Threads.set(0);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,229 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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 nullptr, 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];
|
|
||||||
};
|
|
||||||
|
|
||||||
using Table = HashTable<Entry, 8192>;
|
|
||||||
|
|
||||||
Entry* probe(const Position& pos);
|
|
||||||
|
|
||||||
} // namespace Stockfish::Material
|
|
||||||
|
|
||||||
#endif // #ifndef MATERIAL_H_INCLUDED
|
|
||||||
+144
-164
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,6 +16,8 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "misc.h"
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#if _WIN32_WINNT < 0x0601
|
#if _WIN32_WINNT < 0x0601
|
||||||
#undef _WIN32_WINNT
|
#undef _WIN32_WINNT
|
||||||
@@ -33,7 +35,8 @@
|
|||||||
// first to define the corresponding function pointers.
|
// first to define the corresponding function pointers.
|
||||||
extern "C" {
|
extern "C" {
|
||||||
using fun1_t = bool (*)(LOGICAL_PROCESSOR_RELATIONSHIP,
|
using fun1_t = bool (*)(LOGICAL_PROCESSOR_RELATIONSHIP,
|
||||||
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
|
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX,
|
||||||
|
PDWORD);
|
||||||
using fun2_t = bool (*)(USHORT, PGROUP_AFFINITY);
|
using fun2_t = bool (*)(USHORT, PGROUP_AFFINITY);
|
||||||
using fun3_t = bool (*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
|
using fun3_t = bool (*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
|
||||||
using fun4_t = bool (*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
|
using fun4_t = bool (*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
|
||||||
@@ -44,53 +47,54 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <mutex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
#if defined(__linux__) && !defined(__ANDROID__)
|
#if defined(__linux__) && !defined(__ANDROID__)
|
||||||
#include <stdlib.h>
|
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__)
|
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \
|
||||||
|
|| (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \
|
||||||
|
|| defined(__e2k__)
|
||||||
#define POSIXALIGNEDALLOC
|
#define POSIXALIGNEDALLOC
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "misc.h"
|
|
||||||
#include "thread.h"
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
/// Version number or dev.
|
// Version number or dev.
|
||||||
constexpr string_view version = "16";
|
constexpr std::string_view version = "dev";
|
||||||
|
|
||||||
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
|
// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
|
||||||
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
|
||||||
/// can toggle the logging of std::cout and std:cin at runtime whilst preserving
|
// can toggle the logging of std::cout and std:cin at runtime whilst preserving
|
||||||
/// usual I/O functionality, all without changing a single line of code!
|
// usual I/O functionality, all without changing a single line of code!
|
||||||
/// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
|
// Idea from http://groups.google.com/group/comp.lang.c++/msg/1d941c0f26ea0d81
|
||||||
|
|
||||||
struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout
|
struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout
|
||||||
|
|
||||||
Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {}
|
Tie(std::streambuf* b, std::streambuf* l) :
|
||||||
|
buf(b),
|
||||||
|
logBuf(l) {}
|
||||||
|
|
||||||
int sync() override { return logBuf->pubsync(), buf->pubsync(); }
|
int sync() override { return logBuf->pubsync(), buf->pubsync(); }
|
||||||
int overflow(int c) override { return log(buf->sputc((char)c), "<< "); }
|
int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); }
|
||||||
int underflow() override { return buf->sgetc(); }
|
int underflow() override { return buf->sgetc(); }
|
||||||
int uflow() override { return log(buf->sbumpc(), ">> "); }
|
int uflow() override { return log(buf->sbumpc(), ">> "); }
|
||||||
|
|
||||||
streambuf *buf, *logBuf;
|
std::streambuf *buf, *logBuf;
|
||||||
|
|
||||||
int log(int c, const char* prefix) {
|
int log(int c, const char* prefix) {
|
||||||
|
|
||||||
@@ -99,16 +103,18 @@ struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout
|
|||||||
if (last == '\n')
|
if (last == '\n')
|
||||||
logBuf->sputn(prefix, 3);
|
logBuf->sputn(prefix, 3);
|
||||||
|
|
||||||
return last = logBuf->sputc((char)c);
|
return last = logBuf->sputc(char(c));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
|
|
||||||
Logger() : in(cin.rdbuf(), file.rdbuf()), out(cout.rdbuf(), file.rdbuf()) {}
|
Logger() :
|
||||||
|
in(std::cin.rdbuf(), file.rdbuf()),
|
||||||
|
out(std::cout.rdbuf(), file.rdbuf()) {}
|
||||||
~Logger() { start(""); }
|
~Logger() { start(""); }
|
||||||
|
|
||||||
ofstream file;
|
std::ofstream file;
|
||||||
Tie in, out;
|
Tie in, out;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -118,23 +124,23 @@ public:
|
|||||||
|
|
||||||
if (l.file.is_open())
|
if (l.file.is_open())
|
||||||
{
|
{
|
||||||
cout.rdbuf(l.out.buf);
|
std::cout.rdbuf(l.out.buf);
|
||||||
cin.rdbuf(l.in.buf);
|
std::cin.rdbuf(l.in.buf);
|
||||||
l.file.close();
|
l.file.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fname.empty())
|
if (!fname.empty())
|
||||||
{
|
{
|
||||||
l.file.open(fname, ifstream::out);
|
l.file.open(fname, std::ifstream::out);
|
||||||
|
|
||||||
if (!l.file.is_open())
|
if (!l.file.is_open())
|
||||||
{
|
{
|
||||||
cerr << "Unable to open debug log file " << fname << endl;
|
std::cerr << "Unable to open debug log file " << fname << std::endl;
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
cin.rdbuf(&l.in);
|
std::cin.rdbuf(&l.in);
|
||||||
cout.rdbuf(&l.out);
|
std::cout.rdbuf(&l.out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -142,19 +148,18 @@ public:
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
/// engine_info() returns the full name of the current Stockfish version.
|
// Returns the full name of the current Stockfish version.
|
||||||
/// For local dev compiles we try to append the commit sha and commit date
|
// For local dev compiles we try to append the commit sha and commit date
|
||||||
/// from git if that fails only the local compilation date is set and "nogit" is specified:
|
// from git if that fails only the local compilation date is set and "nogit" is specified:
|
||||||
/// Stockfish dev-YYYYMMDD-SHA
|
// Stockfish dev-YYYYMMDD-SHA
|
||||||
/// or
|
// or
|
||||||
/// Stockfish dev-YYYYMMDD-nogit
|
// Stockfish dev-YYYYMMDD-nogit
|
||||||
///
|
//
|
||||||
/// For releases (non dev builds) we only include the version number:
|
// For releases (non-dev builds) we only include the version number:
|
||||||
/// Stockfish version
|
// Stockfish version
|
||||||
|
std::string engine_info(bool to_uci) {
|
||||||
string engine_info(bool to_uci) {
|
std::stringstream ss;
|
||||||
stringstream ss;
|
ss << "Stockfish " << version << std::setfill('0');
|
||||||
ss << "Stockfish " << version << setfill('0');
|
|
||||||
|
|
||||||
if constexpr (version == "dev")
|
if constexpr (version == "dev")
|
||||||
{
|
{
|
||||||
@@ -162,12 +167,13 @@ string engine_info(bool to_uci) {
|
|||||||
#ifdef GIT_DATE
|
#ifdef GIT_DATE
|
||||||
ss << stringify(GIT_DATE);
|
ss << stringify(GIT_DATE);
|
||||||
#else
|
#else
|
||||||
constexpr string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
|
constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
|
||||||
string month, day, year;
|
std::string month, day, year;
|
||||||
stringstream date(__DATE__); // From compiler, format is "Sep 21 2008"
|
std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008"
|
||||||
|
|
||||||
date >> month >> day >> year;
|
date >> month >> day >> year;
|
||||||
ss << year << setw(2) << setfill('0') << (1 + months.find(month) / 4) << setw(2) << setfill('0') << day;
|
ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4)
|
||||||
|
<< std::setw(2) << std::setfill('0') << day;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ss << "-";
|
ss << "-";
|
||||||
@@ -179,37 +185,35 @@ string engine_info(bool to_uci) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
ss << (to_uci ? "\nid author ": " by ")
|
ss << (to_uci ? "\nid author " : " by ") << "the Stockfish developers (see AUTHORS file)";
|
||||||
<< "the Stockfish developers (see AUTHORS file)";
|
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// compiler_info() returns a string trying to describe the compiler we use
|
// Returns a string trying to describe the compiler we use
|
||||||
|
|
||||||
std::string compiler_info() {
|
std::string compiler_info() {
|
||||||
|
|
||||||
#define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch)
|
#define make_version_string(major, minor, patch) \
|
||||||
|
stringify(major) "." stringify(minor) "." stringify(patch)
|
||||||
|
|
||||||
/// Predefined macros hell:
|
// Predefined macros hell:
|
||||||
///
|
//
|
||||||
/// __GNUC__ Compiler is gcc, Clang or Intel on Linux
|
// __GNUC__ Compiler is GCC, Clang or ICX
|
||||||
/// __INTEL_COMPILER Compiler is Intel
|
// __clang__ Compiler is Clang or ICX
|
||||||
/// _MSC_VER Compiler is MSVC or Intel on Windows
|
// __INTEL_LLVM_COMPILER Compiler is ICX
|
||||||
/// _WIN32 Building on Windows (any)
|
// _MSC_VER Compiler is MSVC
|
||||||
/// _WIN64 Building on Windows 64 bit
|
// _WIN32 Building on Windows (any)
|
||||||
|
// _WIN64 Building on Windows 64 bit
|
||||||
|
|
||||||
std::string compiler = "\nCompiled by ";
|
std::string compiler = "\nCompiled by : ";
|
||||||
|
|
||||||
#ifdef __clang__
|
#if defined(__INTEL_LLVM_COMPILER)
|
||||||
|
compiler += "ICX ";
|
||||||
|
compiler += stringify(__INTEL_LLVM_COMPILER);
|
||||||
|
#elif defined(__clang__)
|
||||||
compiler += "clang++ ";
|
compiler += "clang++ ";
|
||||||
compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
|
compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
|
||||||
#elif __INTEL_COMPILER
|
|
||||||
compiler += "Intel compiler ";
|
|
||||||
compiler += "(version ";
|
|
||||||
compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE);
|
|
||||||
compiler += ")";
|
|
||||||
#elif _MSC_VER
|
#elif _MSC_VER
|
||||||
compiler += "MSVC ";
|
compiler += "MSVC ";
|
||||||
compiler += "(version ";
|
compiler += "(version ";
|
||||||
@@ -217,16 +221,14 @@ std::string compiler_info() {
|
|||||||
compiler += ")";
|
compiler += ")";
|
||||||
#elif defined(__e2k__) && defined(__LCC__)
|
#elif defined(__e2k__) && defined(__LCC__)
|
||||||
#define dot_ver2(n) \
|
#define dot_ver2(n) \
|
||||||
compiler += (char)'.'; \
|
compiler += char('.'); \
|
||||||
compiler += (char)('0' + (n) / 10); \
|
compiler += char('0' + (n) / 10); \
|
||||||
compiler += (char)('0' + (n) % 10);
|
compiler += char('0' + (n) % 10);
|
||||||
|
|
||||||
compiler += "MCST LCC ";
|
compiler += "MCST LCC ";
|
||||||
compiler += "(version ";
|
compiler += "(version ";
|
||||||
compiler += std::to_string(__LCC__ / 100);
|
compiler += std::to_string(__LCC__ / 100);
|
||||||
dot_ver2(__LCC__ % 100)
|
dot_ver2(__LCC__ % 100) dot_ver2(__LCC_MINOR__) compiler += ")";
|
||||||
dot_ver2(__LCC_MINOR__)
|
|
||||||
compiler += ")";
|
|
||||||
#elif __GNUC__
|
#elif __GNUC__
|
||||||
compiler += "g++ (GNUC) ";
|
compiler += "g++ (GNUC) ";
|
||||||
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
|
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
|
||||||
@@ -255,7 +257,14 @@ std::string compiler_info() {
|
|||||||
compiler += " on unknown system";
|
compiler += " on unknown system";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
compiler += "\nCompilation settings include: ";
|
compiler += "\nCompilation architecture : ";
|
||||||
|
#if defined(ARCH)
|
||||||
|
compiler += stringify(ARCH);
|
||||||
|
#else
|
||||||
|
compiler += "(undefined architecture)";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
compiler += "\nCompilation settings : ";
|
||||||
compiler += (Is64Bit ? "64bit" : "32bit");
|
compiler += (Is64Bit ? "64bit" : "32bit");
|
||||||
#if defined(USE_VNNI)
|
#if defined(USE_VNNI)
|
||||||
compiler += " VNNI";
|
compiler += " VNNI";
|
||||||
@@ -277,10 +286,9 @@ std::string compiler_info() {
|
|||||||
compiler += " SSE2";
|
compiler += " SSE2";
|
||||||
#endif
|
#endif
|
||||||
compiler += (HasPopCnt ? " POPCNT" : "");
|
compiler += (HasPopCnt ? " POPCNT" : "");
|
||||||
#if defined(USE_MMX)
|
#if defined(USE_NEON_DOTPROD)
|
||||||
compiler += " MMX";
|
compiler += " NEON_DOTPROD";
|
||||||
#endif
|
#elif defined(USE_NEON)
|
||||||
#if defined(USE_NEON)
|
|
||||||
compiler += " NEON";
|
compiler += " NEON";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -288,19 +296,20 @@ std::string compiler_info() {
|
|||||||
compiler += " DEBUG";
|
compiler += " DEBUG";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
compiler += "\n__VERSION__ macro expands to: ";
|
compiler += "\nCompiler __VERSION__ macro : ";
|
||||||
#ifdef __VERSION__
|
#ifdef __VERSION__
|
||||||
compiler += __VERSION__;
|
compiler += __VERSION__;
|
||||||
#else
|
#else
|
||||||
compiler += "(undefined macro)";
|
compiler += "(undefined macro)";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
compiler += "\n";
|
compiler += "\n";
|
||||||
|
|
||||||
return compiler;
|
return compiler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Debug functions used mainly to collect run-time statistics
|
// Debug functions used mainly to collect run-time statistics
|
||||||
constexpr int MaxDebugSlots = 32;
|
constexpr int MaxDebugSlots = 32;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -357,44 +366,35 @@ void dbg_print() {
|
|||||||
|
|
||||||
for (int i = 0; i < MaxDebugSlots; ++i)
|
for (int i = 0; i < MaxDebugSlots; ++i)
|
||||||
if ((n = hit[i][0]))
|
if ((n = hit[i][0]))
|
||||||
std::cerr << "Hit #" << i
|
std::cerr << "Hit #" << i << ": Total " << n << " Hits " << hit[i][1]
|
||||||
<< ": Total " << n << " Hits " << hit[i][1]
|
<< " Hit Rate (%) " << 100.0 * E(hit[i][1]) << std::endl;
|
||||||
<< " Hit Rate (%) " << 100.0 * E(hit[i][1])
|
|
||||||
<< std::endl;
|
|
||||||
|
|
||||||
for (int i = 0; i < MaxDebugSlots; ++i)
|
for (int i = 0; i < MaxDebugSlots; ++i)
|
||||||
if ((n = mean[i][0]))
|
if ((n = mean[i][0]))
|
||||||
{
|
{
|
||||||
std::cerr << "Mean #" << i
|
std::cerr << "Mean #" << i << ": Total " << n << " Mean " << E(mean[i][1]) << std::endl;
|
||||||
<< ": Total " << n << " Mean " << E(mean[i][1])
|
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < MaxDebugSlots; ++i)
|
for (int i = 0; i < MaxDebugSlots; ++i)
|
||||||
if ((n = stdev[i][0]))
|
if ((n = stdev[i][0]))
|
||||||
{
|
{
|
||||||
double r = sqrtl(E(stdev[i][2]) - sqr(E(stdev[i][1])));
|
double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1])));
|
||||||
std::cerr << "Stdev #" << i
|
std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl;
|
||||||
<< ": Total " << n << " Stdev " << r
|
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < MaxDebugSlots; ++i)
|
for (int i = 0; i < MaxDebugSlots; ++i)
|
||||||
if ((n = correl[i][0]))
|
if ((n = correl[i][0]))
|
||||||
{
|
{
|
||||||
double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3]))
|
double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3]))
|
||||||
/ ( sqrtl(E(correl[i][2]) - sqr(E(correl[i][1])))
|
/ (sqrt(E(correl[i][2]) - sqr(E(correl[i][1])))
|
||||||
* sqrtl(E(correl[i][4]) - sqr(E(correl[i][3]))));
|
* sqrt(E(correl[i][4]) - sqr(E(correl[i][3]))));
|
||||||
std::cerr << "Correl. #" << i
|
std::cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r << std::endl;
|
||||||
<< ": Total " << n << " Coefficient " << r
|
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Used to serialize access to std::cout to avoid multiple threads writing at
|
// Used to serialize access to std::cout
|
||||||
/// the same time.
|
// to avoid multiple threads writing at the same time.
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, SyncCout sc) {
|
std::ostream& operator<<(std::ostream& os, SyncCout sc) {
|
||||||
|
|
||||||
static std::mutex m;
|
static std::mutex m;
|
||||||
@@ -409,13 +409,10 @@ std::ostream& operator<<(std::ostream& os, SyncCout sc) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Trampoline helper to avoid moving Logger to misc.h
|
// Trampoline helper to avoid moving Logger to misc.h
|
||||||
void start_logger(const std::string& fname) { Logger::start(fname); }
|
void start_logger(const std::string& fname) { Logger::start(fname); }
|
||||||
|
|
||||||
|
|
||||||
/// prefetch() 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.
|
|
||||||
#ifdef NO_PREFETCH
|
#ifdef NO_PREFETCH
|
||||||
|
|
||||||
void prefetch(void*) {}
|
void prefetch(void*) {}
|
||||||
@@ -424,13 +421,7 @@ void prefetch(void*) {}
|
|||||||
|
|
||||||
void prefetch(void* addr) {
|
void prefetch(void* addr) {
|
||||||
|
|
||||||
# if defined(__INTEL_COMPILER)
|
#if defined(_MSC_VER)
|
||||||
// This hack prevents prefetches from being optimized away by
|
|
||||||
// Intel compiler. Both MSVC and gcc seem not be affected by this.
|
|
||||||
__asm__ ("");
|
|
||||||
# endif
|
|
||||||
|
|
||||||
# if defined(__INTEL_COMPILER) || defined(_MSC_VER)
|
|
||||||
_mm_prefetch((char*) addr, _MM_HINT_T0);
|
_mm_prefetch((char*) addr, _MM_HINT_T0);
|
||||||
#else
|
#else
|
||||||
__builtin_prefetch(addr);
|
__builtin_prefetch(addr);
|
||||||
@@ -440,10 +431,9 @@ void prefetch(void* addr) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation
|
// Wrapper for systems where the c++17 implementation
|
||||||
/// does not guarantee the availability of aligned_alloc(). Memory allocated with
|
// does not guarantee the availability of aligned_alloc(). Memory allocated with
|
||||||
/// std_aligned_alloc() must be freed with std_aligned_free().
|
// std_aligned_alloc() must be freed with std_aligned_free().
|
||||||
|
|
||||||
void* std_aligned_alloc(size_t alignment, size_t size) {
|
void* std_aligned_alloc(size_t alignment, size_t size) {
|
||||||
|
|
||||||
#if defined(POSIXALIGNEDALLOC)
|
#if defined(POSIXALIGNEDALLOC)
|
||||||
@@ -471,7 +461,7 @@ void std_aligned_free(void* ptr) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages.
|
// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages.
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
|
||||||
@@ -496,13 +486,13 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize
|
|||||||
if (!hAdvapi32)
|
if (!hAdvapi32)
|
||||||
hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
|
hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
|
||||||
|
|
||||||
auto fun6 = (fun6_t)(void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken");
|
auto fun6 = fun6_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken"));
|
||||||
if (!fun6)
|
if (!fun6)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
auto fun7 = (fun7_t)(void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA");
|
auto fun7 = fun7_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"));
|
||||||
if (!fun7)
|
if (!fun7)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
auto fun8 = (fun8_t)(void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges");
|
auto fun8 = fun8_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"));
|
||||||
if (!fun8)
|
if (!fun8)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@@ -525,13 +515,13 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize
|
|||||||
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
|
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
|
||||||
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
|
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
|
||||||
if (fun8( // AdjustTokenPrivileges()
|
if (fun8( // AdjustTokenPrivileges()
|
||||||
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
|
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen)
|
||||||
GetLastError() == ERROR_SUCCESS)
|
&& GetLastError() == ERROR_SUCCESS)
|
||||||
{
|
{
|
||||||
// Round up size to full pages and allocate
|
// Round up size to full pages and allocate
|
||||||
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
|
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
|
||||||
mem = VirtualAlloc(
|
mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES,
|
||||||
nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
|
PAGE_READWRITE);
|
||||||
|
|
||||||
// Privilege no longer needed, restore previous state
|
// Privilege no longer needed, restore previous state
|
||||||
fun8( // AdjustTokenPrivileges ()
|
fun8( // AdjustTokenPrivileges ()
|
||||||
@@ -551,7 +541,7 @@ void* aligned_large_pages_alloc(size_t allocSize) {
|
|||||||
// Try to allocate large pages
|
// Try to allocate large pages
|
||||||
void* mem = aligned_large_pages_alloc_windows(allocSize);
|
void* mem = aligned_large_pages_alloc_windows(allocSize);
|
||||||
|
|
||||||
// Fall back to regular, page aligned, allocation if necessary
|
// Fall back to regular, page-aligned, allocation if necessary
|
||||||
if (!mem)
|
if (!mem)
|
||||||
mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
|
||||||
@@ -568,7 +558,7 @@ void* aligned_large_pages_alloc(size_t allocSize) {
|
|||||||
constexpr size_t alignment = 4096; // assumed small page size
|
constexpr size_t alignment = 4096; // assumed small page size
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// round up to multiples of alignment
|
// Round up to multiples of alignment
|
||||||
size_t size = ((allocSize + alignment - 1) / alignment) * alignment;
|
size_t size = ((allocSize + alignment - 1) / alignment) * alignment;
|
||||||
void* mem = std_aligned_alloc(alignment, size);
|
void* mem = std_aligned_alloc(alignment, size);
|
||||||
#if defined(MADV_HUGEPAGE)
|
#if defined(MADV_HUGEPAGE)
|
||||||
@@ -580,7 +570,7 @@ void* aligned_large_pages_alloc(size_t allocSize) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/// aligned_large_pages_free() will free the previously allocated ttmem
|
// aligned_large_pages_free() will free the previously allocated ttmem
|
||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
|
|
||||||
@@ -589,8 +579,7 @@ void aligned_large_pages_free(void* mem) {
|
|||||||
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
|
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
|
||||||
{
|
{
|
||||||
DWORD err = GetLastError();
|
DWORD err = GetLastError();
|
||||||
std::cerr << "Failed to free large page memory. Error code: 0x"
|
std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err
|
||||||
<< std::hex << err
|
|
||||||
<< std::dec << std::endl;
|
<< std::dec << std::endl;
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
@@ -598,9 +587,7 @@ void aligned_large_pages_free(void* mem) {
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
void aligned_large_pages_free(void *mem) {
|
void aligned_large_pages_free(void* mem) { std_aligned_free(mem); }
|
||||||
std_aligned_free(mem);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -609,15 +596,15 @@ namespace WinProcGroup {
|
|||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
|
||||||
void bindThisThread(size_t) {}
|
void bind_this_thread(size_t) {}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
/// best_node() retrieves logical processor information using Windows specific
|
namespace {
|
||||||
/// API and returns the best node id for the thread with index idx. Original
|
// Retrieves logical processor information using Windows-specific
|
||||||
/// code from Texel by Peter Österlund.
|
// API and returns the best node id for the thread with index idx. Original
|
||||||
|
// code from Texel by Peter Österlund.
|
||||||
static int best_node(size_t idx) {
|
int best_node(size_t idx) {
|
||||||
|
|
||||||
int threads = 0;
|
int threads = 0;
|
||||||
int nodes = 0;
|
int nodes = 0;
|
||||||
@@ -667,15 +654,14 @@ static int best_node(size_t idx) {
|
|||||||
|
|
||||||
std::vector<int> groups;
|
std::vector<int> groups;
|
||||||
|
|
||||||
// Run as many threads as possible on the same node until core limit is
|
// Run as many threads as possible on the same node until the core limit is
|
||||||
// reached, then move on filling the next node.
|
// reached, then move on to filling the next node.
|
||||||
for (int n = 0; n < nodes; n++)
|
for (int n = 0; n < nodes; n++)
|
||||||
for (int i = 0; i < cores / nodes; i++)
|
for (int i = 0; i < cores / nodes; i++)
|
||||||
groups.push_back(n);
|
groups.push_back(n);
|
||||||
|
|
||||||
// In case a core has more than one logical processor (we assume 2) and we
|
// In case a core has more than one logical processor (we assume 2) and we
|
||||||
// have still threads to allocate, then spread them evenly across available
|
// still have threads to allocate, spread them evenly across available nodes.
|
||||||
// nodes.
|
|
||||||
for (int t = 0; t < threads - cores; t++)
|
for (int t = 0; t < threads - cores; t++)
|
||||||
groups.push_back(t % nodes);
|
groups.push_back(t % nodes);
|
||||||
|
|
||||||
@@ -683,11 +669,11 @@ static int best_node(size_t idx) {
|
|||||||
// then return -1 and let the OS to decide what to do.
|
// then return -1 and let the OS to decide what to do.
|
||||||
return idx < groups.size() ? groups[idx] : -1;
|
return idx < groups.size() ? groups[idx] : -1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// bindThisThread() set the group affinity of the current thread
|
// Sets the group affinity of the current thread
|
||||||
|
void bind_this_thread(size_t idx) {
|
||||||
void bindThisThread(size_t idx) {
|
|
||||||
|
|
||||||
// Use only local variables to be thread-safe
|
// Use only local variables to be thread-safe
|
||||||
int node = best_node(idx);
|
int node = best_node(idx);
|
||||||
@@ -697,10 +683,10 @@ void bindThisThread(size_t idx) {
|
|||||||
|
|
||||||
// Early exit if the needed API are not available at runtime
|
// Early exit if the needed API are not available at runtime
|
||||||
HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
|
HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
|
||||||
auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx");
|
auto fun2 = fun2_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"));
|
||||||
auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity");
|
auto fun3 = fun3_t((void (*)()) GetProcAddress(k32, "SetThreadGroupAffinity"));
|
||||||
auto fun4 = (fun4_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2");
|
auto fun4 = fun4_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMask2"));
|
||||||
auto fun5 = (fun5_t)(void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount");
|
auto fun5 = fun5_t((void (*)()) GetProcAddress(k32, "GetMaximumProcessorGroupCount"));
|
||||||
|
|
||||||
if (!fun2 || !fun3)
|
if (!fun2 || !fun3)
|
||||||
return;
|
return;
|
||||||
@@ -719,7 +705,8 @@ void bindThisThread(size_t idx) {
|
|||||||
elements = fun5(); // GetMaximumProcessorGroupCount
|
elements = fun5(); // GetMaximumProcessorGroupCount
|
||||||
GROUP_AFFINITY* affinity = (GROUP_AFFINITY*) malloc(elements * sizeof(GROUP_AFFINITY));
|
GROUP_AFFINITY* affinity = (GROUP_AFFINITY*) malloc(elements * sizeof(GROUP_AFFINITY));
|
||||||
if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2
|
if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2
|
||||||
fun3(GetCurrentThread(), &affinity[idx % returnedElements], nullptr); // SetThreadGroupAffinity
|
fun3(GetCurrentThread(), &affinity[idx % returnedElements],
|
||||||
|
nullptr); // SetThreadGroupAffinity
|
||||||
free(affinity);
|
free(affinity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -736,23 +723,19 @@ void bindThisThread(size_t idx) {
|
|||||||
#define GETCWD getcwd
|
#define GETCWD getcwd
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace CommandLine {
|
CommandLine::CommandLine(int _argc, char** _argv) :
|
||||||
|
argc(_argc),
|
||||||
|
argv(_argv) {
|
||||||
|
std::string pathSeparator;
|
||||||
|
|
||||||
string argv0; // path+name of the executable binary, as given by argv[0]
|
// Extract the path+name of the executable binary
|
||||||
string binaryDirectory; // path of the executable directory
|
std::string argv0 = argv[0];
|
||||||
string workingDirectory; // path of the working directory
|
|
||||||
|
|
||||||
void init([[maybe_unused]] int argc, char* argv[]) {
|
|
||||||
string pathSeparator;
|
|
||||||
|
|
||||||
// extract the path+name of the executable binary
|
|
||||||
argv0 = argv[0];
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
pathSeparator = "\\";
|
pathSeparator = "\\";
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
// Under windows argv[0] may not have the extension. Also _get_pgmptr() had
|
// Under windows argv[0] may not have the extension. Also _get_pgmptr() had
|
||||||
// issues in some windows 10 versions, so check returned values carefully.
|
// issues in some Windows 10 versions, so check returned values carefully.
|
||||||
char* pgmptr = nullptr;
|
char* pgmptr = nullptr;
|
||||||
if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)
|
if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)
|
||||||
argv0 = pgmptr;
|
argv0 = pgmptr;
|
||||||
@@ -761,14 +744,14 @@ void init([[maybe_unused]] int argc, char* argv[]) {
|
|||||||
pathSeparator = "/";
|
pathSeparator = "/";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// extract the working directory
|
// Extract the working directory
|
||||||
workingDirectory = "";
|
workingDirectory = "";
|
||||||
char buff[40000];
|
char buff[40000];
|
||||||
char* cwd = GETCWD(buff, 40000);
|
char* cwd = GETCWD(buff, 40000);
|
||||||
if (cwd)
|
if (cwd)
|
||||||
workingDirectory = cwd;
|
workingDirectory = cwd;
|
||||||
|
|
||||||
// extract the binary directory path from argv0
|
// Extract the binary directory path from argv0
|
||||||
binaryDirectory = argv0;
|
binaryDirectory = argv0;
|
||||||
size_t pos = binaryDirectory.find_last_of("\\/");
|
size_t pos = binaryDirectory.find_last_of("\\/");
|
||||||
if (pos == std::string::npos)
|
if (pos == std::string::npos)
|
||||||
@@ -776,12 +759,9 @@ void init([[maybe_unused]] int argc, char* argv[]) {
|
|||||||
else
|
else
|
||||||
binaryDirectory.resize(pos + 1);
|
binaryDirectory.resize(pos + 1);
|
||||||
|
|
||||||
// pattern replacement: "./" at the start of path is replaced by the working directory
|
// Pattern replacement: "./" at the start of path is replaced by the working directory
|
||||||
if (binaryDirectory.find("." + pathSeparator) == 0)
|
if (binaryDirectory.find("." + pathSeparator) == 0)
|
||||||
binaryDirectory.replace(0, 1, workingDirectory);
|
binaryDirectory.replace(0, 1, workingDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace CommandLine
|
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|||||||
+115
-58
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,14 +19,15 @@
|
|||||||
#ifndef MISC_H_INCLUDED
|
#ifndef MISC_H_INCLUDED
|
||||||
#define MISC_H_INCLUDED
|
#define MISC_H_INCLUDED
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <ostream>
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iosfwd>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
#define stringify2(x) #x
|
#define stringify2(x) #x
|
||||||
#define stringify(x) stringify2(x)
|
#define stringify(x) stringify2(x)
|
||||||
@@ -35,12 +36,43 @@ namespace Stockfish {
|
|||||||
|
|
||||||
std::string engine_info(bool to_uci = false);
|
std::string engine_info(bool to_uci = false);
|
||||||
std::string compiler_info();
|
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 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_alloc(size_t alignment, size_t size);
|
||||||
void std_aligned_free(void* ptr);
|
void std_aligned_free(void* ptr);
|
||||||
void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes
|
// memory aligned by page size, min alignment: 4096 bytes
|
||||||
void aligned_large_pages_free(void* mem); // nop if mem == nullptr
|
void* aligned_large_pages_alloc(size_t size);
|
||||||
|
// nop if mem == nullptr
|
||||||
|
void aligned_large_pages_free(void* mem);
|
||||||
|
|
||||||
|
// 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>>;
|
||||||
|
|
||||||
|
|
||||||
void dbg_hit_on(bool cond, int slot = 0);
|
void dbg_hit_on(bool cond, int slot = 0);
|
||||||
void dbg_mean_of(int64_t value, int slot = 0);
|
void dbg_mean_of(int64_t value, int slot = 0);
|
||||||
@@ -51,41 +83,40 @@ void dbg_print();
|
|||||||
using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds
|
using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds
|
||||||
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
|
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
|
||||||
inline TimePoint now() {
|
inline TimePoint now() {
|
||||||
return std::chrono::duration_cast<std::chrono::milliseconds>
|
return std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
(std::chrono::steady_clock::now().time_since_epoch()).count();
|
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:
|
enum SyncCout {
|
||||||
std::vector<Entry> table = std::vector<Entry>(Size); // Allocate on the heap
|
IO_LOCK,
|
||||||
|
IO_UNLOCK
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
enum SyncCout { IO_LOCK, IO_UNLOCK };
|
|
||||||
std::ostream& operator<<(std::ostream&, SyncCout);
|
std::ostream& operator<<(std::ostream&, SyncCout);
|
||||||
|
|
||||||
#define sync_cout std::cout << IO_LOCK
|
#define sync_cout std::cout << IO_LOCK
|
||||||
#define sync_endl std::endl << IO_UNLOCK
|
#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,
|
// 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.
|
// where N is the number of elements in the array.
|
||||||
template<uintptr_t Alignment, typename T>
|
template<uintptr_t Alignment, typename T>
|
||||||
T* align_ptr_up(T* ptr)
|
T* align_ptr_up(T* ptr) {
|
||||||
{
|
|
||||||
static_assert(alignof(T) < Alignment);
|
static_assert(alignof(T) < Alignment);
|
||||||
|
|
||||||
const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));
|
const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));
|
||||||
return reinterpret_cast<T*>(reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
|
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
|
// 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 union {
|
||||||
|
uint32_t i;
|
||||||
|
char c[4];
|
||||||
|
} Le = {0x01020304};
|
||||||
static inline const bool IsLittleEndian = (Le.c[0] == 4);
|
static inline const bool IsLittleEndian = (Le.c[0] == 4);
|
||||||
|
|
||||||
|
|
||||||
@@ -97,6 +128,7 @@ public:
|
|||||||
void push_back(const T& value) { values_[size_++] = value; }
|
void push_back(const T& value) { values_[size_++] = value; }
|
||||||
const T* begin() const { return values_; }
|
const T* begin() const { return values_; }
|
||||||
const T* end() const { return values_ + size_; }
|
const T* end() const { return values_ + size_; }
|
||||||
|
const T& operator[](int index) const { return values_[index]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T values_[MaxSize];
|
T values_[MaxSize];
|
||||||
@@ -104,20 +136,20 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// xorshift64star Pseudo-Random Number Generator
|
// xorshift64star Pseudo-Random Number Generator
|
||||||
/// This class is based on original code written and dedicated
|
// This class is based on original code written and dedicated
|
||||||
/// to the public domain by Sebastiano Vigna (2014).
|
// to the public domain by Sebastiano Vigna (2014).
|
||||||
/// It has the following characteristics:
|
// It has the following characteristics:
|
||||||
///
|
//
|
||||||
/// - Outputs 64-bit numbers
|
// - Outputs 64-bit numbers
|
||||||
/// - Passes Dieharder and SmallCrush test batteries
|
// - Passes Dieharder and SmallCrush test batteries
|
||||||
/// - Does not require warm-up, no zeroland to escape
|
// - Does not require warm-up, no zeroland to escape
|
||||||
/// - Internal state is a single 64-bit integer
|
// - Internal state is a single 64-bit integer
|
||||||
/// - Period is 2^64 - 1
|
// - Period is 2^64 - 1
|
||||||
/// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
|
// - Speed: 1.60 ns/call (Core i7 @3.40GHz)
|
||||||
///
|
//
|
||||||
/// For further analysis see
|
// For further analysis see
|
||||||
/// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
|
// <http://vigna.di.unimi.it/ftp/papers/xorshift.pdf>
|
||||||
|
|
||||||
class PRNG {
|
class PRNG {
|
||||||
|
|
||||||
@@ -130,45 +162,70 @@ class PRNG {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PRNG(uint64_t seed) : s(seed) { assert(seed); }
|
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.
|
// Special generator used to fast init magic numbers.
|
||||||
/// Output values only have 1/8th of their bits set on average.
|
// Output values only have 1/8th of their bits set on average.
|
||||||
template<typename T> T sparse_rand()
|
template<typename T>
|
||||||
{ return T(rand64() & rand64() & rand64()); }
|
T sparse_rand() {
|
||||||
|
return T(rand64() & rand64() & rand64());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
|
||||||
#if defined(__GNUC__) && defined(IS_64BIT)
|
#if defined(__GNUC__) && defined(IS_64BIT)
|
||||||
__extension__ using uint128 = unsigned __int128;
|
__extension__ using uint128 = unsigned __int128;
|
||||||
return ((uint128)a * (uint128)b) >> 64;
|
return (uint128(a) * uint128(b)) >> 64;
|
||||||
#else
|
#else
|
||||||
uint64_t aL = (uint32_t)a, aH = a >> 32;
|
uint64_t aL = uint32_t(a), aH = a >> 32;
|
||||||
uint64_t bL = (uint32_t)b, bH = b >> 32;
|
uint64_t bL = uint32_t(b), bH = b >> 32;
|
||||||
uint64_t c1 = (aL * bL) >> 32;
|
uint64_t c1 = (aL * bL) >> 32;
|
||||||
uint64_t c2 = aH * bL + c1;
|
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);
|
return aH * bH + (c2 >> 32) + (c3 >> 32);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Under Windows it is not possible for a process to run on more than one
|
// 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
|
// logical processor group. This usually means being limited to using max 64
|
||||||
/// cores. To overcome this, some special platform specific API should be
|
// cores. To overcome this, some special platform-specific API should be
|
||||||
/// called to set group affinity for each thread. Original code from Texel by
|
// called to set group affinity for each thread. Original code from Texel by
|
||||||
/// Peter Österlund.
|
// Peter Österlund.
|
||||||
|
|
||||||
namespace WinProcGroup {
|
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
|
struct CommandLine {
|
||||||
extern std::string workingDirectory; // path of the working directory
|
public:
|
||||||
|
CommandLine(int, char**);
|
||||||
|
|
||||||
|
int argc;
|
||||||
|
char** argv;
|
||||||
|
|
||||||
|
std::string binaryDirectory; // path of the executable directory
|
||||||
|
std::string workingDirectory; // path of the working directory
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
+37
-41
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,9 +16,12 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <initializer_list>
|
||||||
|
|
||||||
|
#include "bitboard.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
@@ -28,22 +31,16 @@ namespace {
|
|||||||
template<GenType Type, Direction D, bool Enemy>
|
template<GenType Type, Direction D, bool Enemy>
|
||||||
ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
|
ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
|
||||||
|
|
||||||
if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
|
constexpr bool all = Type == EVASIONS || Type == NON_EVASIONS;
|
||||||
{
|
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, QUEEN);
|
|
||||||
if constexpr (Enemy && Type == CAPTURES)
|
|
||||||
{
|
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, ROOK);
|
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
|
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr ((Type == QUIETS && !Enemy) || 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++ = Move::make<PROMOTION>(to - D, to, ROOK);
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, BISHOP);
|
*moveList++ = Move::make<PROMOTION>(to - D, to, BISHOP);
|
||||||
*moveList++ = make<PROMOTION>(to - D, to, KNIGHT);
|
*moveList++ = Move::make<PROMOTION>(to - D, to, KNIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return moveList;
|
return moveList;
|
||||||
@@ -61,8 +58,7 @@ namespace {
|
|||||||
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
|
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
|
||||||
|
|
||||||
const Bitboard emptySquares = ~pos.pieces();
|
const Bitboard emptySquares = ~pos.pieces();
|
||||||
const Bitboard enemies = Type == EVASIONS ? pos.checkers()
|
const Bitboard enemies = Type == EVASIONS ? pos.checkers() : pos.pieces(Them);
|
||||||
: pos.pieces(Them);
|
|
||||||
|
|
||||||
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
|
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
|
||||||
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
|
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
|
||||||
@@ -93,13 +89,13 @@ namespace {
|
|||||||
while (b1)
|
while (b1)
|
||||||
{
|
{
|
||||||
Square to = pop_lsb(b1);
|
Square to = pop_lsb(b1);
|
||||||
*moveList++ = make_move(to - Up, to);
|
*moveList++ = Move(to - Up, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (b2)
|
while (b2)
|
||||||
{
|
{
|
||||||
Square to = pop_lsb(b2);
|
Square to = pop_lsb(b2);
|
||||||
*moveList++ = make_move(to - Up - Up, to);
|
*moveList++ = Move(to - Up - Up, to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,13 +128,13 @@ namespace {
|
|||||||
while (b1)
|
while (b1)
|
||||||
{
|
{
|
||||||
Square to = pop_lsb(b1);
|
Square to = pop_lsb(b1);
|
||||||
*moveList++ = make_move(to - UpRight, to);
|
*moveList++ = Move(to - UpRight, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (b2)
|
while (b2)
|
||||||
{
|
{
|
||||||
Square to = pop_lsb(b2);
|
Square to = pop_lsb(b2);
|
||||||
*moveList++ = make_move(to - UpLeft, to);
|
*moveList++ = Move(to - UpLeft, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pos.ep_square() != SQ_NONE)
|
if (pos.ep_square() != SQ_NONE)
|
||||||
@@ -154,7 +150,7 @@ namespace {
|
|||||||
assert(b1);
|
assert(b1);
|
||||||
|
|
||||||
while (b1)
|
while (b1)
|
||||||
*moveList++ = make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
|
*moveList++ = Move::make<EN_PASSANT>(pop_lsb(b1), pos.ep_square());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +175,7 @@ namespace {
|
|||||||
b &= pos.check_squares(Pt);
|
b &= pos.check_squares(Pt);
|
||||||
|
|
||||||
while (b)
|
while (b)
|
||||||
*moveList++ = make_move(from, pop_lsb(b));
|
*moveList++ = Move(from, pop_lsb(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
return moveList;
|
return moveList;
|
||||||
@@ -217,12 +213,12 @@ namespace {
|
|||||||
b &= ~attacks_bb<QUEEN>(pos.square<KING>(~Us));
|
b &= ~attacks_bb<QUEEN>(pos.square<KING>(~Us));
|
||||||
|
|
||||||
while (b)
|
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))
|
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))
|
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;
|
return moveList;
|
||||||
@@ -231,19 +227,19 @@ namespace {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
/// <CAPTURES> Generates all pseudo-legal captures plus queen promotions
|
// <CAPTURES> Generates all pseudo-legal captures plus queen promotions
|
||||||
/// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
|
// <QUIETS> Generates all pseudo-legal non-captures and underpromotions
|
||||||
/// <EVASIONS> Generates all pseudo-legal check evasions when the side to move is in check
|
// <EVASIONS> Generates all pseudo-legal check evasions
|
||||||
/// <QUIET_CHECKS> Generates all pseudo-legal non-captures giving check, except castling and promotions
|
// <NON_EVASIONS> Generates all pseudo-legal captures and non-captures
|
||||||
/// <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.
|
//
|
||||||
|
// Returns a pointer to the end of the move list.
|
||||||
template<GenType Type>
|
template<GenType Type>
|
||||||
ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
ExtMove* generate(const Position& pos, ExtMove* moveList) {
|
||||||
|
|
||||||
static_assert(Type != LEGAL, "Unsupported type in generate()");
|
static_assert(Type != LEGAL, "Unsupported type in generate()");
|
||||||
assert((Type == EVASIONS) == (bool)pos.checkers());
|
assert((Type == EVASIONS) == bool(pos.checkers()));
|
||||||
|
|
||||||
Color us = pos.side_to_move();
|
Color us = pos.side_to_move();
|
||||||
|
|
||||||
@@ -259,7 +255,7 @@ template ExtMove* generate<QUIET_CHECKS>(const Position&, ExtMove*);
|
|||||||
template ExtMove* generate<NON_EVASIONS>(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<>
|
template<>
|
||||||
ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
|
ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
|
||||||
@@ -269,12 +265,12 @@ ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
|
|||||||
Square ksq = pos.square<KING>(us);
|
Square ksq = pos.square<KING>(us);
|
||||||
ExtMove* cur = moveList;
|
ExtMove* cur = moveList;
|
||||||
|
|
||||||
moveList = pos.checkers() ? generate<EVASIONS >(pos, moveList)
|
moveList =
|
||||||
: generate<NON_EVASIONS>(pos, moveList);
|
pos.checkers() ? generate<EVASIONS>(pos, moveList) : generate<NON_EVASIONS>(pos, moveList);
|
||||||
while (cur != moveList)
|
while (cur != moveList)
|
||||||
if ( ((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT)
|
if (((pinned & cur->from_sq()) || cur->from_sq() == ksq || cur->type_of() == EN_PASSANT)
|
||||||
&& !pos.legal(*cur))
|
&& !pos.legal(*cur))
|
||||||
*cur = (--moveList)->move;
|
*cur = *(--moveList);
|
||||||
else
|
else
|
||||||
++cur;
|
++cur;
|
||||||
|
|
||||||
|
|||||||
+12
-15
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
#ifndef MOVEGEN_H_INCLUDED
|
#ifndef MOVEGEN_H_INCLUDED
|
||||||
#define MOVEGEN_H_INCLUDED
|
#define MOVEGEN_H_INCLUDED
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm> // IWYU pragma: keep
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
@@ -36,37 +37,33 @@ enum GenType {
|
|||||||
LEGAL
|
LEGAL
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ExtMove {
|
struct ExtMove: public Move {
|
||||||
Move move;
|
|
||||||
int value;
|
int value;
|
||||||
|
|
||||||
operator Move() const { return move; }
|
void operator=(Move m) { data = m.raw(); }
|
||||||
void operator=(Move m) { move = m; }
|
|
||||||
|
|
||||||
// Inhibit unwanted implicit conversions to Move
|
// Inhibit unwanted implicit conversions to Move
|
||||||
// with an ambiguity that yields to a compile error.
|
// with an ambiguity that yields to a compile error.
|
||||||
operator float() const = delete;
|
operator float() const = delete;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool operator<(const ExtMove& f, const ExtMove& s) {
|
inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; }
|
||||||
return f.value < s.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<GenType>
|
template<GenType>
|
||||||
ExtMove* generate(const Position& pos, ExtMove* moveList);
|
ExtMove* generate(const Position& pos, ExtMove* moveList);
|
||||||
|
|
||||||
/// The MoveList struct is a simple wrapper around generate(). It sometimes comes
|
// The MoveList struct wraps the generate() function and returns a convenient
|
||||||
/// in handy to use this class instead of the low level generate() function.
|
// list of moves. Using MoveList is sometimes preferable to directly calling
|
||||||
|
// the lower level generate() function.
|
||||||
template<GenType T>
|
template<GenType T>
|
||||||
struct MoveList {
|
struct MoveList {
|
||||||
|
|
||||||
explicit MoveList(const Position& pos) : last(generate<T>(pos, moveList)) {}
|
explicit MoveList(const Position& pos) :
|
||||||
|
last(generate<T>(pos, moveList)) {}
|
||||||
const ExtMove* begin() const { return moveList; }
|
const ExtMove* begin() const { return moveList; }
|
||||||
const ExtMove* end() const { return last; }
|
const ExtMove* end() const { return last; }
|
||||||
size_t size() const { return last - moveList; }
|
size_t size() const { return last - moveList; }
|
||||||
bool contains(Move move) const {
|
bool contains(Move move) const { return std::find(begin(), end(), move) != end(); }
|
||||||
return std::find(begin(), end(), move) != end();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ExtMove moveList[MAX_MOVES], *last;
|
ExtMove moveList[MAX_MOVES], *last;
|
||||||
|
|||||||
+175
-84
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,23 +16,50 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "movepick.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <iterator>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "movepick.h"
|
#include "position.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
enum Stages {
|
enum Stages {
|
||||||
MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE,
|
// generate main search moves
|
||||||
EVASION_TT, EVASION_INIT, EVASION,
|
MAIN_TT,
|
||||||
PROBCUT_TT, PROBCUT_INIT, PROBCUT,
|
CAPTURE_INIT,
|
||||||
QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK
|
GOOD_CAPTURE,
|
||||||
|
REFUTATION,
|
||||||
|
QUIET_INIT,
|
||||||
|
GOOD_QUIET,
|
||||||
|
BAD_CAPTURE,
|
||||||
|
BAD_QUIET,
|
||||||
|
|
||||||
|
// 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
|
||||||
};
|
};
|
||||||
|
|
||||||
// partial_insertion_sort() sorts moves in descending order up to and including
|
// Sort moves in descending order up to and including
|
||||||
// a given limit. The order of moves smaller than the limit is left unspecified.
|
// a given limit. The order of moves smaller than the limit is left unspecified.
|
||||||
void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
|
void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
|
||||||
|
|
||||||
@@ -50,68 +77,85 @@ namespace {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
/// Constructors of the MovePicker class. As arguments we pass information
|
// Constructors of the MovePicker class. As arguments, we pass information
|
||||||
/// to help it to return the (presumably) good moves first, to decide which
|
// 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
|
// moves to return (in the quiescence search, for instance, we only want to
|
||||||
/// search captures, promotions, and some checks) and how important good move
|
// search captures, promotions, and some checks) and how important a good
|
||||||
/// ordering is at the current node.
|
// move ordering is at the current node.
|
||||||
|
|
||||||
/// MovePicker constructor for the main search
|
// MovePicker constructor for the main search
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
MovePicker::MovePicker(const Position& p,
|
||||||
|
Move ttm,
|
||||||
|
Depth d,
|
||||||
|
const ButterflyHistory* mh,
|
||||||
const CapturePieceToHistory* cph,
|
const CapturePieceToHistory* cph,
|
||||||
const PieceToHistory** ch,
|
const PieceToHistory** ch,
|
||||||
|
const PawnHistory* ph,
|
||||||
Move cm,
|
Move cm,
|
||||||
const Move* killers)
|
const Move* killers) :
|
||||||
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch),
|
pos(p),
|
||||||
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d)
|
mainHistory(mh),
|
||||||
{
|
captureHistory(cph),
|
||||||
|
continuationHistory(ch),
|
||||||
|
pawnHistory(ph),
|
||||||
|
ttMove(ttm),
|
||||||
|
refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}},
|
||||||
|
depth(d) {
|
||||||
assert(d > 0);
|
assert(d > 0);
|
||||||
|
|
||||||
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) +
|
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm));
|
||||||
!(ttm && pos.pseudo_legal(ttm));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MovePicker constructor for quiescence search
|
// Constructor for quiescence search
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
|
MovePicker::MovePicker(const Position& p,
|
||||||
|
Move ttm,
|
||||||
|
Depth d,
|
||||||
|
const ButterflyHistory* mh,
|
||||||
const CapturePieceToHistory* cph,
|
const CapturePieceToHistory* cph,
|
||||||
const PieceToHistory** ch,
|
const PieceToHistory** ch,
|
||||||
Square rs)
|
const PawnHistory* ph) :
|
||||||
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d)
|
pos(p),
|
||||||
{
|
mainHistory(mh),
|
||||||
|
captureHistory(cph),
|
||||||
|
continuationHistory(ch),
|
||||||
|
pawnHistory(ph),
|
||||||
|
ttMove(ttm),
|
||||||
|
depth(d) {
|
||||||
assert(d <= 0);
|
assert(d <= 0);
|
||||||
|
|
||||||
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) +
|
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm));
|
||||||
!( ttm
|
|
||||||
&& pos.pseudo_legal(ttm));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MovePicker constructor for ProbCut: we generate captures with SEE greater
|
// Constructor for ProbCut: we generate captures with SEE greater
|
||||||
/// than or equal to the given threshold.
|
// than or equal to the given threshold.
|
||||||
MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
|
MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceToHistory* cph) :
|
||||||
: pos(p), captureHistory(cph), ttMove(ttm), threshold(th)
|
pos(p),
|
||||||
{
|
captureHistory(cph),
|
||||||
|
ttMove(ttm),
|
||||||
|
threshold(th) {
|
||||||
assert(!pos.checkers());
|
assert(!pos.checkers());
|
||||||
|
|
||||||
stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm)
|
stage = PROBCUT_TT
|
||||||
&& pos.pseudo_legal(ttm)
|
+ !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold));
|
||||||
&& pos.see_ge(ttm, threshold));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MovePicker::score() assigns a numerical value to each move in a list, used
|
// Assigns a numerical value to each move in a list, used
|
||||||
/// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring
|
// for sorting. Captures are ordered by Most Valuable Victim (MVV), preferring
|
||||||
/// captures with a good history. Quiets moves are ordered using the history tables.
|
// captures with a good history. Quiets moves are ordered using the history tables.
|
||||||
template<GenType Type>
|
template<GenType Type>
|
||||||
void MovePicker::score() {
|
void MovePicker::score() {
|
||||||
|
|
||||||
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
|
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
|
||||||
|
|
||||||
[[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, threatenedPieces;
|
[[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook,
|
||||||
|
threatenedPieces;
|
||||||
if constexpr (Type == QUIETS)
|
if constexpr (Type == QUIETS)
|
||||||
{
|
{
|
||||||
Color us = pos.side_to_move();
|
Color us = pos.side_to_move();
|
||||||
|
|
||||||
threatenedByPawn = pos.attacks_by<PAWN>(~us);
|
threatenedByPawn = pos.attacks_by<PAWN>(~us);
|
||||||
threatenedByMinor = pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
|
threatenedByMinor =
|
||||||
|
pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
|
||||||
threatenedByRook = pos.attacks_by<ROOK>(~us) | threatenedByMinor;
|
threatenedByRook = pos.attacks_by<ROOK>(~us) | threatenedByMinor;
|
||||||
|
|
||||||
// Pieces threatened by pieces of lesser material value
|
// Pieces threatened by pieces of lesser material value
|
||||||
@@ -122,36 +166,60 @@ void MovePicker::score() {
|
|||||||
|
|
||||||
for (auto& m : *this)
|
for (auto& m : *this)
|
||||||
if constexpr (Type == CAPTURES)
|
if constexpr (Type == CAPTURES)
|
||||||
m.value = (7 * int(PieceValue[MG][pos.piece_on(to_sq(m))])
|
m.value =
|
||||||
+ (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16;
|
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)
|
else if constexpr (Type == QUIETS)
|
||||||
m.value = 2 * (*mainHistory)[pos.side_to_move()][from_to(m)]
|
{
|
||||||
+ 2 * (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)]
|
Piece pc = pos.moved_piece(m);
|
||||||
+ (*continuationHistory[1])[pos.moved_piece(m)][to_sq(m)]
|
PieceType pt = type_of(pc);
|
||||||
+ (*continuationHistory[3])[pos.moved_piece(m)][to_sq(m)]
|
Square from = m.from_sq();
|
||||||
+ (*continuationHistory[5])[pos.moved_piece(m)][to_sq(m)]
|
Square to = m.to_sq();
|
||||||
+ (threatenedPieces & from_sq(m) ?
|
|
||||||
(type_of(pos.moved_piece(m)) == QUEEN && !(to_sq(m) & threatenedByRook) ? 50000
|
// histories
|
||||||
: type_of(pos.moved_piece(m)) == ROOK && !(to_sq(m) & threatenedByMinor) ? 25000
|
m.value = 2 * (*mainHistory)[pos.side_to_move()][m.from_to()];
|
||||||
: !(to_sq(m) & threatenedByPawn) ? 15000
|
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) ? 51000
|
||||||
|
: pt == ROOK && !(to & threatenedByMinor) ? 24950
|
||||||
|
: !(to & threatenedByPawn) ? 14450
|
||||||
: 0)
|
: 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) * 24500
|
||||||
|
: pt != PAWN ? bool(to & threatenedByPawn) * 14950
|
||||||
: 0)
|
: 0)
|
||||||
+ bool(pos.check_squares(type_of(pos.moved_piece(m))) & to_sq(m)) * 16384;
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
else // Type == EVASIONS
|
else // Type == EVASIONS
|
||||||
{
|
{
|
||||||
if (pos.capture_stage(m))
|
if (pos.capture_stage(m))
|
||||||
m.value = PieceValue[MG][pos.piece_on(to_sq(m))]
|
m.value =
|
||||||
- Value(type_of(pos.moved_piece(m)))
|
PieceValue[pos.piece_on(m.to_sq())] - type_of(pos.moved_piece(m)) + (1 << 28);
|
||||||
+ (1 << 28);
|
|
||||||
else
|
else
|
||||||
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
|
m.value = (*mainHistory)[pos.side_to_move()][m.from_to()]
|
||||||
+ (*continuationHistory[0])[pos.moved_piece(m)][to_sq(m)];
|
+ (*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.
|
// Returns the next move satisfying a predicate function.
|
||||||
/// It never returns the TT move.
|
// It never returns the TT move.
|
||||||
template<MovePicker::PickType T, typename Pred>
|
template<MovePicker::PickType T, typename Pred>
|
||||||
Move MovePicker::select(Pred filter) {
|
Move MovePicker::select(Pred filter) {
|
||||||
|
|
||||||
@@ -165,16 +233,19 @@ Move MovePicker::select(Pred filter) {
|
|||||||
|
|
||||||
cur++;
|
cur++;
|
||||||
}
|
}
|
||||||
return MOVE_NONE;
|
return Move::none();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MovePicker::next_move() is the most important method of the MovePicker class. It
|
// Most important method of the MovePicker class. It
|
||||||
/// returns a new pseudo-legal move every time it is called until there are no more
|
// 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.
|
// moves left, picking the move with the highest score from a list of generated moves.
|
||||||
Move MovePicker::next_move(bool skipQuiets) {
|
Move MovePicker::next_move(bool skipQuiets) {
|
||||||
|
|
||||||
|
auto quiet_threshold = [](Depth d) { return -3550 * d; };
|
||||||
|
|
||||||
top:
|
top:
|
||||||
switch (stage) {
|
switch (stage)
|
||||||
|
{
|
||||||
|
|
||||||
case MAIN_TT :
|
case MAIN_TT :
|
||||||
case EVASION_TT :
|
case EVASION_TT :
|
||||||
@@ -196,9 +267,10 @@ top:
|
|||||||
|
|
||||||
case GOOD_CAPTURE :
|
case GOOD_CAPTURE :
|
||||||
if (select<Next>([&]() {
|
if (select<Next>([&]() {
|
||||||
return pos.see_ge(*cur, Value(-cur->value)) ?
|
|
||||||
// Move losing capture to endBadCaptures to be tried later
|
// Move losing capture to endBadCaptures to be tried later
|
||||||
true : (*endBadCaptures++ = *cur, false); }))
|
return pos.see_ge(*cur, -cur->value / 18) ? true
|
||||||
|
: (*endBadCaptures++ = *cur, false);
|
||||||
|
}))
|
||||||
return *(cur - 1);
|
return *(cur - 1);
|
||||||
|
|
||||||
// Prepare the pointers to loop over the refutations array
|
// Prepare the pointers to loop over the refutations array
|
||||||
@@ -206,17 +278,16 @@ top:
|
|||||||
endMoves = std::end(refutations);
|
endMoves = std::end(refutations);
|
||||||
|
|
||||||
// If the countermove is the same as a killer, skip it
|
// If the countermove is the same as a killer, skip it
|
||||||
if ( refutations[0].move == refutations[2].move
|
if (refutations[0] == refutations[2] || refutations[1] == refutations[2])
|
||||||
|| refutations[1].move == refutations[2].move)
|
|
||||||
--endMoves;
|
--endMoves;
|
||||||
|
|
||||||
++stage;
|
++stage;
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
|
|
||||||
case REFUTATION :
|
case REFUTATION :
|
||||||
if (select<Next>([&](){ return *cur != MOVE_NONE
|
if (select<Next>([&]() {
|
||||||
&& !pos.capture_stage(*cur)
|
return *cur != Move::none() && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur);
|
||||||
&& pos.pseudo_legal(*cur); }))
|
}))
|
||||||
return *(cur - 1);
|
return *(cur - 1);
|
||||||
++stage;
|
++stage;
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
@@ -225,22 +296,27 @@ top:
|
|||||||
if (!skipQuiets)
|
if (!skipQuiets)
|
||||||
{
|
{
|
||||||
cur = endBadCaptures;
|
cur = endBadCaptures;
|
||||||
endMoves = generate<QUIETS>(pos, cur);
|
endMoves = beginBadQuiets = endBadQuiets = generate<QUIETS>(pos, cur);
|
||||||
|
|
||||||
score<QUIETS>();
|
score<QUIETS>();
|
||||||
partial_insertion_sort(cur, endMoves, -3000 * depth);
|
partial_insertion_sort(cur, endMoves, quiet_threshold(depth));
|
||||||
}
|
}
|
||||||
|
|
||||||
++stage;
|
++stage;
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
|
|
||||||
case QUIET:
|
case GOOD_QUIET :
|
||||||
if ( !skipQuiets
|
if (!skipQuiets && select<Next>([&]() {
|
||||||
&& select<Next>([&](){return *cur != refutations[0].move
|
return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2];
|
||||||
&& *cur != refutations[1].move
|
}))
|
||||||
&& *cur != refutations[2].move;}))
|
{
|
||||||
|
if ((cur - 1)->value > -8000 || (cur - 1)->value <= quiet_threshold(depth))
|
||||||
return *(cur - 1);
|
return *(cur - 1);
|
||||||
|
|
||||||
|
// Remaining quiets are bad
|
||||||
|
beginBadQuiets = cur - 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare the pointers to loop over the bad captures
|
// Prepare the pointers to loop over the bad captures
|
||||||
cur = moves;
|
cur = moves;
|
||||||
endMoves = endBadCaptures;
|
endMoves = endBadCaptures;
|
||||||
@@ -249,7 +325,23 @@ top:
|
|||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
|
|
||||||
case BAD_CAPTURE :
|
case BAD_CAPTURE :
|
||||||
return select<Next>([](){ return true; });
|
if (select<Next>([]() { return true; }))
|
||||||
|
return *(cur - 1);
|
||||||
|
|
||||||
|
// Prepare the pointers to loop over the bad quiets
|
||||||
|
cur = beginBadQuiets;
|
||||||
|
endMoves = endBadQuiets;
|
||||||
|
|
||||||
|
++stage;
|
||||||
|
[[fallthrough]];
|
||||||
|
|
||||||
|
case BAD_QUIET :
|
||||||
|
if (!skipQuiets)
|
||||||
|
return select<Next>([&]() {
|
||||||
|
return *cur != refutations[0] && *cur != refutations[1] && *cur != refutations[2];
|
||||||
|
});
|
||||||
|
|
||||||
|
return Move::none();
|
||||||
|
|
||||||
case EVASION_INIT :
|
case EVASION_INIT :
|
||||||
cur = moves;
|
cur = moves;
|
||||||
@@ -266,13 +358,12 @@ top:
|
|||||||
return select<Next>([&]() { return pos.see_ge(*cur, threshold); });
|
return select<Next>([&]() { return pos.see_ge(*cur, threshold); });
|
||||||
|
|
||||||
case QCAPTURE :
|
case QCAPTURE :
|
||||||
if (select<Next>([&](){ return depth > DEPTH_QS_RECAPTURES
|
if (select<Next>([]() { return true; }))
|
||||||
|| to_sq(*cur) == recaptureSquare; }))
|
|
||||||
return *(cur - 1);
|
return *(cur - 1);
|
||||||
|
|
||||||
// If we did not find any move and we do not try checks, we have finished
|
// If we did not find any move and we do not try checks, we have finished
|
||||||
if (depth != DEPTH_QS_CHECKS)
|
if (depth != DEPTH_QS_CHECKS)
|
||||||
return MOVE_NONE;
|
return Move::none();
|
||||||
|
|
||||||
++stage;
|
++stage;
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
@@ -289,7 +380,7 @@ top:
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert(false);
|
assert(false);
|
||||||
return MOVE_NONE; // Silence warning
|
return Move::none(); // Silence warning
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|||||||
+97
-50
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,9 +19,14 @@
|
|||||||
#ifndef MOVEPICK_H_INCLUDED
|
#ifndef MOVEPICK_H_INCLUDED
|
||||||
#define MOVEPICK_H_INCLUDED
|
#define MOVEPICK_H_INCLUDED
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <type_traits>
|
#include <type_traits> // IWYU pragma: keep
|
||||||
|
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
@@ -29,10 +34,30 @@
|
|||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
/// StatsEntry stores the stat table value. It is usually a number but could
|
constexpr int PAWN_HISTORY_SIZE = 512; // has to be a power of 2
|
||||||
/// be a move or even a nested history. We use a class instead of naked value
|
constexpr int CORRECTION_HISTORY_SIZE = 16384; // has to be a power of 2
|
||||||
/// to directly call history update operator<<() on the entry so to use stats
|
constexpr int CORRECTION_HISTORY_LIMIT = 1024;
|
||||||
/// tables at caller sites as simple multi-dim arrays.
|
|
||||||
|
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>
|
template<typename T, int D>
|
||||||
class StatsEntry {
|
class StatsEntry {
|
||||||
|
|
||||||
@@ -45,29 +70,29 @@ public:
|
|||||||
operator const T&() const { return entry; }
|
operator const T&() const { return entry; }
|
||||||
|
|
||||||
void operator<<(int bonus) {
|
void operator<<(int bonus) {
|
||||||
assert(abs(bonus) <= D); // Ensure range is [-D, D]
|
|
||||||
static_assert(D <= std::numeric_limits<T>::max(), "D overflows T");
|
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.
|
// 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
|
// 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
|
// 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)
|
// values with the << operator, while the last parameters (Size and Sizes)
|
||||||
/// encode the dimensions of the array.
|
// encode the dimensions of the array.
|
||||||
template<typename T, int D, int Size, int... Sizes>
|
template<typename T, int D, int Size, int... Sizes>
|
||||||
struct Stats : public std::array<Stats<T, D, Sizes...>, Size>
|
struct Stats: public std::array<Stats<T, D, Sizes...>, Size> {
|
||||||
{
|
|
||||||
using stats = Stats<T, D, Size, Sizes...>;
|
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
|
// For standard-layout 'this' points to the first struct member
|
||||||
assert(std::is_standard_layout<stats>::value);
|
assert(std::is_standard_layout_v<stats>);
|
||||||
|
|
||||||
using entry = StatsEntry<T, D>;
|
using entry = StatsEntry<T, D>;
|
||||||
entry* p = reinterpret_cast<entry*>(this);
|
entry* p = reinterpret_cast<entry*>(this);
|
||||||
@@ -78,62 +103,84 @@ struct Stats : public std::array<Stats<T, D, Sizes...>, Size>
|
|||||||
template<typename T, int D, int Size>
|
template<typename T, int D, int Size>
|
||||||
struct Stats<T, D, Size>: public std::array<StatsEntry<T, D>, 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
|
// In stats table, D=0 means that the template parameter is not used
|
||||||
enum StatsParams { NOT_USED = 0 };
|
enum StatsParams {
|
||||||
enum StatsType { NoCaptures, Captures };
|
NOT_USED = 0
|
||||||
|
};
|
||||||
|
enum StatsType {
|
||||||
|
NoCaptures,
|
||||||
|
Captures
|
||||||
|
};
|
||||||
|
|
||||||
/// ButterflyHistory records how often quiet moves have been successful or
|
// ButterflyHistory records how often quiet moves have been successful or unsuccessful
|
||||||
/// unsuccessful during the current search, and is used for reduction and move
|
// during the current search, and is used for reduction and move ordering decisions.
|
||||||
/// ordering decisions. It uses 2 tables (one for each color) indexed by
|
// It uses 2 tables (one for each color) indexed by the move's from and to squares,
|
||||||
/// the move's from and to squares, see www.chessprogramming.org/Butterfly_Boards
|
// see www.chessprogramming.org/Butterfly_Boards (~11 elo)
|
||||||
/// (~11 elo)
|
|
||||||
using ButterflyHistory = Stats<int16_t, 7183, COLOR_NB, int(SQUARE_NB) * int(SQUARE_NB)>;
|
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
|
// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous
|
||||||
/// move, see www.chessprogramming.org/Countermove_Heuristic
|
// move, see www.chessprogramming.org/Countermove_Heuristic
|
||||||
using CounterMoveHistory = Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB>;
|
using CounterMoveHistory = Stats<Move, NOT_USED, PIECE_NB, SQUARE_NB>;
|
||||||
|
|
||||||
/// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
|
// 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>;
|
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]
|
// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
|
||||||
using PieceToHistory = Stats<int16_t, 29952, PIECE_NB, SQUARE_NB>;
|
using PieceToHistory = Stats<int16_t, 29952, PIECE_NB, SQUARE_NB>;
|
||||||
|
|
||||||
/// ContinuationHistory is the combined history of a given pair of moves, usually
|
// 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
|
// the current one given a previous one. The nested history table is based on
|
||||||
/// PieceToHistory instead of ButterflyBoards.
|
// PieceToHistory instead of ButterflyBoards.
|
||||||
/// (~63 elo)
|
// (~63 elo)
|
||||||
using ContinuationHistory = Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB>;
|
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
|
// CorrectionHistory is addressed by color and pawn structure
|
||||||
/// current position. The most important method is next_move(), which returns a
|
using CorrectionHistory =
|
||||||
/// new pseudo-legal move each time it is called, until there are no moves left,
|
Stats<int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, CORRECTION_HISTORY_SIZE>;
|
||||||
/// 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
|
// MovePicker class is used to pick one pseudo-legal move at a time from the
|
||||||
/// likely to get a cut-off first.
|
// 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 {
|
class MovePicker {
|
||||||
|
|
||||||
enum PickType { Next, Best };
|
enum PickType {
|
||||||
|
Next,
|
||||||
|
Best
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MovePicker(const MovePicker&) = delete;
|
MovePicker(const MovePicker&) = delete;
|
||||||
MovePicker& operator=(const MovePicker&) = delete;
|
MovePicker& operator=(const MovePicker&) = delete;
|
||||||
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
MovePicker(const Position&,
|
||||||
|
Move,
|
||||||
|
Depth,
|
||||||
|
const ButterflyHistory*,
|
||||||
const CapturePieceToHistory*,
|
const CapturePieceToHistory*,
|
||||||
const PieceToHistory**,
|
const PieceToHistory**,
|
||||||
|
const PawnHistory*,
|
||||||
Move,
|
Move,
|
||||||
const Move*);
|
const Move*);
|
||||||
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
|
MovePicker(const Position&,
|
||||||
|
Move,
|
||||||
|
Depth,
|
||||||
|
const ButterflyHistory*,
|
||||||
const CapturePieceToHistory*,
|
const CapturePieceToHistory*,
|
||||||
const PieceToHistory**,
|
const PieceToHistory**,
|
||||||
Square);
|
const PawnHistory*);
|
||||||
MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
|
MovePicker(const Position&, Move, int, const CapturePieceToHistory*);
|
||||||
Move next_move(bool skipQuiets = false);
|
Move next_move(bool skipQuiets = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<PickType T, typename Pred> Move select(Pred);
|
template<PickType T, typename Pred>
|
||||||
template<GenType> void score();
|
Move select(Pred);
|
||||||
|
template<GenType>
|
||||||
|
void score();
|
||||||
ExtMove* begin() { return cur; }
|
ExtMove* begin() { return cur; }
|
||||||
ExtMove* end() { return endMoves; }
|
ExtMove* end() { return endMoves; }
|
||||||
|
|
||||||
@@ -141,11 +188,11 @@ private:
|
|||||||
const ButterflyHistory* mainHistory;
|
const ButterflyHistory* mainHistory;
|
||||||
const CapturePieceToHistory* captureHistory;
|
const CapturePieceToHistory* captureHistory;
|
||||||
const PieceToHistory** continuationHistory;
|
const PieceToHistory** continuationHistory;
|
||||||
|
const PawnHistory* pawnHistory;
|
||||||
Move ttMove;
|
Move ttMove;
|
||||||
ExtMove refutations[3], *cur, *endMoves, *endBadCaptures;
|
ExtMove refutations[3], *cur, *endMoves, *endBadCaptures, *beginBadQuiets, *endBadQuiets;
|
||||||
int stage;
|
int stage;
|
||||||
Square recaptureSquare;
|
int threshold;
|
||||||
Value threshold;
|
|
||||||
Depth depth;
|
Depth depth;
|
||||||
ExtMove moves[MAX_MOVES];
|
ExtMove moves[MAX_MOVES];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,405 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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 <fstream>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <iostream>
|
|
||||||
#include <set>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "../evaluate.h"
|
|
||||||
#include "../position.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
|
|
||||||
static void initialize() {
|
|
||||||
|
|
||||||
Detail::initialize(featureTransformer);
|
|
||||||
for (std::size_t i = 0; i < LayerStacks; ++i)
|
|
||||||
Detail::initialize(network[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read network header
|
|
||||||
static 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
|
|
||||||
static 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, (std::uint32_t)desc.size());
|
|
||||||
stream.write(&desc[0], desc.size());
|
|
||||||
return !stream.fail();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read network parameters
|
|
||||||
static 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
|
|
||||||
static 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void hint_common_parent_position(const Position& pos) {
|
|
||||||
if (Eval::useNNUE)
|
|
||||||
featureTransformer->hint_common_access(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluation function. Perform differential calculation.
|
|
||||||
Value evaluate(const Position& pos, bool adjusted, int* complexity) {
|
|
||||||
|
|
||||||
// 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::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 int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
|
||||||
const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket);
|
|
||||||
const auto positional = network[bucket]->propagate(transformedFeatures);
|
|
||||||
|
|
||||||
if (complexity)
|
|
||||||
*complexity = 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (IndexType 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr std::string_view 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 / UCI::NormalizeToPawnValue);
|
|
||||||
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, always keeping two decimals.
|
|
||||||
static void format_cp_aligned_dot(Value v, std::stringstream &stream) {
|
|
||||||
const double cp = 1.0 * std::abs(int(v)) / UCI::NormalizeToPawnValue;
|
|
||||||
|
|
||||||
stream << (v < 0 ? '-' : v > 0 ? '+' : ' ')
|
|
||||||
<< std::setiosflags(std::ios::fixed)
|
|
||||||
<< std::setw(6)
|
|
||||||
<< std::setprecision(2)
|
|
||||||
<< 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)
|
|
||||||
{
|
|
||||||
ss << "| " << bucket << " ";
|
|
||||||
ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss); ss << " "
|
|
||||||
<< " | "; format_cp_aligned_dot(t.positional[bucket], ss); ss << " "
|
|
||||||
<< " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); ss << " "
|
|
||||||
<< " |";
|
|
||||||
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
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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>>;
|
|
||||||
|
|
||||||
std::string trace(Position& pos);
|
|
||||||
Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr);
|
|
||||||
void hint_common_parent_position(const Position& pos);
|
|
||||||
|
|
||||||
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 Stockfish::Eval::NNUE
|
|
||||||
|
|
||||||
#endif // #ifndef NNUE_EVALUATE_NNUE_H_INCLUDED
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,22 +20,23 @@
|
|||||||
|
|
||||||
#include "half_ka_v2_hm.h"
|
#include "half_ka_v2_hm.h"
|
||||||
|
|
||||||
|
#include "../../bitboard.h"
|
||||||
#include "../../position.h"
|
#include "../../position.h"
|
||||||
|
#include "../../types.h"
|
||||||
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
namespace Stockfish::Eval::NNUE::Features {
|
namespace Stockfish::Eval::NNUE::Features {
|
||||||
|
|
||||||
// Index of a feature for a given king position and another piece on some square
|
// Index of a feature for a given king position and another piece on some square
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) {
|
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]);
|
return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc]
|
||||||
|
+ KingBuckets[Perspective][ksq]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a list of indices for active features
|
// Get a list of indices for active features
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
void HalfKAv2_hm::append_active_indices(
|
void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) {
|
||||||
const Position& pos,
|
|
||||||
IndexList& active
|
|
||||||
) {
|
|
||||||
Square ksq = pos.square<KING>(Perspective);
|
Square ksq = pos.square<KING>(Perspective);
|
||||||
Bitboard bb = pos.pieces();
|
Bitboard bb = pos.pieces();
|
||||||
while (bb)
|
while (bb)
|
||||||
@@ -49,15 +50,14 @@ namespace Stockfish::Eval::NNUE::Features {
|
|||||||
template void HalfKAv2_hm::append_active_indices<WHITE>(const Position& pos, IndexList& active);
|
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 void HalfKAv2_hm::append_active_indices<BLACK>(const Position& pos, IndexList& active);
|
||||||
|
|
||||||
// append_changed_indices() : get a list of indices for recently changed features
|
// Get a list of indices for recently changed features
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
void HalfKAv2_hm::append_changed_indices(
|
void HalfKAv2_hm::append_changed_indices(Square ksq,
|
||||||
Square ksq,
|
|
||||||
const DirtyPiece& dp,
|
const DirtyPiece& dp,
|
||||||
IndexList& removed,
|
IndexList& removed,
|
||||||
IndexList& added
|
IndexList& added) {
|
||||||
) {
|
for (int i = 0; i < dp.dirty_num; ++i)
|
||||||
for (int i = 0; i < dp.dirty_num; ++i) {
|
{
|
||||||
if (dp.from[i] != SQ_NONE)
|
if (dp.from[i] != SQ_NONE)
|
||||||
removed.push_back(make_index<Perspective>(dp.from[i], dp.piece[i], ksq));
|
removed.push_back(make_index<Perspective>(dp.from[i], dp.piece[i], ksq));
|
||||||
if (dp.to[i] != SQ_NONE)
|
if (dp.to[i] != SQ_NONE)
|
||||||
@@ -66,16 +66,18 @@ namespace Stockfish::Eval::NNUE::Features {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Explicit template instantiations
|
// 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<WHITE>(Square ksq,
|
||||||
template void HalfKAv2_hm::append_changed_indices<BLACK>(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
|
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::update_cost(const StateInfo* st) {
|
int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; }
|
||||||
return st->dirtyPiece.dirty_num;
|
|
||||||
}
|
|
||||||
|
|
||||||
int HalfKAv2_hm::refresh_cost(const Position& pos) {
|
int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count<ALL_PIECES>(); }
|
||||||
return pos.count<ALL_PIECES>();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
|
bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
|
||||||
return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
|
return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,22 +21,24 @@
|
|||||||
#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
||||||
#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
|
||||||
|
|
||||||
#include "../nnue_common.h"
|
#include <cstdint>
|
||||||
|
|
||||||
#include "../../evaluate.h"
|
|
||||||
#include "../../misc.h"
|
#include "../../misc.h"
|
||||||
|
#include "../../types.h"
|
||||||
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
struct StateInfo;
|
struct StateInfo;
|
||||||
|
class Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Stockfish::Eval::NNUE::Features {
|
namespace Stockfish::Eval::NNUE::Features {
|
||||||
|
|
||||||
// Feature HalfKAv2_hm: Combination of the position of own king
|
// Feature HalfKAv2_hm: Combination of the position of own king and the
|
||||||
// and the position of pieces. Position mirrored such that king always on e..h files.
|
// position of pieces. Position mirrored such that king is always on e..h files.
|
||||||
class HalfKAv2_hm {
|
class HalfKAv2_hm {
|
||||||
|
|
||||||
// unique number for each piece type on each square
|
// Unique number for each piece type on each square
|
||||||
enum {
|
enum {
|
||||||
PS_NONE = 0,
|
PS_NONE = 0,
|
||||||
PS_W_PAWN = 0,
|
PS_W_PAWN = 0,
|
||||||
@@ -54,13 +56,12 @@ namespace Stockfish::Eval::NNUE::Features {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
|
static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
|
||||||
// convention: W - us, B - them
|
// Convention: W - us, B - them
|
||||||
// viewed from other side, W and B are reversed
|
// 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_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_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 }
|
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}};
|
||||||
};
|
|
||||||
|
|
||||||
// Index of a feature for a given king position and another piece on some square
|
// Index of a feature for a given king position and another piece on some square
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
@@ -78,6 +79,7 @@ namespace Stockfish::Eval::NNUE::Features {
|
|||||||
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
|
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
|
||||||
|
|
||||||
#define B(v) (v * PS_NB)
|
#define B(v) (v * PS_NB)
|
||||||
|
// clang-format off
|
||||||
static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = {
|
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(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(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
|
||||||
@@ -96,8 +98,9 @@ namespace Stockfish::Eval::NNUE::Features {
|
|||||||
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
|
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
|
||||||
B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) }
|
B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) }
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
#undef B
|
#undef B
|
||||||
|
// clang-format off
|
||||||
// Orient a square according to perspective (rotates by 180 for black)
|
// Orient a square according to perspective (rotates by 180 for black)
|
||||||
static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = {
|
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,
|
||||||
@@ -117,6 +120,7 @@ namespace Stockfish::Eval::NNUE::Features {
|
|||||||
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.
|
// Maximum number of simultaneously active features.
|
||||||
static constexpr IndexType MaxActiveDimensions = 32;
|
static constexpr IndexType MaxActiveDimensions = 32;
|
||||||
@@ -124,26 +128,20 @@ namespace Stockfish::Eval::NNUE::Features {
|
|||||||
|
|
||||||
// Get a list of indices for active features
|
// Get a list of indices for active features
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
static void append_active_indices(
|
static void append_active_indices(const Position& pos, IndexList& active);
|
||||||
const Position& pos,
|
|
||||||
IndexList& active);
|
|
||||||
|
|
||||||
// Get a list of indices for recently changed features
|
// Get a list of indices for recently changed features
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
static void append_changed_indices(
|
static void
|
||||||
Square ksq,
|
append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
|
||||||
const DirtyPiece& dp,
|
|
||||||
IndexList& removed,
|
|
||||||
IndexList& added
|
|
||||||
);
|
|
||||||
|
|
||||||
// Returns the cost of updating one perspective, the most costly one.
|
// Returns the cost of updating one perspective, the most costly one.
|
||||||
// Assumes no refresh needed.
|
// Assumes no refresh needed.
|
||||||
static int update_cost(const StateInfo* st);
|
static int update_cost(const StateInfo* st);
|
||||||
static int refresh_cost(const Position& pos);
|
static int refresh_cost(const Position& pos);
|
||||||
|
|
||||||
// Returns whether the change stored in this StateInfo means that
|
// Returns whether the change stored in this StateInfo means
|
||||||
// a full accumulator refresh is required.
|
// that a full accumulator refresh is required.
|
||||||
static bool requires_refresh(const StateInfo* st, Color perspective);
|
static bool requires_refresh(const StateInfo* st, Color perspective);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,33 +21,18 @@
|
|||||||
#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
||||||
#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
#define NNUE_LAYERS_AFFINE_TRANSFORM_H_INCLUDED
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
|
||||||
#include <type_traits>
|
|
||||||
#include "../nnue_common.h"
|
#include "../nnue_common.h"
|
||||||
#include "simd.h"
|
#include "simd.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This file contains the definition for a fully connected layer (aka affine transform).
|
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 (a specialization for large inputs):
|
|
||||||
- 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 (a specialization for small inputs):
|
|
||||||
- used when the PaddedInputDimensions < 128
|
|
||||||
- expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32.
|
- expected use-case is for when PaddedInputDimensions == 32 and InputDimensions <= 32.
|
||||||
- that's why AVX512 is hard to implement
|
- that's why AVX512 is hard to implement
|
||||||
- expected use-case is small layers
|
- 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
|
- inputs are processed in chunks of 4, weights are respectively transposed
|
||||||
- accumulation happens directly to int32s
|
- accumulation happens directly to int32s
|
||||||
*/
|
*/
|
||||||
@@ -55,22 +40,20 @@
|
|||||||
namespace Stockfish::Eval::NNUE::Layers {
|
namespace Stockfish::Eval::NNUE::Layers {
|
||||||
|
|
||||||
// Fallback implementation for older/other architectures.
|
// 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)
|
#if !defined(USE_SSSE3)
|
||||||
template<IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
|
template<IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
|
||||||
static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input)
|
static void affine_transform_non_ssse3(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)
|
#if defined(USE_SSE2)
|
||||||
// At least a multiple of 16, with SSE2.
|
// At least a multiple of 16, with SSE2.
|
||||||
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
|
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
|
||||||
const __m128i Zeros = _mm_setzero_si128();
|
const __m128i Zeros = _mm_setzero_si128();
|
||||||
const auto inputVector = reinterpret_cast<const __m128i*>(input);
|
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)
|
#elif defined(USE_NEON_DOTPROD)
|
||||||
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
|
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
|
||||||
const auto inputVector = reinterpret_cast<const int8x16_t*>(input);
|
const auto inputVector = reinterpret_cast<const int8x16_t*>(input);
|
||||||
@@ -80,14 +63,16 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
const auto inputVector = reinterpret_cast<const int8x8_t*>(input);
|
const auto inputVector = reinterpret_cast<const int8x8_t*>(input);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (IndexType i = 0; i < OutputDimensions; ++i) {
|
for (IndexType i = 0; i < OutputDimensions; ++i)
|
||||||
|
{
|
||||||
const IndexType offset = i * PaddedInputDimensions;
|
const IndexType offset = i * PaddedInputDimensions;
|
||||||
|
|
||||||
#if defined(USE_SSE2)
|
#if defined(USE_SSE2)
|
||||||
__m128i sumLo = _mm_cvtsi32_si128(biases[i]);
|
__m128i sumLo = _mm_cvtsi32_si128(biases[i]);
|
||||||
__m128i sumHi = Zeros;
|
__m128i sumHi = Zeros;
|
||||||
const auto row = reinterpret_cast<const __m128i*>(&weights[offset]);
|
const auto row = reinterpret_cast<const __m128i*>(&weights[offset]);
|
||||||
for (IndexType j = 0; j < NumChunks; ++j) {
|
for (IndexType j = 0; j < NumChunks; ++j)
|
||||||
|
{
|
||||||
__m128i row_j = _mm_load_si128(&row[j]);
|
__m128i row_j = _mm_load_si128(&row[j]);
|
||||||
__m128i input_j = _mm_load_si128(&inputVector[j]);
|
__m128i input_j = _mm_load_si128(&inputVector[j]);
|
||||||
__m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);
|
__m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);
|
||||||
@@ -106,30 +91,11 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
sum = _mm_add_epi32(sum, sum_second_32);
|
sum = _mm_add_epi32(sum, sum_second_32);
|
||||||
output[i] = _mm_cvtsi128_si32(sum);
|
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)
|
#elif defined(USE_NEON_DOTPROD)
|
||||||
int32x4_t sum = {biases[i]};
|
int32x4_t sum = {biases[i]};
|
||||||
const auto row = reinterpret_cast<const int8x16_t*>(&weights[offset]);
|
const auto row = reinterpret_cast<const int8x16_t*>(&weights[offset]);
|
||||||
for (IndexType j = 0; j < NumChunks; ++j) {
|
for (IndexType j = 0; j < NumChunks; ++j)
|
||||||
|
{
|
||||||
sum = vdotq_s32(sum, inputVector[j], row[j]);
|
sum = vdotq_s32(sum, inputVector[j], row[j]);
|
||||||
}
|
}
|
||||||
output[i] = vaddvq_s32(sum);
|
output[i] = vaddvq_s32(sum);
|
||||||
@@ -137,40 +103,34 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
#elif defined(USE_NEON)
|
#elif defined(USE_NEON)
|
||||||
int32x4_t sum = {biases[i]};
|
int32x4_t sum = {biases[i]};
|
||||||
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
|
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
|
||||||
for (IndexType j = 0; j < NumChunks; ++j) {
|
for (IndexType j = 0; j < NumChunks; ++j)
|
||||||
|
{
|
||||||
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
|
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
|
||||||
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
|
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
|
||||||
sum = vpadalq_s16(sum, product);
|
sum = vpadalq_s16(sum, product);
|
||||||
}
|
}
|
||||||
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
|
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
|
||||||
|
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
std::int32_t sum = biases[i];
|
std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions);
|
||||||
for (IndexType j = 0; j < InputDimensions; ++j) {
|
|
||||||
sum += weights[offset + j] * input[j];
|
|
||||||
}
|
|
||||||
output[i] = sum;
|
|
||||||
# endif
|
|
||||||
}
|
|
||||||
|
|
||||||
# if defined(USE_MMX)
|
// Traverse weights in transpose order to take advantage of input sparsity
|
||||||
_mm_empty();
|
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
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <IndexType InDims, IndexType OutDims, typename Enabled = void>
|
|
||||||
class AffineTransform;
|
|
||||||
|
|
||||||
#if defined (USE_AVX512)
|
|
||||||
constexpr IndexType LargeInputSize = 2 * 64;
|
|
||||||
#else
|
|
||||||
constexpr IndexType LargeInputSize = std::numeric_limits<IndexType>::max();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// A specialization for large inputs
|
|
||||||
template<IndexType InDims, IndexType OutDims>
|
template<IndexType InDims, IndexType OutDims>
|
||||||
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) >= LargeInputSize)>> {
|
class AffineTransform {
|
||||||
public:
|
public:
|
||||||
// Input/output type
|
// Input/output type
|
||||||
using InputType = std::uint8_t;
|
using InputType = std::uint8_t;
|
||||||
@@ -187,42 +147,6 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
|
|
||||||
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
using OutputBuffer = OutputType[PaddedOutputDimensions];
|
||||||
|
|
||||||
static_assert(PaddedInputDimensions >= LargeInputSize, "Something went wrong. This specialization (for large inputs) should not have been chosen.");
|
|
||||||
|
|
||||||
#if defined (USE_AVX512)
|
|
||||||
static constexpr IndexType InputSimdWidth = 64;
|
|
||||||
static constexpr IndexType MaxNumOutputRegs = 16;
|
|
||||||
#elif defined (USE_AVX2)
|
|
||||||
static constexpr IndexType InputSimdWidth = 32;
|
|
||||||
static constexpr IndexType MaxNumOutputRegs = 8;
|
|
||||||
#elif defined (USE_SSSE3)
|
|
||||||
static constexpr IndexType InputSimdWidth = 16;
|
|
||||||
static constexpr IndexType MaxNumOutputRegs = 8;
|
|
||||||
#elif defined (USE_NEON_DOTPROD)
|
|
||||||
static constexpr IndexType InputSimdWidth = 16;
|
|
||||||
static constexpr IndexType MaxNumOutputRegs = 8;
|
|
||||||
#elif defined (USE_NEON)
|
|
||||||
static constexpr IndexType InputSimdWidth = 8;
|
|
||||||
static constexpr IndexType MaxNumOutputRegs = 8;
|
|
||||||
#else
|
|
||||||
// The fallback implementation will not have permuted weights.
|
|
||||||
// We define these to avoid a lot of ifdefs later.
|
|
||||||
static constexpr IndexType InputSimdWidth = 1;
|
|
||||||
static constexpr 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 IndexType NumOutputRegs = std::min(MaxNumOutputRegs, OutputDimensions);
|
|
||||||
static constexpr IndexType SmallBlockSize = InputSimdWidth;
|
|
||||||
static constexpr IndexType BigBlockSize = NumOutputRegs * PaddedInputDimensions;
|
|
||||||
static constexpr IndexType NumSmallBlocksInBigBlock = BigBlockSize / SmallBlockSize;
|
|
||||||
static constexpr IndexType NumSmallBlocksPerOutput = PaddedInputDimensions / SmallBlockSize;
|
|
||||||
static constexpr IndexType NumBigBlocks = OutputDimensions / NumOutputRegs;
|
|
||||||
|
|
||||||
static_assert(OutputDimensions % NumOutputRegs == 0);
|
|
||||||
|
|
||||||
// Hash value embedded in the evaluation file
|
// Hash value embedded in the evaluation file
|
||||||
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
|
||||||
std::uint32_t hashValue = 0xCC03DAE4u;
|
std::uint32_t hashValue = 0xCC03DAE4u;
|
||||||
@@ -232,210 +156,12 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
return hashValue;
|
return hashValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
static constexpr IndexType get_weight_index_scrambled(IndexType i) {
|
||||||
Transposes the small blocks within a block.
|
return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4
|
||||||
Effectively means that weights can be traversed sequentially during inference.
|
+ i / PaddedInputDimensions * 4 + i % 4;
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read network parameters
|
static constexpr IndexType get_weight_index(IndexType i) {
|
||||||
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
|
|
||||||
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_DOTPROD)
|
|
||||||
using acc_vec_t = int32x4_t;
|
|
||||||
using bias_vec_t = int32x4_t;
|
|
||||||
using weight_vec_t = int8x16_t;
|
|
||||||
using in_vec_t = int8x16_t;
|
|
||||||
#define vec_zero {0}
|
|
||||||
#define vec_add_dpbusd_32x2 Simd::dotprod_m128_add_dpbusd_epi32x2
|
|
||||||
#define vec_hadd Simd::neon_m128_hadd
|
|
||||||
#define vec_haddx4 Simd::neon_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
|
|
||||||
#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];
|
|
||||||
};
|
|
||||||
|
|
||||||
// A specialization for small inputs
|
|
||||||
template <IndexType InDims, IndexType OutDims>
|
|
||||||
class AffineTransform<InDims, OutDims, std::enable_if_t<(ceil_to_multiple<IndexType>(InDims, MaxSimdWidth) < LargeInputSize)>> {
|
|
||||||
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 < LargeInputSize, "Something went wrong. This specialization (for small inputs) should not have been chosen.");
|
|
||||||
|
|
||||||
// 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)
|
#if defined(USE_SSSE3)
|
||||||
return get_weight_index_scrambled(i);
|
return get_weight_index_scrambled(i);
|
||||||
#else
|
#else
|
||||||
@@ -462,41 +188,37 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
return !stream.fail();
|
return !stream.fail();
|
||||||
}
|
}
|
||||||
// Forward propagation
|
// Forward propagation
|
||||||
const OutputType* propagate(
|
void propagate(const InputType* input, OutputType* output) const {
|
||||||
const InputType* input, OutputType* output) const {
|
|
||||||
|
#if defined(USE_SSSE3)
|
||||||
|
|
||||||
|
if constexpr (OutputDimensions > 1)
|
||||||
|
{
|
||||||
|
|
||||||
#if defined(USE_AVX512)
|
#if defined(USE_AVX512)
|
||||||
using vec_t = __m512i;
|
using vec_t = __m512i;
|
||||||
#define vec_setzero _mm512_setzero_si512
|
#define vec_setzero _mm512_setzero_si512
|
||||||
#define vec_set_32 _mm512_set1_epi32
|
#define vec_set_32 _mm512_set1_epi32
|
||||||
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
|
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
|
||||||
#define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2
|
|
||||||
#define vec_hadd Simd::m512_hadd
|
#define vec_hadd Simd::m512_hadd
|
||||||
#elif defined(USE_AVX2)
|
#elif defined(USE_AVX2)
|
||||||
using vec_t = __m256i;
|
using vec_t = __m256i;
|
||||||
#define vec_setzero _mm256_setzero_si256
|
#define vec_setzero _mm256_setzero_si256
|
||||||
#define vec_set_32 _mm256_set1_epi32
|
#define vec_set_32 _mm256_set1_epi32
|
||||||
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
||||||
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
|
|
||||||
#define vec_hadd Simd::m256_hadd
|
#define vec_hadd Simd::m256_hadd
|
||||||
#elif defined(USE_SSSE3)
|
#elif defined(USE_SSSE3)
|
||||||
using vec_t = __m128i;
|
using vec_t = __m128i;
|
||||||
#define vec_setzero _mm_setzero_si128
|
#define vec_setzero _mm_setzero_si128
|
||||||
#define vec_set_32 _mm_set1_epi32
|
#define vec_set_32 _mm_set1_epi32
|
||||||
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
||||||
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
|
|
||||||
#define vec_hadd Simd::m128_hadd
|
#define vec_hadd Simd::m128_hadd
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined (USE_SSSE3)
|
|
||||||
const auto inputVector = reinterpret_cast<const vec_t*>(input);
|
|
||||||
|
|
||||||
static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);
|
static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);
|
||||||
|
|
||||||
static_assert(OutputDimensions % OutputSimdWidth == 0 || OutputDimensions == 1);
|
static_assert(OutputDimensions % OutputSimdWidth == 0);
|
||||||
|
|
||||||
if constexpr (OutputDimensions % OutputSimdWidth == 0)
|
|
||||||
{
|
|
||||||
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;
|
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;
|
||||||
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
|
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
|
||||||
|
|
||||||
@@ -506,48 +228,71 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
for (IndexType k = 0; k < NumRegs; ++k)
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
acc[k] = biasvec[k];
|
acc[k] = biasvec[k];
|
||||||
|
|
||||||
for (IndexType i = 0; i < NumChunks; i += 2)
|
for (IndexType i = 0; i < NumChunks; ++i)
|
||||||
{
|
{
|
||||||
const vec_t in0 = vec_set_32(input32[i + 0]);
|
const vec_t in0 = vec_set_32(input32[i]);
|
||||||
const vec_t in1 = vec_set_32(input32[i + 1]);
|
const auto col0 =
|
||||||
const auto col0 = reinterpret_cast<const vec_t*>(&weights[(i + 0) * OutputDimensions * 4]);
|
reinterpret_cast<const vec_t*>(&weights[i * OutputDimensions * 4]);
|
||||||
const auto col1 = reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
|
|
||||||
for (IndexType k = 0; k < NumRegs; ++k)
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]);
|
vec_add_dpbusd_32(acc[k], in0, col0[k]);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec_t* outptr = reinterpret_cast<vec_t*>(output);
|
vec_t* outptr = reinterpret_cast<vec_t*>(output);
|
||||||
for (IndexType k = 0; k < NumRegs; ++k)
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
outptr[k] = acc[k];
|
outptr[k] = acc[k];
|
||||||
|
|
||||||
|
#undef vec_setzero
|
||||||
|
#undef vec_set_32
|
||||||
|
#undef vec_add_dpbusd_32
|
||||||
|
#undef vec_hadd
|
||||||
}
|
}
|
||||||
else if constexpr (OutputDimensions == 1)
|
else if constexpr (OutputDimensions == 1)
|
||||||
{
|
{
|
||||||
constexpr IndexType NumChunks = PaddedInputDimensions / SimdWidth;
|
|
||||||
|
// 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();
|
vec_t sum0 = vec_setzero();
|
||||||
const auto row0 = reinterpret_cast<const vec_t*>(&weights[0]);
|
const auto row0 = reinterpret_cast<const vec_t*>(&weights[0]);
|
||||||
|
|
||||||
for (int j = 0; j < (int)NumChunks; ++j)
|
for (int j = 0; j < int(NumChunks); ++j)
|
||||||
{
|
{
|
||||||
const vec_t in = inputVector[j];
|
const vec_t in = inputVector[j];
|
||||||
vec_add_dpbusd_32(sum0, in, row0[j]);
|
vec_add_dpbusd_32(sum0, in, row0[j]);
|
||||||
}
|
}
|
||||||
output[0] = vec_hadd(sum0, biases[0]);
|
output[0] = vec_hadd(sum0, biases[0]);
|
||||||
}
|
|
||||||
|
|
||||||
#undef vec_setzero
|
#undef vec_setzero
|
||||||
#undef vec_set_32
|
#undef vec_set_32
|
||||||
#undef vec_add_dpbusd_32
|
#undef vec_add_dpbusd_32
|
||||||
# undef vec_add_dpbusd_32x2
|
|
||||||
#undef vec_hadd
|
#undef vec_hadd
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
// Use old implementation for the other architectures.
|
// Use old implementation for the other architectures.
|
||||||
affine_transform_non_ssse3<
|
affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
|
||||||
InputDimensions,
|
output, weights, biases, input);
|
||||||
PaddedInputDimensions,
|
|
||||||
OutputDimensions>(output, weights, biases, input);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,10 +21,12 @@
|
|||||||
#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
|
#ifndef NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
|
||||||
#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
|
#define NNUE_LAYERS_AFFINE_TRANSFORM_SPARSE_INPUT_H_INCLUDED
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <type_traits>
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "../../bitboard.h"
|
||||||
#include "../nnue_common.h"
|
#include "../nnue_common.h"
|
||||||
#include "affine_transform.h"
|
#include "affine_transform.h"
|
||||||
#include "simd.h"
|
#include "simd.h"
|
||||||
@@ -34,58 +36,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
namespace Stockfish::Eval::NNUE::Layers {
|
namespace Stockfish::Eval::NNUE::Layers {
|
||||||
#if defined(__GNUC__) // GCC, Clang, ICC
|
|
||||||
|
|
||||||
static inline IndexType lsb_(std::uint32_t b) {
|
#if (USE_SSSE3 | (USE_NEON >= 8))
|
||||||
assert(b);
|
alignas(CacheLineSize) static inline const
|
||||||
return IndexType(__builtin_ctzl(b));
|
std::array<std::array<std::uint16_t, 8>, 256> lookup_indices = []() {
|
||||||
}
|
|
||||||
|
|
||||||
#elif defined(_MSC_VER) // MSVC
|
|
||||||
|
|
||||||
static inline IndexType lsb_(std::uint32_t b) {
|
|
||||||
assert(b);
|
|
||||||
unsigned long idx;
|
|
||||||
_BitScanForward(&idx, b);
|
|
||||||
return (IndexType) idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
#else // Compiler is neither GCC nor MSVC compatible
|
|
||||||
|
|
||||||
#error "Compiler not supported."
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#if defined(USE_SSSE3)
|
|
||||||
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{};
|
std::array<std::array<std::uint16_t, 8>, 256> v{};
|
||||||
for (int i = 0; i < 256; ++i)
|
for (unsigned i = 0; i < 256; ++i)
|
||||||
{
|
{
|
||||||
int j = i;
|
std::uint64_t j = i, k = 0;
|
||||||
int k = 0;
|
|
||||||
while (j)
|
while (j)
|
||||||
{
|
v[i][k++] = pop_lsb(j);
|
||||||
const IndexType lsbIndex = lsb_(std::uint32_t(j));
|
|
||||||
j &= j - 1;
|
|
||||||
v[i][k] = lsbIndex;
|
|
||||||
++k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}();
|
|
||||||
alignas(CacheLineSize) static inline const std::array<unsigned, 256> lookup_count = [](){
|
|
||||||
std::array<unsigned, 256> v;
|
|
||||||
for (int i = 0; i < 256; ++i)
|
|
||||||
{
|
|
||||||
int j = i;
|
|
||||||
int k = 0;
|
|
||||||
while(j)
|
|
||||||
{
|
|
||||||
j &= j - 1;
|
|
||||||
++k;
|
|
||||||
}
|
|
||||||
v[i] = k;
|
|
||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
}();
|
}();
|
||||||
@@ -93,15 +53,40 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
// Find indices of nonzero numbers in an int32_t array
|
// Find indices of nonzero numbers in an int32_t array
|
||||||
template<const IndexType InputDimensions>
|
template<const IndexType InputDimensions>
|
||||||
void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) {
|
void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) {
|
||||||
|
#if defined(USE_SSSE3)
|
||||||
#if defined(USE_AVX512)
|
#if defined(USE_AVX512)
|
||||||
using vec_t = __m512i;
|
using vec_t = __m512i;
|
||||||
#define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
|
#define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
|
||||||
#elif defined(USE_AVX2)
|
#elif defined(USE_AVX2)
|
||||||
using vec_t = __m256i;
|
using vec_t = __m256i;
|
||||||
#define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
|
#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)
|
#elif defined(USE_SSSE3)
|
||||||
using vec_t = __m128i;
|
using vec_t = __m128i;
|
||||||
#define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128())))
|
#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
|
#endif
|
||||||
constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t);
|
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)
|
// Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8)
|
||||||
@@ -112,8 +97,8 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
|
|
||||||
const auto inputVector = reinterpret_cast<const vec_t*>(input);
|
const auto inputVector = reinterpret_cast<const vec_t*>(input);
|
||||||
IndexType count = 0;
|
IndexType count = 0;
|
||||||
__m128i base = _mm_set1_epi16(0);
|
vec128_t base = vec128_zero;
|
||||||
__m128i increment = _mm_set1_epi16(8);
|
const vec128_t increment = vec128_set_16(8);
|
||||||
for (IndexType i = 0; i < NumChunks; ++i)
|
for (IndexType i = 0; i < NumChunks; ++i)
|
||||||
{
|
{
|
||||||
// bitmask of nonzero values in this chunk
|
// bitmask of nonzero values in this chunk
|
||||||
@@ -121,27 +106,32 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
for (IndexType j = 0; j < InputsPerChunk; ++j)
|
for (IndexType j = 0; j < InputsPerChunk; ++j)
|
||||||
{
|
{
|
||||||
const vec_t inputChunk = inputVector[i * InputsPerChunk + j];
|
const vec_t inputChunk = inputVector[i * InputsPerChunk + j];
|
||||||
nnz |= (unsigned)vec_nnz(inputChunk) << (j * InputSimdWidth);
|
nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth);
|
||||||
}
|
}
|
||||||
for (IndexType j = 0; j < OutputsPerChunk; ++j)
|
for (IndexType j = 0; j < OutputsPerChunk; ++j)
|
||||||
{
|
{
|
||||||
const auto lookup = (nnz >> (j * 8)) & 0xFF;
|
const auto lookup = (nnz >> (j * 8)) & 0xFF;
|
||||||
const auto offsets = _mm_loadu_si128(reinterpret_cast<const __m128i*>(&lookup_indices[lookup]));
|
const auto offsets =
|
||||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(out + count), _mm_add_epi16(base, offsets));
|
vec128_load(reinterpret_cast<const vec128_t*>(&lookup_indices[lookup]));
|
||||||
count += lookup_count[lookup];
|
vec128_storeu(reinterpret_cast<vec128_t*>(out + count), vec128_add(base, offsets));
|
||||||
base = _mm_add_epi16(base, increment);
|
count += popcount(lookup);
|
||||||
|
base = vec128_add(base, increment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count_out = count;
|
count_out = count;
|
||||||
}
|
}
|
||||||
#undef vec_nnz
|
#undef vec_nnz
|
||||||
|
#undef vec128_zero
|
||||||
|
#undef vec128_set_16
|
||||||
|
#undef vec128_load
|
||||||
|
#undef vec128_storeu
|
||||||
|
#undef vec128_add
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Sparse input implementation
|
// Sparse input implementation
|
||||||
template<IndexType InDims, IndexType OutDims>
|
template<IndexType InDims, IndexType OutDims>
|
||||||
class AffineTransformSparseInput {
|
class AffineTransformSparseInput {
|
||||||
public:
|
public:
|
||||||
// Input/output type
|
|
||||||
// Input/output type
|
// Input/output type
|
||||||
using InputType = std::uint8_t;
|
using InputType = std::uint8_t;
|
||||||
using OutputType = std::int32_t;
|
using OutputType = std::int32_t;
|
||||||
@@ -150,14 +140,15 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
static constexpr IndexType InputDimensions = InDims;
|
static constexpr IndexType InputDimensions = InDims;
|
||||||
static constexpr IndexType OutputDimensions = OutDims;
|
static constexpr IndexType OutputDimensions = OutDims;
|
||||||
|
|
||||||
static_assert(OutputDimensions % 16 == 0, "Only implemented for OutputDimensions divisible by 16.");
|
static_assert(OutputDimensions % 16 == 0,
|
||||||
|
"Only implemented for OutputDimensions divisible by 16.");
|
||||||
|
|
||||||
static constexpr IndexType PaddedInputDimensions =
|
static constexpr IndexType PaddedInputDimensions =
|
||||||
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
|
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
|
||||||
static constexpr IndexType PaddedOutputDimensions =
|
static constexpr IndexType PaddedOutputDimensions =
|
||||||
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
|
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
|
||||||
|
|
||||||
#if defined (USE_SSSE3)
|
#if (USE_SSSE3 | (USE_NEON >= 8))
|
||||||
static constexpr IndexType ChunkSize = 4;
|
static constexpr IndexType ChunkSize = 4;
|
||||||
#else
|
#else
|
||||||
static constexpr IndexType ChunkSize = 1;
|
static constexpr IndexType ChunkSize = 1;
|
||||||
@@ -174,17 +165,13 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
return hashValue;
|
return hashValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
static IndexType get_weight_index_scrambled(IndexType i)
|
static constexpr IndexType get_weight_index_scrambled(IndexType i) {
|
||||||
{
|
return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize
|
||||||
return
|
+ i / PaddedInputDimensions * ChunkSize + i % ChunkSize;
|
||||||
(i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize +
|
|
||||||
i / PaddedInputDimensions * ChunkSize +
|
|
||||||
i % ChunkSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static IndexType get_weight_index(IndexType i)
|
static constexpr IndexType get_weight_index(IndexType i) {
|
||||||
{
|
#if (USE_SSSE3 | (USE_NEON >= 8))
|
||||||
#if defined (USE_SSSE3)
|
|
||||||
return get_weight_index_scrambled(i);
|
return get_weight_index_scrambled(i);
|
||||||
#else
|
#else
|
||||||
return i;
|
return i;
|
||||||
@@ -210,27 +197,36 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
return !stream.fail();
|
return !stream.fail();
|
||||||
}
|
}
|
||||||
// Forward propagation
|
// Forward propagation
|
||||||
const OutputType* propagate(
|
void propagate(const InputType* input, OutputType* output) const {
|
||||||
const InputType* input, OutputType* output) const {
|
|
||||||
|
|
||||||
#if defined (USE_SSSE3)
|
#if (USE_SSSE3 | (USE_NEON >= 8))
|
||||||
#if defined(USE_AVX512)
|
#if defined(USE_AVX512)
|
||||||
using vec_t = __m512i;
|
using invec_t = __m512i;
|
||||||
#define vec_setzero _mm512_setzero_si512
|
using outvec_t = __m512i;
|
||||||
#define vec_set_32 _mm512_set1_epi32
|
#define vec_set_32 _mm512_set1_epi32
|
||||||
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
|
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
|
||||||
#elif defined(USE_AVX2)
|
#elif defined(USE_AVX2)
|
||||||
using vec_t = __m256i;
|
using invec_t = __m256i;
|
||||||
#define vec_setzero _mm256_setzero_si256
|
using outvec_t = __m256i;
|
||||||
#define vec_set_32 _mm256_set1_epi32
|
#define vec_set_32 _mm256_set1_epi32
|
||||||
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
|
||||||
#elif defined(USE_SSSE3)
|
#elif defined(USE_SSSE3)
|
||||||
using vec_t = __m128i;
|
using invec_t = __m128i;
|
||||||
#define vec_setzero _mm_setzero_si128
|
using outvec_t = __m128i;
|
||||||
#define vec_set_32 _mm_set1_epi32
|
#define vec_set_32 _mm_set1_epi32
|
||||||
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
|
||||||
|
#elif defined(USE_NEON_DOTPROD)
|
||||||
|
using invec_t = int8x16_t;
|
||||||
|
using outvec_t = int32x4_t;
|
||||||
|
#define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
|
||||||
|
#define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32
|
||||||
|
#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
|
#endif
|
||||||
static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);
|
static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType);
|
||||||
|
|
||||||
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;
|
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;
|
||||||
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
|
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
|
||||||
@@ -239,38 +235,34 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
|
|
||||||
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
|
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
|
||||||
|
|
||||||
// Find indices of nonzero 32bit blocks
|
// Find indices of nonzero 32-bit blocks
|
||||||
find_nnz<NumChunks>(input32, nnz, count);
|
find_nnz<NumChunks>(input32, nnz, count);
|
||||||
|
|
||||||
const vec_t* biasvec = reinterpret_cast<const vec_t*>(biases);
|
const outvec_t* biasvec = reinterpret_cast<const outvec_t*>(biases);
|
||||||
vec_t acc[NumRegs];
|
outvec_t acc[NumRegs];
|
||||||
for (IndexType k = 0; k < NumRegs; ++k)
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
acc[k] = biasvec[k];
|
acc[k] = biasvec[k];
|
||||||
|
|
||||||
for (IndexType j = 0; j < count; ++j)
|
for (IndexType j = 0; j < count; ++j)
|
||||||
{
|
{
|
||||||
const auto i = nnz[j];
|
const auto i = nnz[j];
|
||||||
const vec_t in = vec_set_32(input32[i]);
|
const invec_t in = vec_set_32(input32[i]);
|
||||||
const auto col = reinterpret_cast<const vec_t*>(&weights[i * OutputDimensions * ChunkSize]);
|
const auto col =
|
||||||
|
reinterpret_cast<const invec_t*>(&weights[i * OutputDimensions * ChunkSize]);
|
||||||
for (IndexType k = 0; k < NumRegs; ++k)
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
vec_add_dpbusd_32(acc[k], in, col[k]);
|
vec_add_dpbusd_32(acc[k], in, col[k]);
|
||||||
}
|
}
|
||||||
|
|
||||||
vec_t* outptr = reinterpret_cast<vec_t*>(output);
|
outvec_t* outptr = reinterpret_cast<outvec_t*>(output);
|
||||||
for (IndexType k = 0; k < NumRegs; ++k)
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
outptr[k] = acc[k];
|
outptr[k] = acc[k];
|
||||||
# undef vec_setzero
|
|
||||||
#undef vec_set_32
|
#undef vec_set_32
|
||||||
#undef vec_add_dpbusd_32
|
#undef vec_add_dpbusd_32
|
||||||
#else
|
#else
|
||||||
// Use dense implementation for the other architectures.
|
// Use dense implementation for the other architectures.
|
||||||
affine_transform_non_ssse3<
|
affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
|
||||||
InputDimensions,
|
output, weights, biases, input);
|
||||||
PaddedInputDimensions,
|
|
||||||
OutputDimensions>(output, weights, biases, input);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,6 +21,10 @@
|
|||||||
#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
#ifndef NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
||||||
#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
#define NNUE_LAYERS_CLIPPED_RELU_H_INCLUDED
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iosfwd>
|
||||||
|
|
||||||
#include "../nnue_common.h"
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
namespace Stockfish::Eval::NNUE::Layers {
|
namespace Stockfish::Eval::NNUE::Layers {
|
||||||
@@ -49,54 +53,56 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read network parameters
|
// Read network parameters
|
||||||
bool read_parameters(std::istream&) {
|
bool read_parameters(std::istream&) { return true; }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write network parameters
|
// Write network parameters
|
||||||
bool write_parameters(std::ostream&) const {
|
bool write_parameters(std::ostream&) const { return true; }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward propagation
|
// Forward propagation
|
||||||
const OutputType* propagate(
|
void propagate(const InputType* input, OutputType* output) const {
|
||||||
const InputType* input, OutputType* output) const {
|
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
#if defined(USE_AVX2)
|
||||||
if constexpr (InputDimensions % SimdWidth == 0) {
|
if constexpr (InputDimensions % SimdWidth == 0)
|
||||||
|
{
|
||||||
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
|
||||||
const __m256i Zero = _mm256_setzero_si256();
|
const __m256i Zero = _mm256_setzero_si256();
|
||||||
const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
|
const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
|
||||||
const auto in = reinterpret_cast<const __m256i*>(input);
|
const auto in = reinterpret_cast<const __m256i*>(input);
|
||||||
const auto out = reinterpret_cast<__m256i*>(output);
|
const auto out = reinterpret_cast<__m256i*>(output);
|
||||||
for (IndexType i = 0; i < NumChunks; ++i) {
|
for (IndexType i = 0; i < NumChunks; ++i)
|
||||||
const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
|
{
|
||||||
_mm256_load_si256(&in[i * 4 + 0]),
|
const __m256i words0 =
|
||||||
_mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits);
|
_mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 0]),
|
||||||
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
|
_mm256_load_si256(&in[i * 4 + 1])),
|
||||||
_mm256_load_si256(&in[i * 4 + 2]),
|
WeightScaleBits);
|
||||||
_mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits);
|
const __m256i words1 =
|
||||||
_mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
|
_mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 2]),
|
||||||
_mm256_packs_epi16(words0, words1), Zero), Offsets));
|
_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 {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
||||||
const __m128i Zero = _mm_setzero_si128();
|
const __m128i Zero = _mm_setzero_si128();
|
||||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||||
const auto out = reinterpret_cast<__m128i*>(output);
|
const auto out = reinterpret_cast<__m128i*>(output);
|
||||||
for (IndexType i = 0; i < NumChunks; ++i) {
|
for (IndexType i = 0; i < NumChunks; ++i)
|
||||||
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
|
{
|
||||||
_mm_load_si128(&in[i * 4 + 0]),
|
const __m128i words0 = _mm_srai_epi16(
|
||||||
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
|
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
|
||||||
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
|
WeightScaleBits);
|
||||||
_mm_load_si128(&in[i * 4 + 2]),
|
const __m128i words1 = _mm_srai_epi16(
|
||||||
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
|
_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);
|
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
||||||
_mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero));
|
_mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
constexpr IndexType Start =
|
constexpr IndexType Start = InputDimensions % SimdWidth == 0
|
||||||
InputDimensions % SimdWidth == 0
|
|
||||||
? InputDimensions / SimdWidth * SimdWidth
|
? InputDimensions / SimdWidth * SimdWidth
|
||||||
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
|
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
|
||||||
|
|
||||||
@@ -111,13 +117,14 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
|
|
||||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||||
const auto out = reinterpret_cast<__m128i*>(output);
|
const auto out = reinterpret_cast<__m128i*>(output);
|
||||||
for (IndexType i = 0; i < NumChunks; ++i) {
|
for (IndexType i = 0; i < NumChunks; ++i)
|
||||||
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
|
{
|
||||||
_mm_load_si128(&in[i * 4 + 0]),
|
const __m128i words0 = _mm_srai_epi16(
|
||||||
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
|
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
|
||||||
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
|
WeightScaleBits);
|
||||||
_mm_load_si128(&in[i * 4 + 2]),
|
const __m128i words1 = _mm_srai_epi16(
|
||||||
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
|
_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);
|
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
||||||
_mm_store_si128(&out[i],
|
_mm_store_si128(&out[i],
|
||||||
|
|
||||||
@@ -131,30 +138,13 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
}
|
}
|
||||||
constexpr IndexType Start = NumChunks * SimdWidth;
|
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)
|
#elif defined(USE_NEON)
|
||||||
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
|
||||||
const int8x8_t Zero = {0};
|
const int8x8_t Zero = {0};
|
||||||
const auto in = reinterpret_cast<const int32x4_t*>(input);
|
const auto in = reinterpret_cast<const int32x4_t*>(input);
|
||||||
const auto out = reinterpret_cast<int8x8_t*>(output);
|
const auto out = reinterpret_cast<int8x8_t*>(output);
|
||||||
for (IndexType i = 0; i < NumChunks; ++i) {
|
for (IndexType i = 0; i < NumChunks; ++i)
|
||||||
|
{
|
||||||
int16x8_t shifted;
|
int16x8_t shifted;
|
||||||
const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
|
const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
|
||||||
pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits);
|
pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits);
|
||||||
@@ -166,12 +156,10 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
constexpr IndexType Start = 0;
|
constexpr IndexType Start = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (IndexType i = Start; i < InputDimensions; ++i) {
|
for (IndexType i = Start; i < InputDimensions; ++i)
|
||||||
output[i] = static_cast<OutputType>(
|
{
|
||||||
std::max(0, std::min(127, input[i] >> WeightScaleBits)));
|
output[i] = static_cast<OutputType>(std::clamp(input[i] >> WeightScaleBits, 0, 127));
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+18
-254
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -31,28 +31,10 @@
|
|||||||
#elif defined(USE_SSE2)
|
#elif defined(USE_SSE2)
|
||||||
#include <emmintrin.h>
|
#include <emmintrin.h>
|
||||||
|
|
||||||
#elif defined(USE_MMX)
|
|
||||||
# include <mmintrin.h>
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
#elif defined(USE_NEON)
|
||||||
#include <arm_neon.h>
|
#include <arm_neon.h>
|
||||||
#endif
|
#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 {
|
namespace Stockfish::Simd {
|
||||||
|
|
||||||
#if defined(USE_AVX512)
|
#if defined(USE_AVX512)
|
||||||
@@ -76,8 +58,8 @@ namespace Stockfish::Simd {
|
|||||||
reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3])
|
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(
|
[[maybe_unused]] static __m512i
|
||||||
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
|
m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
|
||||||
|
|
||||||
__m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1);
|
__m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1);
|
||||||
__m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1);
|
__m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1);
|
||||||
@@ -94,91 +76,14 @@ namespace Stockfish::Simd {
|
|||||||
return _mm512_add_epi32(sum0123a, sum0123b);
|
return _mm512_add_epi32(sum0123a, sum0123b);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] static __m128i m512_haddx4(
|
[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) {
|
||||||
__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_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);
|
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
|
#else
|
||||||
__m512i product0 = _mm512_maddubs_epi16(a, b);
|
__m512i product0 = _mm512_maddubs_epi16(a, b);
|
||||||
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
|
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
|
||||||
acc = _mm512_add_epi32(acc, product0);
|
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(
|
|
||||||
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
|
|
||||||
"vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t"
|
|
||||||
"vpaddd %[tmp0], %[tmp1], %[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_madd_epi16(product0, _mm512_set1_epi16(1));
|
|
||||||
product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1));
|
|
||||||
acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1));
|
|
||||||
# endif
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,89 +98,14 @@ namespace Stockfish::Simd {
|
|||||||
return _mm_cvtsi128_si32(sum128) + bias;
|
return _mm_cvtsi128_si32(sum128) + bias;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] static __m128i m256_haddx4(
|
[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) {
|
||||||
__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_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);
|
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
|
#else
|
||||||
__m256i product0 = _mm256_maddubs_epi16(a, b);
|
__m256i product0 = _mm256_maddubs_epi16(a, b);
|
||||||
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
|
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
|
||||||
acc = _mm256_add_epi32(acc, product0);
|
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(
|
|
||||||
"vpmaddwd %[tmp0], %[ones], %[tmp0]\n\t"
|
|
||||||
"vpmaddwd %[tmp1], %[ones], %[tmp1]\n\t"
|
|
||||||
"vpaddd %[tmp0], %[tmp1], %[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_madd_epi16(product0, _mm256_set1_epi16(1));
|
|
||||||
product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1));
|
|
||||||
acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1));
|
|
||||||
# endif
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,74 +119,22 @@ namespace Stockfish::Simd {
|
|||||||
return _mm_cvtsi128_si32(sum) + bias;
|
return _mm_cvtsi128_si32(sum) + bias;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[maybe_unused]] static __m128i m128_haddx4(
|
[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
|
||||||
__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);
|
__m128i product0 = _mm_maddubs_epi16(a, b);
|
||||||
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
|
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
|
||||||
acc = _mm_add_epi32(acc, product0);
|
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(
|
|
||||||
"pmaddwd %[ones], %[tmp0]\n\t"
|
|
||||||
"pmaddwd %[ones], %[tmp1]\n\t"
|
|
||||||
"paddd %[tmp1], %[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_madd_epi16(product0, _mm_set1_epi16(1));
|
|
||||||
product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1));
|
|
||||||
acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1));
|
|
||||||
# endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_NEON_DOTPROD)
|
#if defined(USE_NEON_DOTPROD)
|
||||||
|
|
||||||
[[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2(
|
[[maybe_unused]] static void
|
||||||
int32x4_t& acc,
|
dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
|
||||||
int8x16_t a0, int8x16_t b0,
|
|
||||||
int8x16_t a1, int8x16_t b1) {
|
|
||||||
|
|
||||||
acc = vdotq_s32(acc, a0, b0);
|
acc = vdotq_s32(acc, a, b);
|
||||||
acc = vdotq_s32(acc, a1, b1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_NEON)
|
#if defined(USE_NEON)
|
||||||
@@ -373,31 +151,17 @@ namespace Stockfish::Simd {
|
|||||||
return neon_m128_reduce_add_epi32(sum) + 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
|
||||||
|
|
||||||
|
#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
|
#endif // STOCKFISH_SIMD_H_INCLUDED
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,6 +21,10 @@
|
|||||||
#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
|
#ifndef NNUE_LAYERS_SQR_CLIPPED_RELU_H_INCLUDED
|
||||||
#define 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"
|
#include "../nnue_common.h"
|
||||||
|
|
||||||
namespace Stockfish::Eval::NNUE::Layers {
|
namespace Stockfish::Eval::NNUE::Layers {
|
||||||
@@ -49,54 +53,34 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Read network parameters
|
// Read network parameters
|
||||||
bool read_parameters(std::istream&) {
|
bool read_parameters(std::istream&) { return true; }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write network parameters
|
// Write network parameters
|
||||||
bool write_parameters(std::ostream&) const {
|
bool write_parameters(std::ostream&) const { return true; }
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward propagation
|
// Forward propagation
|
||||||
const OutputType* propagate(
|
void propagate(const InputType* input, OutputType* output) const {
|
||||||
const InputType* input, OutputType* output) const {
|
|
||||||
|
|
||||||
#if defined(USE_SSE2)
|
#if defined(USE_SSE2)
|
||||||
constexpr IndexType NumChunks = InputDimensions / 16;
|
constexpr IndexType NumChunks = InputDimensions / 16;
|
||||||
|
|
||||||
#ifdef USE_SSE41
|
|
||||||
const __m128i Zero = _mm_setzero_si128();
|
|
||||||
#else
|
|
||||||
const __m128i k0x80s = _mm_set1_epi8(-128);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static_assert(WeightScaleBits == 6);
|
static_assert(WeightScaleBits == 6);
|
||||||
const auto in = reinterpret_cast<const __m128i*>(input);
|
const auto in = reinterpret_cast<const __m128i*>(input);
|
||||||
const auto out = reinterpret_cast<__m128i*>(output);
|
const auto out = reinterpret_cast<__m128i*>(output);
|
||||||
for (IndexType i = 0; i < NumChunks; ++i) {
|
for (IndexType i = 0; i < NumChunks; ++i)
|
||||||
__m128i words0 = _mm_packs_epi32(
|
{
|
||||||
_mm_load_si128(&in[i * 4 + 0]),
|
__m128i words0 =
|
||||||
_mm_load_si128(&in[i * 4 + 1]));
|
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1]));
|
||||||
__m128i words1 = _mm_packs_epi32(
|
__m128i words1 =
|
||||||
_mm_load_si128(&in[i * 4 + 2]),
|
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3]));
|
||||||
_mm_load_si128(&in[i * 4 + 3]));
|
|
||||||
|
|
||||||
// Not sure if
|
// 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);
|
words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3);
|
||||||
words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3);
|
words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3);
|
||||||
|
|
||||||
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
|
_mm_store_si128(&out[i], _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 * 16;
|
constexpr IndexType Start = NumChunks * 16;
|
||||||
|
|
||||||
@@ -104,14 +88,13 @@ namespace Stockfish::Eval::NNUE::Layers {
|
|||||||
constexpr IndexType Start = 0;
|
constexpr IndexType Start = 0;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (IndexType i = Start; i < InputDimensions; ++i) {
|
for (IndexType i = Start; i < InputDimensions; ++i)
|
||||||
|
{
|
||||||
output[i] = static_cast<OutputType>(
|
output[i] = static_cast<OutputType>(
|
||||||
// really should be /127 but we need to make it fast
|
// Really should be /127 but we need to make it fast so we right-shift
|
||||||
// needs to be accounted for in the trainer
|
// by an extra 7 bits instead. Needs to be accounted for in the trainer.
|
||||||
std::max(0ll, std::min(127ll, (((long long)input[i] * input[i]) >> (2 * WeightScaleBits)) / 128)));
|
std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,444 @@
|
|||||||
|
/*
|
||||||
|
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 "../cluster.h"
|
||||||
|
#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,
|
||||||
|
bool adjusted,
|
||||||
|
int* complexity,
|
||||||
|
bool psqtOnly) 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<Arch::TransformedFeatureDimensions, nullptr>::BufferSize
|
||||||
|
+ alignment / sizeof(TransformedFeatureType)];
|
||||||
|
|
||||||
|
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
||||||
|
#else
|
||||||
|
alignas(alignment) TransformedFeatureType transformedFeatures
|
||||||
|
[FeatureTransformer<Arch::TransformedFeatureDimensions, nullptr>::BufferSize];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ASSERT_ALIGNED(transformedFeatures, alignment);
|
||||||
|
|
||||||
|
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
|
||||||
|
const auto psqt = featureTransformer->transform(pos, transformedFeatures, bucket, psqtOnly);
|
||||||
|
const auto positional = !psqtOnly ? (network[bucket]->propagate(transformedFeatures)) : 0;
|
||||||
|
|
||||||
|
if (complexity)
|
||||||
|
*complexity = !psqtOnly ? std::abs(psqt - positional) / OutputScale : 0;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Cluster::is_root())
|
||||||
|
sync_cout << "info string NNUE evaluation using " << evalfilePath << sync_endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Arch, typename Transformer>
|
||||||
|
void Network<Arch, Transformer>::hint_common_access(const Position& pos, bool psqtOnl) const {
|
||||||
|
featureTransformer->hint_common_access(pos, psqtOnl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Arch, typename Transformer>
|
||||||
|
NnueEvalTrace Network<Arch, Transformer>::trace_evaluate(const Position& pos) 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<Arch::TransformedFeatureDimensions, nullptr>::BufferSize
|
||||||
|
+ alignment / sizeof(TransformedFeatureType)];
|
||||||
|
|
||||||
|
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
|
||||||
|
#else
|
||||||
|
alignas(alignment) TransformedFeatureType transformedFeatures
|
||||||
|
[FeatureTransformer<Arch::TransformedFeatureDimensions, 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, transformedFeatures, bucket, false);
|
||||||
|
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
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
|
||||||
|
|
||||||
|
enum class EmbeddedNNUEType {
|
||||||
|
BIG,
|
||||||
|
SMALL,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Arch, typename Transformer>
|
||||||
|
class Network {
|
||||||
|
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,
|
||||||
|
bool adjusted = false,
|
||||||
|
int* complexity = nullptr,
|
||||||
|
bool psqtOnly = false) const;
|
||||||
|
|
||||||
|
|
||||||
|
void hint_common_access(const Position& pos, bool psqtOnl) const;
|
||||||
|
|
||||||
|
void verify(std::string evalfilePath) const;
|
||||||
|
NnueEvalTrace trace_evaluate(const Position& pos) 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();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,15 +21,20 @@
|
|||||||
#ifndef NNUE_ACCUMULATOR_H_INCLUDED
|
#ifndef NNUE_ACCUMULATOR_H_INCLUDED
|
||||||
#define NNUE_ACCUMULATOR_H_INCLUDED
|
#define NNUE_ACCUMULATOR_H_INCLUDED
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "nnue_architecture.h"
|
#include "nnue_architecture.h"
|
||||||
|
#include "nnue_common.h"
|
||||||
|
|
||||||
namespace Stockfish::Eval::NNUE {
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
|
||||||
// Class that holds the result of affine transformation of input features
|
// Class that holds the result of affine transformation of input features
|
||||||
|
template<IndexType Size>
|
||||||
struct alignas(CacheLineSize) Accumulator {
|
struct alignas(CacheLineSize) Accumulator {
|
||||||
std::int16_t accumulation[2][TransformedFeatureDimensions];
|
std::int16_t accumulation[2][Size];
|
||||||
std::int32_t psqtAccumulation[2][PSQTBuckets];
|
std::int32_t psqtAccumulation[2][PSQTBuckets];
|
||||||
bool computed[2];
|
bool computed[2];
|
||||||
|
bool computedPSQT[2];
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Stockfish::Eval::NNUE
|
} // namespace Stockfish::Eval::NNUE
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,18 +21,16 @@
|
|||||||
#ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
#ifndef NNUE_ARCHITECTURE_H_INCLUDED
|
||||||
#define NNUE_ARCHITECTURE_H_INCLUDED
|
#define NNUE_ARCHITECTURE_H_INCLUDED
|
||||||
|
|
||||||
#include <memory>
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
#include "nnue_common.h"
|
#include <iosfwd>
|
||||||
|
|
||||||
#include "features/half_ka_v2_hm.h"
|
#include "features/half_ka_v2_hm.h"
|
||||||
|
|
||||||
#include "layers/affine_transform_sparse_input.h"
|
|
||||||
#include "layers/affine_transform.h"
|
#include "layers/affine_transform.h"
|
||||||
|
#include "layers/affine_transform_sparse_input.h"
|
||||||
#include "layers/clipped_relu.h"
|
#include "layers/clipped_relu.h"
|
||||||
#include "layers/sqr_clipped_relu.h"
|
#include "layers/sqr_clipped_relu.h"
|
||||||
|
#include "nnue_common.h"
|
||||||
#include "../misc.h"
|
|
||||||
|
|
||||||
namespace Stockfish::Eval::NNUE {
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
|
||||||
@@ -40,14 +38,22 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
using FeatureSet = Features::HalfKAv2_hm;
|
using FeatureSet = Features::HalfKAv2_hm;
|
||||||
|
|
||||||
// Number of input feature dimensions after conversion
|
// Number of input feature dimensions after conversion
|
||||||
constexpr IndexType TransformedFeatureDimensions = 1536;
|
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 PSQTBuckets = 8;
|
||||||
constexpr IndexType LayerStacks = 8;
|
constexpr IndexType LayerStacks = 8;
|
||||||
|
|
||||||
struct Network
|
template<IndexType L1, int L2, int L3>
|
||||||
{
|
struct NetworkArchitecture {
|
||||||
static constexpr int FC_0_OUTPUTS = 15;
|
static constexpr IndexType TransformedFeatureDimensions = L1;
|
||||||
static constexpr int FC_1_OUTPUTS = 32;
|
static constexpr int FC_0_OUTPUTS = L2;
|
||||||
|
static constexpr int FC_1_OUTPUTS = L3;
|
||||||
|
|
||||||
Layers::AffineTransformSparseInput<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
|
Layers::AffineTransformSparseInput<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
|
||||||
Layers::SqrClippedReLU<FC_0_OUTPUTS + 1> ac_sqr_0;
|
Layers::SqrClippedReLU<FC_0_OUTPUTS + 1> ac_sqr_0;
|
||||||
@@ -73,37 +79,29 @@ struct Network
|
|||||||
|
|
||||||
// Read network parameters
|
// Read network parameters
|
||||||
bool read_parameters(std::istream& stream) {
|
bool read_parameters(std::istream& stream) {
|
||||||
return fc_0.read_parameters(stream)
|
return fc_0.read_parameters(stream) && ac_0.read_parameters(stream)
|
||||||
&& ac_0.read_parameters(stream)
|
&& fc_1.read_parameters(stream) && ac_1.read_parameters(stream)
|
||||||
&& fc_1.read_parameters(stream)
|
|
||||||
&& ac_1.read_parameters(stream)
|
|
||||||
&& fc_2.read_parameters(stream);
|
&& fc_2.read_parameters(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write network parameters
|
// Write network parameters
|
||||||
bool write_parameters(std::ostream& stream) const {
|
bool write_parameters(std::ostream& stream) const {
|
||||||
return fc_0.write_parameters(stream)
|
return fc_0.write_parameters(stream) && ac_0.write_parameters(stream)
|
||||||
&& ac_0.write_parameters(stream)
|
&& fc_1.write_parameters(stream) && ac_1.write_parameters(stream)
|
||||||
&& fc_1.write_parameters(stream)
|
|
||||||
&& ac_1.write_parameters(stream)
|
|
||||||
&& fc_2.write_parameters(stream);
|
&& fc_2.write_parameters(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::int32_t propagate(const TransformedFeatureType* transformedFeatures)
|
std::int32_t propagate(const TransformedFeatureType* transformedFeatures) {
|
||||||
{
|
struct alignas(CacheLineSize) Buffer {
|
||||||
struct alignas(CacheLineSize) Buffer
|
alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out;
|
||||||
{
|
alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType
|
||||||
alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out;
|
ac_sqr_0_out[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2, 32)];
|
||||||
alignas(CacheLineSize) 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) decltype(ac_0)::OutputBuffer ac_0_out;
|
alignas(CacheLineSize) typename decltype(fc_1)::OutputBuffer fc_1_out;
|
||||||
alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out;
|
alignas(CacheLineSize) typename decltype(ac_1)::OutputBuffer ac_1_out;
|
||||||
alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out;
|
alignas(CacheLineSize) typename decltype(fc_2)::OutputBuffer fc_2_out;
|
||||||
alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out;
|
|
||||||
|
|
||||||
Buffer()
|
Buffer() { std::memset(this, 0, sizeof(*this)); }
|
||||||
{
|
|
||||||
std::memset(this, 0, sizeof(*this));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#if defined(__clang__) && (__APPLE__)
|
#if defined(__clang__) && (__APPLE__)
|
||||||
@@ -118,14 +116,16 @@ struct Network
|
|||||||
fc_0.propagate(transformedFeatures, buffer.fc_0_out);
|
fc_0.propagate(transformedFeatures, buffer.fc_0_out);
|
||||||
ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_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);
|
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(decltype(ac_0)::OutputType));
|
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);
|
fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out);
|
||||||
ac_1.propagate(buffer.fc_1_out, buffer.ac_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_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
|
// buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in
|
||||||
// but we want 1.0 to be equal to 600*OutputScale
|
// 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 fwdOut =
|
||||||
|
(buffer.fc_0_out[FC_0_OUTPUTS]) * (600 * OutputScale) / (127 * (1 << WeightScaleBits));
|
||||||
std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
|
std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
|
||||||
|
|
||||||
return outputValue;
|
return outputValue;
|
||||||
|
|||||||
+75
-32
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,10 +21,14 @@
|
|||||||
#ifndef NNUE_COMMON_H_INCLUDED
|
#ifndef NNUE_COMMON_H_INCLUDED
|
||||||
#define NNUE_COMMON_H_INCLUDED
|
#define NNUE_COMMON_H_INCLUDED
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
#include "../misc.h" // for IsLittleEndian
|
#include "../misc.h"
|
||||||
|
|
||||||
#if defined(USE_AVX2)
|
#if defined(USE_AVX2)
|
||||||
#include <immintrin.h>
|
#include <immintrin.h>
|
||||||
@@ -38,9 +42,6 @@
|
|||||||
#elif defined(USE_SSE2)
|
#elif defined(USE_SSE2)
|
||||||
#include <emmintrin.h>
|
#include <emmintrin.h>
|
||||||
|
|
||||||
#elif defined(USE_MMX)
|
|
||||||
#include <mmintrin.h>
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
#elif defined(USE_NEON)
|
||||||
#include <arm_neon.h>
|
#include <arm_neon.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -67,9 +68,6 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
#elif defined(USE_SSE2)
|
#elif defined(USE_SSE2)
|
||||||
constexpr std::size_t SimdWidth = 16;
|
constexpr std::size_t SimdWidth = 16;
|
||||||
|
|
||||||
#elif defined(USE_MMX)
|
|
||||||
constexpr std::size_t SimdWidth = 8;
|
|
||||||
|
|
||||||
#elif defined(USE_NEON)
|
#elif defined(USE_NEON)
|
||||||
constexpr std::size_t SimdWidth = 16;
|
constexpr std::size_t SimdWidth = 16;
|
||||||
#endif
|
#endif
|
||||||
@@ -86,7 +84,8 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
return (n + base - 1) / base * base;
|
return (n + base - 1) / base * base;
|
||||||
}
|
}
|
||||||
|
|
||||||
// read_little_endian() is our utility to read an integer (signed or unsigned, any size)
|
|
||||||
|
// 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
|
// 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.
|
// necessary to return a result with the byte ordering of the compiling machine.
|
||||||
template<typename IntType>
|
template<typename IntType>
|
||||||
@@ -98,7 +97,7 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::uint8_t u[sizeof(IntType)];
|
std::uint8_t u[sizeof(IntType)];
|
||||||
typename std::make_unsigned<IntType>::type v = 0;
|
std::make_unsigned_t<IntType> v = 0;
|
||||||
|
|
||||||
stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
|
stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
|
||||||
for (std::size_t i = 0; i < sizeof(IntType); ++i)
|
for (std::size_t i = 0; i < sizeof(IntType); ++i)
|
||||||
@@ -110,9 +109,10 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// write_little_endian() is our utility to write an integer (signed or unsigned, any size)
|
|
||||||
|
// 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
|
// 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
|
// necessary to always write in little-endian order, independently of the byte
|
||||||
// ordering of the compiling machine.
|
// ordering of the compiling machine.
|
||||||
template<typename IntType>
|
template<typename IntType>
|
||||||
inline void write_little_endian(std::ostream& stream, IntType value) {
|
inline void write_little_endian(std::ostream& stream, IntType value) {
|
||||||
@@ -122,7 +122,7 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
std::uint8_t u[sizeof(IntType)];
|
std::uint8_t u[sizeof(IntType)];
|
||||||
typename std::make_unsigned<IntType>::type v = value;
|
std::make_unsigned_t<IntType> v = value;
|
||||||
|
|
||||||
std::size_t i = 0;
|
std::size_t i = 0;
|
||||||
// if constexpr to silence the warning about shift by 8
|
// if constexpr to silence the warning about shift by 8
|
||||||
@@ -130,18 +130,19 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
{
|
{
|
||||||
for (; i + 1 < sizeof(IntType); ++i)
|
for (; i + 1 < sizeof(IntType); ++i)
|
||||||
{
|
{
|
||||||
u[i] = (std::uint8_t)v;
|
u[i] = std::uint8_t(v);
|
||||||
v >>= 8;
|
v >>= 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u[i] = (std::uint8_t)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.
|
// 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>
|
template<typename IntType>
|
||||||
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
|
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
|
||||||
if (IsLittleEndian)
|
if (IsLittleEndian)
|
||||||
@@ -151,7 +152,8 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
out[i] = read_little_endian<IntType>(stream);
|
out[i] = read_little_endian<IntType>(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write_little_endian(s, values, N) : write integers in bulk to a little indian stream.
|
|
||||||
|
// Write integers in bulk to a little-endian stream.
|
||||||
// This takes N integers from array values and writes them on stream s.
|
// This takes N integers from array values and writes them on stream s.
|
||||||
template<typename IntType>
|
template<typename IntType>
|
||||||
inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
|
inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
|
||||||
@@ -162,77 +164,118 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
write_little_endian<IntType>(stream, values[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>
|
template<typename IntType>
|
||||||
inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) {
|
inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) {
|
||||||
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
|
|
||||||
|
// Check the presence of our LEB128 magic string
|
||||||
char leb128MagicString[Leb128MagicStringSize];
|
char leb128MagicString[Leb128MagicStringSize];
|
||||||
stream.read(leb128MagicString, Leb128MagicStringSize);
|
stream.read(leb128MagicString, Leb128MagicStringSize);
|
||||||
assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);
|
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;
|
const std::uint32_t BUF_SIZE = 4096;
|
||||||
std::uint8_t buf[BUF_SIZE];
|
std::uint8_t buf[BUF_SIZE];
|
||||||
|
|
||||||
auto bytes_left = read_little_endian<std::uint32_t>(stream);
|
auto bytes_left = read_little_endian<std::uint32_t>(stream);
|
||||||
|
|
||||||
std::uint32_t buf_pos = BUF_SIZE;
|
std::uint32_t buf_pos = BUF_SIZE;
|
||||||
for (std::size_t i = 0; i < count; ++i) {
|
for (std::size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
IntType result = 0;
|
IntType result = 0;
|
||||||
size_t shift = 0;
|
size_t shift = 0;
|
||||||
do {
|
do
|
||||||
if (buf_pos == BUF_SIZE) {
|
{
|
||||||
|
if (buf_pos == BUF_SIZE)
|
||||||
|
{
|
||||||
stream.read(reinterpret_cast<char*>(buf), std::min(bytes_left, BUF_SIZE));
|
stream.read(reinterpret_cast<char*>(buf), std::min(bytes_left, BUF_SIZE));
|
||||||
buf_pos = 0;
|
buf_pos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::uint8_t byte = buf[buf_pos++];
|
std::uint8_t byte = buf[buf_pos++];
|
||||||
--bytes_left;
|
--bytes_left;
|
||||||
result |= (byte & 0x7f) << shift;
|
result |= (byte & 0x7f) << shift;
|
||||||
shift += 7;
|
shift += 7;
|
||||||
if ((byte & 0x80) == 0) {
|
|
||||||
out[i] = sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0 ? result : result | ~((1 << shift) - 1);
|
if ((byte & 0x80) == 0)
|
||||||
|
{
|
||||||
|
out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0)
|
||||||
|
? result
|
||||||
|
: result | ~((1 << shift) - 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} while (shift < sizeof(IntType) * 8);
|
} while (shift < sizeof(IntType) * 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(bytes_left == 0);
|
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>
|
template<typename IntType>
|
||||||
inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) {
|
inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) {
|
||||||
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
|
|
||||||
|
// Write our LEB128 magic string
|
||||||
stream.write(Leb128MagicString, Leb128MagicStringSize);
|
stream.write(Leb128MagicString, Leb128MagicStringSize);
|
||||||
|
|
||||||
|
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
|
||||||
|
|
||||||
std::uint32_t byte_count = 0;
|
std::uint32_t byte_count = 0;
|
||||||
for (std::size_t i = 0; i < count; ++i) {
|
for (std::size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
IntType value = values[i];
|
IntType value = values[i];
|
||||||
std::uint8_t byte;
|
std::uint8_t byte;
|
||||||
do {
|
do
|
||||||
|
{
|
||||||
byte = value & 0x7f;
|
byte = value & 0x7f;
|
||||||
value >>= 7;
|
value >>= 7;
|
||||||
++byte_count;
|
++byte_count;
|
||||||
} while ((byte & 0x40) == 0 ? value != 0 : value != -1);
|
} while ((byte & 0x40) == 0 ? value != 0 : value != -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
write_little_endian(stream, byte_count);
|
write_little_endian(stream, byte_count);
|
||||||
|
|
||||||
const std::uint32_t BUF_SIZE = 4096;
|
const std::uint32_t BUF_SIZE = 4096;
|
||||||
std::uint8_t buf[BUF_SIZE];
|
std::uint8_t buf[BUF_SIZE];
|
||||||
std::uint32_t buf_pos = 0;
|
std::uint32_t buf_pos = 0;
|
||||||
|
|
||||||
auto flush = [&]() {
|
auto flush = [&]() {
|
||||||
if (buf_pos > 0) {
|
if (buf_pos > 0)
|
||||||
|
{
|
||||||
stream.write(reinterpret_cast<char*>(buf), buf_pos);
|
stream.write(reinterpret_cast<char*>(buf), buf_pos);
|
||||||
buf_pos = 0;
|
buf_pos = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto write = [&](std::uint8_t byte) {
|
auto write = [&](std::uint8_t byte) {
|
||||||
buf[buf_pos++] = byte;
|
buf[buf_pos++] = byte;
|
||||||
if (buf_pos == BUF_SIZE) flush();
|
if (buf_pos == BUF_SIZE)
|
||||||
|
flush();
|
||||||
};
|
};
|
||||||
for (std::size_t i = 0; i < count; ++i) {
|
|
||||||
|
for (std::size_t i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
IntType value = values[i];
|
IntType value = values[i];
|
||||||
while (true) {
|
while (true)
|
||||||
|
{
|
||||||
std::uint8_t byte = value & 0x7f;
|
std::uint8_t byte = value & 0x7f;
|
||||||
value >>= 7;
|
value >>= 7;
|
||||||
if ((byte & 0x40) == 0 ? value == 0 : value == -1) {
|
if ((byte & 0x40) == 0 ? value == 0 : value == -1)
|
||||||
|
{
|
||||||
write(byte);
|
write(byte);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
write(byte | 0x80);
|
write(byte | 0x80);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+237
-141
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,11 +21,18 @@
|
|||||||
#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
#ifndef NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
||||||
#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
#define NNUE_FEATURE_TRANSFORMER_H_INCLUDED
|
||||||
|
|
||||||
#include "nnue_common.h"
|
#include <algorithm>
|
||||||
#include "nnue_architecture.h"
|
#include <cassert>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iosfwd>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include <cstring> // std::memset()
|
#include "../position.h"
|
||||||
#include <utility> // std::pair
|
#include "../types.h"
|
||||||
|
#include "nnue_accumulator.h"
|
||||||
|
#include "nnue_architecture.h"
|
||||||
|
#include "nnue_common.h"
|
||||||
|
|
||||||
namespace Stockfish::Eval::NNUE {
|
namespace Stockfish::Eval::NNUE {
|
||||||
|
|
||||||
@@ -62,7 +69,7 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
#define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)
|
#define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)
|
||||||
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
|
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
|
||||||
#define vec_zero_psqt() _mm256_setzero_si256()
|
#define vec_zero_psqt() _mm256_setzero_si256()
|
||||||
#define NumRegistersSIMD 32
|
#define NumRegistersSIMD 16
|
||||||
#define MaxChunkSize 64
|
#define MaxChunkSize 64
|
||||||
|
|
||||||
#elif USE_AVX2
|
#elif USE_AVX2
|
||||||
@@ -110,34 +117,6 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
#define NumRegistersSIMD (Is64Bit ? 16 : 8)
|
#define NumRegistersSIMD (Is64Bit ? 16 : 8)
|
||||||
#define MaxChunkSize 16
|
#define MaxChunkSize 16
|
||||||
|
|
||||||
#elif USE_MMX
|
|
||||||
using vec_t = __m64;
|
|
||||||
using psqt_vec_t = __m64;
|
|
||||||
#define vec_load(a) (*(a))
|
|
||||||
#define vec_store(a,b) *(a)=(b)
|
|
||||||
#define vec_add_16(a,b) _mm_add_pi16(a,b)
|
|
||||||
#define vec_sub_16(a,b) _mm_sub_pi16(a,b)
|
|
||||||
#define vec_mul_16(a,b) _mm_mullo_pi16(a,b)
|
|
||||||
#define vec_zero() _mm_setzero_si64()
|
|
||||||
#define vec_set_16(a) _mm_set1_pi16(a)
|
|
||||||
inline vec_t vec_max_16(vec_t a,vec_t b){
|
|
||||||
vec_t comparison = _mm_cmpgt_pi16(a,b);
|
|
||||||
return _mm_or_si64(_mm_and_si64(comparison, a), _mm_andnot_si64(comparison, b));
|
|
||||||
}
|
|
||||||
inline vec_t vec_min_16(vec_t a,vec_t b){
|
|
||||||
vec_t comparison = _mm_cmpgt_pi16(a,b);
|
|
||||||
return _mm_or_si64(_mm_and_si64(comparison, b), _mm_andnot_si64(comparison, a));
|
|
||||||
}
|
|
||||||
#define vec_msb_pack_16(a,b) _mm_packs_pi16(_mm_srli_pi16(a,7),_mm_srli_pi16(b,7))
|
|
||||||
#define vec_load_psqt(a) (*(a))
|
|
||||||
#define vec_store_psqt(a,b) *(a)=(b)
|
|
||||||
#define vec_add_psqt_32(a,b) _mm_add_pi32(a,b)
|
|
||||||
#define vec_sub_psqt_32(a,b) _mm_sub_pi32(a,b)
|
|
||||||
#define vec_zero_psqt() _mm_setzero_si64()
|
|
||||||
#define vec_cleanup() _mm_empty()
|
|
||||||
#define NumRegistersSIMD 8
|
|
||||||
#define MaxChunkSize 8
|
|
||||||
|
|
||||||
#elif USE_NEON
|
#elif USE_NEON
|
||||||
using vec_t = int16x8_t;
|
using vec_t = int16x8_t;
|
||||||
using psqt_vec_t = int32x4_t;
|
using psqt_vec_t = int32x4_t;
|
||||||
@@ -146,7 +125,8 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
#define vec_add_16(a, b) vaddq_s16(a, b)
|
#define vec_add_16(a, b) vaddq_s16(a, b)
|
||||||
#define vec_sub_16(a, b) vsubq_s16(a, b)
|
#define vec_sub_16(a, b) vsubq_s16(a, b)
|
||||||
#define vec_mul_16(a, b) vmulq_s16(a, b)
|
#define vec_mul_16(a, b) vmulq_s16(a, b)
|
||||||
#define vec_zero() vec_t{0}
|
#define vec_zero() \
|
||||||
|
vec_t { 0 }
|
||||||
#define vec_set_16(a) vdupq_n_s16(a)
|
#define vec_set_16(a) vdupq_n_s16(a)
|
||||||
#define vec_max_16(a, b) vmaxq_s16(a, b)
|
#define vec_max_16(a, b) vmaxq_s16(a, b)
|
||||||
#define vec_min_16(a, b) vminq_s16(a, b)
|
#define vec_min_16(a, b) vminq_s16(a, b)
|
||||||
@@ -160,7 +140,8 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
#define vec_store_psqt(a, b) *(a) = (b)
|
#define vec_store_psqt(a, b) *(a) = (b)
|
||||||
#define vec_add_psqt_32(a, b) vaddq_s32(a, b)
|
#define vec_add_psqt_32(a, b) vaddq_s32(a, b)
|
||||||
#define vec_sub_psqt_32(a, b) vsubq_s32(a, b)
|
#define vec_sub_psqt_32(a, b) vsubq_s32(a, b)
|
||||||
#define vec_zero_psqt() psqt_vec_t{0}
|
#define vec_zero_psqt() \
|
||||||
|
psqt_vec_t { 0 }
|
||||||
#define NumRegistersSIMD 16
|
#define NumRegistersSIMD 16
|
||||||
#define MaxChunkSize 16
|
#define MaxChunkSize 16
|
||||||
|
|
||||||
@@ -182,12 +163,8 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
#pragma GCC diagnostic ignored "-Wignored-attributes"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <typename SIMDRegisterType,
|
template<typename SIMDRegisterType, typename LaneType, int NumLanes, int MaxRegisters>
|
||||||
typename LaneType,
|
static constexpr int BestRegisterCount() {
|
||||||
int NumLanes,
|
|
||||||
int MaxRegisters>
|
|
||||||
static constexpr int BestRegisterCount()
|
|
||||||
{
|
|
||||||
#define RegisterSize sizeof(SIMDRegisterType)
|
#define RegisterSize sizeof(SIMDRegisterType)
|
||||||
#define LaneSize sizeof(LaneType)
|
#define LaneSize sizeof(LaneType)
|
||||||
|
|
||||||
@@ -209,17 +186,15 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr int NumRegs = BestRegisterCount<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
|
|
||||||
static constexpr int NumPsqtRegs = BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
|
|
||||||
#if defined(__GNUC__)
|
#if defined(__GNUC__)
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Input feature converter
|
// Input feature converter
|
||||||
|
template<IndexType TransformedFeatureDimensions,
|
||||||
|
Accumulator<TransformedFeatureDimensions> StateInfo::*accPtr>
|
||||||
class FeatureTransformer {
|
class FeatureTransformer {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -227,6 +202,11 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
|
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
|
||||||
|
|
||||||
#ifdef VECTOR
|
#ifdef VECTOR
|
||||||
|
static constexpr int NumRegs =
|
||||||
|
BestRegisterCount<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
|
||||||
|
static constexpr int NumPsqtRegs =
|
||||||
|
BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
|
||||||
|
|
||||||
static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
|
static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
|
||||||
static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;
|
static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;
|
||||||
static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions");
|
static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions");
|
||||||
@@ -242,8 +222,7 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
static constexpr IndexType OutputDimensions = HalfDimensions;
|
static constexpr IndexType OutputDimensions = HalfDimensions;
|
||||||
|
|
||||||
// Size of forward propagation buffer
|
// Size of forward propagation buffer
|
||||||
static constexpr std::size_t BufferSize =
|
static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType);
|
||||||
OutputDimensions * sizeof(OutputType);
|
|
||||||
|
|
||||||
// Hash value embedded in the evaluation file
|
// Hash value embedded in the evaluation file
|
||||||
static constexpr std::uint32_t get_hash_value() {
|
static constexpr std::uint32_t get_hash_value() {
|
||||||
@@ -271,19 +250,21 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert input features
|
// Convert input features
|
||||||
std::int32_t transform(const Position& pos, OutputType* output, int bucket) const {
|
std::int32_t
|
||||||
update_accumulator<WHITE>(pos);
|
transform(const Position& pos, OutputType* output, int bucket, bool psqtOnly) const {
|
||||||
update_accumulator<BLACK>(pos);
|
update_accumulator<WHITE>(pos, psqtOnly);
|
||||||
|
update_accumulator<BLACK>(pos, psqtOnly);
|
||||||
|
|
||||||
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
|
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
|
||||||
const auto& accumulation = pos.state()->accumulator.accumulation;
|
const auto& psqtAccumulation = (pos.state()->*accPtr).psqtAccumulation;
|
||||||
const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation;
|
const auto psqt =
|
||||||
|
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket])
|
||||||
|
/ 2;
|
||||||
|
|
||||||
const auto psqt = (
|
if (psqtOnly)
|
||||||
psqtAccumulation[perspectives[0]][bucket]
|
return psqt;
|
||||||
- psqtAccumulation[perspectives[1]][bucket]
|
|
||||||
) / 2;
|
|
||||||
|
|
||||||
|
const auto& accumulation = (pos.state()->*accPtr).accumulation;
|
||||||
|
|
||||||
for (IndexType p = 0; p < 2; ++p)
|
for (IndexType p = 0; p < 2; ++p)
|
||||||
{
|
{
|
||||||
@@ -299,10 +280,11 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
vec_t One = vec_set_16(127);
|
vec_t One = vec_set_16(127);
|
||||||
|
|
||||||
const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
|
const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
|
||||||
const vec_t* in1 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][HalfDimensions / 2]));
|
const vec_t* in1 =
|
||||||
|
reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][HalfDimensions / 2]));
|
||||||
vec_t* out = reinterpret_cast<vec_t*>(output + offset);
|
vec_t* out = reinterpret_cast<vec_t*>(output + offset);
|
||||||
|
|
||||||
for (IndexType j = 0; j < NumOutputChunks; j += 1)
|
for (IndexType j = 0; j < NumOutputChunks; ++j)
|
||||||
{
|
{
|
||||||
const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero);
|
const vec_t sum0a = vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero);
|
||||||
const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero);
|
const vec_t sum0b = vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero);
|
||||||
@@ -317,37 +299,38 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
for (IndexType j = 0; j < HalfDimensions / 2; ++j) {
|
for (IndexType j = 0; j < HalfDimensions / 2; ++j)
|
||||||
|
{
|
||||||
BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];
|
BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];
|
||||||
BiasType sum1 = accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
|
BiasType sum1 =
|
||||||
sum0 = std::max<int>(0, std::min<int>(127, sum0));
|
accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
|
||||||
sum1 = std::max<int>(0, std::min<int>(127, sum1));
|
sum0 = std::clamp<BiasType>(sum0, 0, 127);
|
||||||
output[offset + j] = static_cast<OutputType>(sum0 * sum1 / 128);
|
sum1 = std::clamp<BiasType>(sum1, 0, 127);
|
||||||
|
output[offset + j] = static_cast<OutputType>(unsigned(sum0 * sum1) / 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(vec_cleanup)
|
|
||||||
vec_cleanup();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return psqt;
|
return psqt;
|
||||||
} // end of function transform()
|
} // end of function transform()
|
||||||
|
|
||||||
void hint_common_access(const Position& pos) const {
|
void hint_common_access(const Position& pos, bool psqtOnly) const {
|
||||||
hint_common_access_for_perspective<WHITE>(pos);
|
hint_common_access_for_perspective<WHITE>(pos, psqtOnly);
|
||||||
hint_common_access_for_perspective<BLACK>(pos);
|
hint_common_access_for_perspective<BLACK>(pos, psqtOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
[[nodiscard]] std::pair<StateInfo*, StateInfo*> try_find_computed_accumulator(const Position& pos) const {
|
[[nodiscard]] std::pair<StateInfo*, StateInfo*>
|
||||||
|
try_find_computed_accumulator(const Position& pos, bool psqtOnly) const {
|
||||||
// Look for a usable accumulator of an earlier position. We keep track
|
// Look for a usable accumulator of an earlier position. We keep track
|
||||||
// of the estimated gain in terms of features to be added/subtracted.
|
// of the estimated gain in terms of features to be added/subtracted.
|
||||||
StateInfo *st = pos.state(), *next = nullptr;
|
StateInfo *st = pos.state(), *next = nullptr;
|
||||||
int gain = FeatureSet::refresh_cost(pos);
|
int gain = FeatureSet::refresh_cost(pos);
|
||||||
while (st->previous && !st->accumulator.computed[Perspective])
|
while (st->previous
|
||||||
|
&& (!(st->*accPtr).computedPSQT[Perspective]
|
||||||
|
|| (!psqtOnly && !(st->*accPtr).computed[Perspective])))
|
||||||
{
|
{
|
||||||
// This governs when a full feature refresh is needed and how many
|
// This governs when a full feature refresh is needed and how many
|
||||||
// updates are better than just one full refresh.
|
// updates are better than just one full refresh.
|
||||||
@@ -362,10 +345,15 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
|
|
||||||
// NOTE: The parameter states_to_update is an array of position states, ending with nullptr.
|
// NOTE: The parameter states_to_update is an array of position states, ending with nullptr.
|
||||||
// All states must be sequential, that is states_to_update[i] must either be reachable
|
// All states must be sequential, that is states_to_update[i] must either be reachable
|
||||||
// by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr.
|
// by repeatedly applying ->previous from states_to_update[i+1] or
|
||||||
// computed_st must be reachable by repeatedly applying ->previous on states_to_update[0], if not nullptr.
|
// states_to_update[i] == nullptr.
|
||||||
|
// computed_st must be reachable by repeatedly applying ->previous on
|
||||||
|
// states_to_update[0], if not nullptr.
|
||||||
template<Color Perspective, size_t N>
|
template<Color Perspective, size_t N>
|
||||||
void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const {
|
void update_accumulator_incremental(const Position& pos,
|
||||||
|
StateInfo* computed_st,
|
||||||
|
StateInfo* states_to_update[N],
|
||||||
|
bool psqtOnly) const {
|
||||||
static_assert(N > 0);
|
static_assert(N > 0);
|
||||||
assert(states_to_update[N - 1] == nullptr);
|
assert(states_to_update[N - 1] == nullptr);
|
||||||
|
|
||||||
@@ -386,12 +374,14 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
|
|
||||||
// The size must be enough to contain the largest possible update.
|
// The size must be enough to contain the largest possible update.
|
||||||
// That might depend on the feature set and generally relies on the
|
// That might depend on the feature set and generally relies on the
|
||||||
// feature set's update cost calculation to be correct and never
|
// feature set's update cost calculation to be correct and never allow
|
||||||
// allow updates with more added/removed features than MaxActiveDimensions.
|
// updates with more added/removed features than MaxActiveDimensions.
|
||||||
FeatureSet::IndexList removed[N - 1], added[N - 1];
|
FeatureSet::IndexList removed[N - 1], added[N - 1];
|
||||||
|
|
||||||
{
|
{
|
||||||
int i = N-2; // last potential state to update. Skip last element because it must be nullptr.
|
int i =
|
||||||
|
N
|
||||||
|
- 2; // Last potential state to update. Skip last element because it must be nullptr.
|
||||||
while (states_to_update[i] == nullptr)
|
while (states_to_update[i] == nullptr)
|
||||||
--i;
|
--i;
|
||||||
|
|
||||||
@@ -399,13 +389,14 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
|
|
||||||
for (; i >= 0; --i)
|
for (; i >= 0; --i)
|
||||||
{
|
{
|
||||||
states_to_update[i]->accumulator.computed[Perspective] = true;
|
(states_to_update[i]->*accPtr).computed[Perspective] = !psqtOnly;
|
||||||
|
(states_to_update[i]->*accPtr).computedPSQT[Perspective] = true;
|
||||||
|
|
||||||
StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1];
|
const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1];
|
||||||
|
|
||||||
for (; st2 != end_state; st2 = st2->previous)
|
for (; st2 != end_state; st2 = st2->previous)
|
||||||
FeatureSet::append_changed_indices<Perspective>(
|
FeatureSet::append_changed_indices<Perspective>(ksq, st2->dirtyPiece,
|
||||||
ksq, st2->dirtyPiece, removed[i], added[i]);
|
removed[i], added[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,13 +404,81 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
|
|
||||||
// Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
|
// Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
|
||||||
#ifdef VECTOR
|
#ifdef VECTOR
|
||||||
|
|
||||||
|
if (states_to_update[1] == nullptr && (removed[0].size() == 1 || removed[0].size() == 2)
|
||||||
|
&& added[0].size() == 1)
|
||||||
|
{
|
||||||
|
assert(states_to_update[0]);
|
||||||
|
|
||||||
|
if (!psqtOnly)
|
||||||
|
{
|
||||||
|
auto accIn =
|
||||||
|
reinterpret_cast<const vec_t*>(&(st->*accPtr).accumulation[Perspective][0]);
|
||||||
|
auto accOut = reinterpret_cast<vec_t*>(
|
||||||
|
&(states_to_update[0]->*accPtr).accumulation[Perspective][0]);
|
||||||
|
|
||||||
|
const IndexType offsetR0 = HalfDimensions * removed[0][0];
|
||||||
|
auto columnR0 = reinterpret_cast<const vec_t*>(&weights[offsetR0]);
|
||||||
|
const IndexType offsetA = HalfDimensions * added[0][0];
|
||||||
|
auto columnA = reinterpret_cast<const vec_t*>(&weights[offsetA]);
|
||||||
|
|
||||||
|
if (removed[0].size() == 1)
|
||||||
|
{
|
||||||
|
for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t);
|
||||||
|
++k)
|
||||||
|
accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const IndexType offsetR1 = HalfDimensions * removed[0][1];
|
||||||
|
auto columnR1 = reinterpret_cast<const vec_t*>(&weights[offsetR1]);
|
||||||
|
|
||||||
|
for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t);
|
||||||
|
++k)
|
||||||
|
accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]),
|
||||||
|
vec_add_16(columnR0[k], columnR1[k]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto accPsqtIn =
|
||||||
|
reinterpret_cast<const psqt_vec_t*>(&(st->*accPtr).psqtAccumulation[Perspective][0]);
|
||||||
|
auto accPsqtOut = reinterpret_cast<psqt_vec_t*>(
|
||||||
|
&(states_to_update[0]->*accPtr).psqtAccumulation[Perspective][0]);
|
||||||
|
|
||||||
|
const IndexType offsetPsqtR0 = PSQTBuckets * removed[0][0];
|
||||||
|
auto columnPsqtR0 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtR0]);
|
||||||
|
const IndexType offsetPsqtA = PSQTBuckets * added[0][0];
|
||||||
|
auto columnPsqtA = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtA]);
|
||||||
|
|
||||||
|
if (removed[0].size() == 1)
|
||||||
|
{
|
||||||
|
for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t);
|
||||||
|
++k)
|
||||||
|
accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[k], columnPsqtR0[k]),
|
||||||
|
columnPsqtA[k]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1];
|
||||||
|
auto columnPsqtR1 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtR1]);
|
||||||
|
|
||||||
|
for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t);
|
||||||
|
++k)
|
||||||
|
accPsqtOut[k] =
|
||||||
|
vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]),
|
||||||
|
vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!psqtOnly)
|
||||||
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
|
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
|
||||||
{
|
{
|
||||||
// Load accumulator
|
// Load accumulator
|
||||||
auto accTile = reinterpret_cast<vec_t*>(
|
auto accTileIn = reinterpret_cast<const vec_t*>(
|
||||||
&st->accumulator.accumulation[Perspective][j * TileHeight]);
|
&(st->*accPtr).accumulation[Perspective][j * TileHeight]);
|
||||||
for (IndexType k = 0; k < NumRegs; ++k)
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
acc[k] = vec_load(&accTile[k]);
|
acc[k] = vec_load(&accTileIn[k]);
|
||||||
|
|
||||||
for (IndexType i = 0; states_to_update[i]; ++i)
|
for (IndexType i = 0; states_to_update[i]; ++i)
|
||||||
{
|
{
|
||||||
@@ -442,20 +501,21 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store accumulator
|
// Store accumulator
|
||||||
accTile = reinterpret_cast<vec_t*>(
|
auto accTileOut =
|
||||||
&states_to_update[i]->accumulator.accumulation[Perspective][j * TileHeight]);
|
reinterpret_cast<vec_t*>(&(states_to_update[i]->*accPtr)
|
||||||
|
.accumulation[Perspective][j * TileHeight]);
|
||||||
for (IndexType k = 0; k < NumRegs; ++k)
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
vec_store(&accTile[k], acc[k]);
|
vec_store(&accTileOut[k], acc[k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
|
for (IndexType j = 0; j < PSQTBuckets / PsqtTileHeight; ++j)
|
||||||
{
|
{
|
||||||
// Load accumulator
|
// Load accumulator
|
||||||
auto accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
auto accTilePsqtIn = reinterpret_cast<const psqt_vec_t*>(
|
||||||
&st->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
|
&(st->*accPtr).psqtAccumulation[Perspective][j * PsqtTileHeight]);
|
||||||
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
psqt[k] = vec_load_psqt(&accTilePsqt[k]);
|
psqt[k] = vec_load_psqt(&accTilePsqtIn[k]);
|
||||||
|
|
||||||
for (IndexType i = 0; states_to_update[i]; ++i)
|
for (IndexType i = 0; states_to_update[i]; ++i)
|
||||||
{
|
{
|
||||||
@@ -478,58 +538,63 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store accumulator
|
// Store accumulator
|
||||||
accTilePsqt = reinterpret_cast<psqt_vec_t*>(
|
auto accTilePsqtOut = reinterpret_cast<psqt_vec_t*>(
|
||||||
&states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
|
&(states_to_update[i]->*accPtr)
|
||||||
|
.psqtAccumulation[Perspective][j * PsqtTileHeight]);
|
||||||
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
vec_store_psqt(&accTilePsqt[k], psqt[k]);
|
vec_store_psqt(&accTilePsqtOut[k], psqt[k]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
for (IndexType i = 0; states_to_update[i]; ++i)
|
for (IndexType i = 0; states_to_update[i]; ++i)
|
||||||
{
|
{
|
||||||
std::memcpy(states_to_update[i]->accumulator.accumulation[Perspective],
|
if (!psqtOnly)
|
||||||
st->accumulator.accumulation[Perspective],
|
std::memcpy((states_to_update[i]->*accPtr).accumulation[Perspective],
|
||||||
|
(st->*accPtr).accumulation[Perspective],
|
||||||
HalfDimensions * sizeof(BiasType));
|
HalfDimensions * sizeof(BiasType));
|
||||||
|
|
||||||
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||||
states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k];
|
(states_to_update[i]->*accPtr).psqtAccumulation[Perspective][k] =
|
||||||
|
(st->*accPtr).psqtAccumulation[Perspective][k];
|
||||||
|
|
||||||
st = states_to_update[i];
|
st = states_to_update[i];
|
||||||
|
|
||||||
// Difference calculation for the deactivated features
|
// Difference calculation for the deactivated features
|
||||||
for (const auto index : removed[i])
|
for (const auto index : removed[i])
|
||||||
|
{
|
||||||
|
if (!psqtOnly)
|
||||||
{
|
{
|
||||||
const IndexType offset = HalfDimensions * index;
|
const IndexType offset = HalfDimensions * index;
|
||||||
|
|
||||||
for (IndexType j = 0; j < HalfDimensions; ++j)
|
for (IndexType j = 0; j < HalfDimensions; ++j)
|
||||||
st->accumulator.accumulation[Perspective][j] -= weights[offset + j];
|
(st->*accPtr).accumulation[Perspective][j] -= weights[offset + j];
|
||||||
|
}
|
||||||
|
|
||||||
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||||
st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k];
|
(st->*accPtr).psqtAccumulation[Perspective][k] -=
|
||||||
|
psqtWeights[index * PSQTBuckets + k];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Difference calculation for the activated features
|
// Difference calculation for the activated features
|
||||||
for (const auto index : added[i])
|
for (const auto index : added[i])
|
||||||
|
{
|
||||||
|
if (!psqtOnly)
|
||||||
{
|
{
|
||||||
const IndexType offset = HalfDimensions * index;
|
const IndexType offset = HalfDimensions * index;
|
||||||
|
|
||||||
for (IndexType j = 0; j < HalfDimensions; ++j)
|
for (IndexType j = 0; j < HalfDimensions; ++j)
|
||||||
st->accumulator.accumulation[Perspective][j] += weights[offset + j];
|
(st->*accPtr).accumulation[Perspective][j] += weights[offset + j];
|
||||||
|
}
|
||||||
|
|
||||||
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||||
st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
|
(st->*accPtr).psqtAccumulation[Perspective][k] +=
|
||||||
|
psqtWeights[index * PSQTBuckets + k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_MMX)
|
|
||||||
_mm_empty();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
void update_accumulator_refresh(const Position& pos) const {
|
void update_accumulator_refresh(const Position& pos, bool psqtOnly) const {
|
||||||
#ifdef VECTOR
|
#ifdef VECTOR
|
||||||
// Gcc-10.2 unnecessarily spills AVX2 registers if this array
|
// Gcc-10.2 unnecessarily spills AVX2 registers if this array
|
||||||
// is defined in the VECTOR code below, once in each branch
|
// is defined in the VECTOR code below, once in each branch
|
||||||
@@ -540,21 +605,36 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
// Refresh the accumulator
|
// Refresh the accumulator
|
||||||
// Could be extracted to a separate function because it's done in 2 places,
|
// Could be extracted to a separate function because it's done in 2 places,
|
||||||
// but it's unclear if compilers would correctly handle register allocation.
|
// but it's unclear if compilers would correctly handle register allocation.
|
||||||
auto& accumulator = pos.state()->accumulator;
|
auto& accumulator = pos.state()->*accPtr;
|
||||||
accumulator.computed[Perspective] = true;
|
accumulator.computed[Perspective] = !psqtOnly;
|
||||||
|
accumulator.computedPSQT[Perspective] = true;
|
||||||
FeatureSet::IndexList active;
|
FeatureSet::IndexList active;
|
||||||
FeatureSet::append_active_indices<Perspective>(pos, active);
|
FeatureSet::append_active_indices<Perspective>(pos, active);
|
||||||
|
|
||||||
#ifdef VECTOR
|
#ifdef VECTOR
|
||||||
|
if (!psqtOnly)
|
||||||
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
|
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
|
||||||
{
|
{
|
||||||
auto biasesTile = reinterpret_cast<const vec_t*>(
|
auto biasesTile = reinterpret_cast<const vec_t*>(&biases[j * TileHeight]);
|
||||||
&biases[j * TileHeight]);
|
|
||||||
for (IndexType k = 0; k < NumRegs; ++k)
|
for (IndexType k = 0; k < NumRegs; ++k)
|
||||||
acc[k] = biasesTile[k];
|
acc[k] = biasesTile[k];
|
||||||
|
|
||||||
for (const auto index : active)
|
int i = 0;
|
||||||
|
for (; i < int(active.size()) - 1; i += 2)
|
||||||
{
|
{
|
||||||
|
IndexType index0 = active[i];
|
||||||
|
IndexType index1 = active[i + 1];
|
||||||
|
const IndexType offset0 = HalfDimensions * index0 + j * TileHeight;
|
||||||
|
const IndexType offset1 = HalfDimensions * index1 + j * TileHeight;
|
||||||
|
auto column0 = reinterpret_cast<const vec_t*>(&weights[offset0]);
|
||||||
|
auto column1 = reinterpret_cast<const vec_t*>(&weights[offset1]);
|
||||||
|
|
||||||
|
for (unsigned k = 0; k < NumRegs; ++k)
|
||||||
|
acc[k] = vec_add_16(acc[k], vec_add_16(column0[k], column1[k]));
|
||||||
|
}
|
||||||
|
for (; i < int(active.size()); ++i)
|
||||||
|
{
|
||||||
|
IndexType index = active[i];
|
||||||
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
const IndexType offset = HalfDimensions * index + j * TileHeight;
|
||||||
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
|
||||||
|
|
||||||
@@ -562,8 +642,8 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
acc[k] = vec_add_16(acc[k], column[k]);
|
acc[k] = vec_add_16(acc[k], column[k]);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto accTile = reinterpret_cast<vec_t*>(
|
auto accTile =
|
||||||
&accumulator.accumulation[Perspective][j * TileHeight]);
|
reinterpret_cast<vec_t*>(&accumulator.accumulation[Perspective][j * TileHeight]);
|
||||||
for (unsigned k = 0; k < NumRegs; k++)
|
for (unsigned k = 0; k < NumRegs; k++)
|
||||||
vec_store(&accTile[k], acc[k]);
|
vec_store(&accTile[k], acc[k]);
|
||||||
}
|
}
|
||||||
@@ -573,8 +653,23 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
psqt[k] = vec_zero_psqt();
|
psqt[k] = vec_zero_psqt();
|
||||||
|
|
||||||
for (const auto index : active)
|
int i = 0;
|
||||||
|
for (; i < int(active.size()) - 1; i += 2)
|
||||||
{
|
{
|
||||||
|
IndexType index0 = active[i];
|
||||||
|
IndexType index1 = active[i + 1];
|
||||||
|
const IndexType offset0 = PSQTBuckets * index0 + j * PsqtTileHeight;
|
||||||
|
const IndexType offset1 = PSQTBuckets * index1 + j * PsqtTileHeight;
|
||||||
|
auto columnPsqt0 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset0]);
|
||||||
|
auto columnPsqt1 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset1]);
|
||||||
|
|
||||||
|
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
|
||||||
|
psqt[k] =
|
||||||
|
vec_add_psqt_32(psqt[k], vec_add_psqt_32(columnPsqt0[k], columnPsqt1[k]));
|
||||||
|
}
|
||||||
|
for (; i < int(active.size()); ++i)
|
||||||
|
{
|
||||||
|
IndexType index = active[i];
|
||||||
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
|
const IndexType offset = PSQTBuckets * index + j * PsqtTileHeight;
|
||||||
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
auto columnPsqt = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offset]);
|
||||||
|
|
||||||
@@ -589,6 +684,7 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
if (!psqtOnly)
|
||||||
std::memcpy(accumulator.accumulation[Perspective], biases,
|
std::memcpy(accumulator.accumulation[Perspective], biases,
|
||||||
HalfDimensions * sizeof(BiasType));
|
HalfDimensions * sizeof(BiasType));
|
||||||
|
|
||||||
@@ -596,24 +692,23 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
accumulator.psqtAccumulation[Perspective][k] = 0;
|
accumulator.psqtAccumulation[Perspective][k] = 0;
|
||||||
|
|
||||||
for (const auto index : active)
|
for (const auto index : active)
|
||||||
|
{
|
||||||
|
if (!psqtOnly)
|
||||||
{
|
{
|
||||||
const IndexType offset = HalfDimensions * index;
|
const IndexType offset = HalfDimensions * index;
|
||||||
|
|
||||||
for (IndexType j = 0; j < HalfDimensions; ++j)
|
for (IndexType j = 0; j < HalfDimensions; ++j)
|
||||||
accumulator.accumulation[Perspective][j] += weights[offset + j];
|
accumulator.accumulation[Perspective][j] += weights[offset + j];
|
||||||
|
}
|
||||||
|
|
||||||
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
for (std::size_t k = 0; k < PSQTBuckets; ++k)
|
||||||
accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
|
accumulator.psqtAccumulation[Perspective][k] +=
|
||||||
|
psqtWeights[index * PSQTBuckets + k];
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_MMX)
|
|
||||||
_mm_empty();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
void hint_common_access_for_perspective(const Position& pos) const {
|
void hint_common_access_for_perspective(const Position& pos, bool psqtOnly) const {
|
||||||
|
|
||||||
// Works like update_accumulator, but performs less work.
|
// Works like update_accumulator, but performs less work.
|
||||||
// Updates ONLY the accumulator for pos.
|
// Updates ONLY the accumulator for pos.
|
||||||
@@ -621,29 +716,31 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
// Look for a usable accumulator of an earlier position. We keep track
|
// Look for a usable accumulator of an earlier position. We keep track
|
||||||
// of the estimated gain in terms of features to be added/subtracted.
|
// of the estimated gain in terms of features to be added/subtracted.
|
||||||
// Fast early exit.
|
// Fast early exit.
|
||||||
if (pos.state()->accumulator.computed[Perspective])
|
if ((pos.state()->*accPtr).computed[Perspective]
|
||||||
|
|| (psqtOnly && (pos.state()->*accPtr).computedPSQT[Perspective]))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto [oldest_st, _] = try_find_computed_accumulator<Perspective>(pos);
|
auto [oldest_st, _] = try_find_computed_accumulator<Perspective>(pos, psqtOnly);
|
||||||
|
|
||||||
if (oldest_st->accumulator.computed[Perspective])
|
if ((oldest_st->*accPtr).computed[Perspective]
|
||||||
|
|| (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective]))
|
||||||
{
|
{
|
||||||
// Only update current position accumulator to minimize work.
|
// Only update current position accumulator to minimize work.
|
||||||
StateInfo* states_to_update[2] = {pos.state(), nullptr};
|
StateInfo* states_to_update[2] = {pos.state(), nullptr};
|
||||||
update_accumulator_incremental<Perspective, 2>(pos, oldest_st, states_to_update);
|
update_accumulator_incremental<Perspective, 2>(pos, oldest_st, states_to_update,
|
||||||
|
psqtOnly);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
update_accumulator_refresh<Perspective>(pos, psqtOnly);
|
||||||
update_accumulator_refresh<Perspective>(pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<Color Perspective>
|
template<Color Perspective>
|
||||||
void update_accumulator(const Position& pos) const {
|
void update_accumulator(const Position& pos, bool psqtOnly) const {
|
||||||
|
|
||||||
auto [oldest_st, next] = try_find_computed_accumulator<Perspective>(pos);
|
auto [oldest_st, next] = try_find_computed_accumulator<Perspective>(pos, psqtOnly);
|
||||||
|
|
||||||
if (oldest_st->accumulator.computed[Perspective])
|
if ((oldest_st->*accPtr).computed[Perspective]
|
||||||
|
|| (psqtOnly && (oldest_st->*accPtr).computedPSQT[Perspective]))
|
||||||
{
|
{
|
||||||
if (next == nullptr)
|
if (next == nullptr)
|
||||||
return;
|
return;
|
||||||
@@ -653,15 +750,14 @@ namespace Stockfish::Eval::NNUE {
|
|||||||
// 1. for the current position
|
// 1. for the current position
|
||||||
// 2. the next accumulator after the computed one
|
// 2. the next accumulator after the computed one
|
||||||
// The heuristic may change in the future.
|
// The heuristic may change in the future.
|
||||||
StateInfo *states_to_update[3] =
|
StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(),
|
||||||
{ next, next == pos.state() ? nullptr : pos.state(), nullptr };
|
nullptr};
|
||||||
|
|
||||||
update_accumulator_incremental<Perspective, 3>(pos, oldest_st, states_to_update);
|
update_accumulator_incremental<Perspective, 3>(pos, oldest_st, states_to_update,
|
||||||
|
psqtOnly);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
update_accumulator_refresh<Perspective>(pos, psqtOnly);
|
||||||
update_accumulator_refresh<Perspective>(pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
alignas(CacheLineSize) BiasType biases[HalfDimensions];
|
alignas(CacheLineSize) BiasType biases[HalfDimensions];
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
/*
|
||||||
|
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) {
|
||||||
|
|
||||||
|
int simpleEvalAbs = std::abs(simple_eval(pos, pos.side_to_move()));
|
||||||
|
if (simpleEvalAbs > Eval::SmallNetThreshold)
|
||||||
|
networks.small.hint_common_access(pos, simpleEvalAbs > Eval::PsqtOnlyThreshold);
|
||||||
|
else
|
||||||
|
networks.big.hint_common_access(pos, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(UCI::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 * UCI::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) {
|
||||||
|
|
||||||
|
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);
|
||||||
|
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] =
|
||||||
|
st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[BLACK] =
|
||||||
|
false;
|
||||||
|
|
||||||
|
Value eval = networks.big.evaluate(pos);
|
||||||
|
eval = pos.side_to_move() == WHITE ? eval : -eval;
|
||||||
|
v = base - eval;
|
||||||
|
|
||||||
|
pos.put_piece(pc, sq);
|
||||||
|
st->accumulatorBig.computed[WHITE] = st->accumulatorBig.computed[BLACK] =
|
||||||
|
st->accumulatorBig.computedPSQT[WHITE] = st->accumulatorBig.computedPSQT[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);
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
|
std::string trace(Position& pos, const Networks& networks);
|
||||||
|
void hint_common_parent_position(const Position& pos, const Networks& networks);
|
||||||
|
|
||||||
|
} // namespace Stockfish::Eval::NNUE
|
||||||
|
} // namespace Stockfish
|
||||||
|
|
||||||
|
#endif // #ifndef NNUE_MISC_H_INCLUDED
|
||||||
-305
@@ -1,305 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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( 6, 19);
|
|
||||||
constexpr Score Doubled = S(11, 51);
|
|
||||||
constexpr Score DoubledEarly = S(17, 7);
|
|
||||||
constexpr Score Isolated = S( 1, 20);
|
|
||||||
constexpr Score WeakLever = S( 2, 57);
|
|
||||||
constexpr Score WeakUnopposed = S(15, 18);
|
|
||||||
|
|
||||||
// Bonus for blocked pawns at 5th or 6th rank
|
|
||||||
constexpr Score BlockedPawn[2] = { S(-19, -8), S(-7, 3) };
|
|
||||||
|
|
||||||
constexpr Score BlockedStorm[RANK_NB] = {
|
|
||||||
S(0, 0), S(0, 0), S(64, 75), S(-3, 14), S(-12, 19), S(-7, 4), S(-10, 5)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Connected pawn bonus
|
|
||||||
constexpr int Connected[RANK_NB] = { 0, 3, 7, 7, 15, 54, 86 };
|
|
||||||
|
|
||||||
// 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(-2), V(85), V(95), V(53), V(39), V(23), V(25) },
|
|
||||||
{ V(-55), V(64), V(32), V(-55), V(-30), V(-11), V(-61) },
|
|
||||||
{ V(-11), V(75), V(19), V(-6), V(26), V(9), V(-47) },
|
|
||||||
{ V(-41), V(-11), V(-27), V(-58), V(-42), V(-66), V(-163) }
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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(94), V(-280), V(-170), V(90), V(59), V(47), V(53) },
|
|
||||||
{ V(43), V(-17), V(128), V(39), V(26), V(-17), V(15) },
|
|
||||||
{ V(-9), V(62), V(170), V(34), V(-5), V(-20), V(-11) },
|
|
||||||
{ V(-27), V(-19), V(106), V(10), V(2), V(-13), V(-24) }
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// 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(-18,11), S(-6,-3) },
|
|
||||||
{ S( 0, 0), S( 5,-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
|
|
||||||
-70
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
using Table = HashTable<Entry, 131072>;
|
|
||||||
|
|
||||||
Entry* probe(const Position& pos);
|
|
||||||
|
|
||||||
} // namespace Stockfish::Pawns
|
|
||||||
|
|
||||||
#endif // #ifndef PAWNS_H_INCLUDED
|
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
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 "cluster.h"
|
||||||
|
#include "movegen.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "types.h"
|
||||||
|
#include "uci.h"
|
||||||
|
|
||||||
|
namespace Stockfish {
|
||||||
|
|
||||||
|
// 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 && Cluster::is_root())
|
||||||
|
sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl;
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void perft(const std::string& fen, Depth depth, bool isChess960) {
|
||||||
|
StateListPtr states(new std::deque<StateInfo>(1));
|
||||||
|
Position p;
|
||||||
|
p.set(fen, isChess960, &states->back());
|
||||||
|
|
||||||
|
uint64_t nodes = perft<true>(p, depth);
|
||||||
|
if (Cluster::is_root())
|
||||||
|
sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // PERFT_H_INCLUDED
|
||||||
+207
-255
File diff suppressed because it is too large
Load Diff
+74
-160
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,27 +21,28 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <memory> // For std::unique_ptr
|
#include <iosfwd>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "bitboard.h"
|
#include "bitboard.h"
|
||||||
#include "evaluate.h"
|
|
||||||
#include "psqt.h"
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
#include "nnue/nnue_accumulator.h"
|
#include "nnue/nnue_accumulator.h"
|
||||||
|
#include "nnue/nnue_architecture.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
/// StateInfo struct stores information needed to restore a Position object to
|
class TranspositionTable;
|
||||||
/// 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.
|
// 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 {
|
struct StateInfo {
|
||||||
|
|
||||||
// Copied when making a move
|
// Copied when making a move
|
||||||
Key pawnKey;
|
|
||||||
Key materialKey;
|
Key materialKey;
|
||||||
|
Key pawnKey;
|
||||||
Value nonPawnMaterial[COLOR_NB];
|
Value nonPawnMaterial[COLOR_NB];
|
||||||
int castlingRights;
|
int castlingRights;
|
||||||
int rule50;
|
int rule50;
|
||||||
@@ -59,24 +60,23 @@ struct StateInfo {
|
|||||||
int repetition;
|
int repetition;
|
||||||
|
|
||||||
// Used by NNUE
|
// Used by NNUE
|
||||||
Eval::NNUE::Accumulator accumulator;
|
Eval::NNUE::Accumulator<Eval::NNUE::TransformedFeatureDimensionsBig> accumulatorBig;
|
||||||
|
Eval::NNUE::Accumulator<Eval::NNUE::TransformedFeatureDimensionsSmall> accumulatorSmall;
|
||||||
DirtyPiece dirtyPiece;
|
DirtyPiece dirtyPiece;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// A list to keep track of the position states along the setup moves (from the
|
// 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
|
// start position to the position just before the search starts). Needed by
|
||||||
/// 'draw by repetition' detection. Use a std::deque because pointers to
|
// 'draw by repetition' detection. Use a std::deque because pointers to
|
||||||
/// elements are not invalidated upon list resizing.
|
// elements are not invalidated upon list resizing.
|
||||||
using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
|
using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
|
||||||
|
|
||||||
|
|
||||||
/// Position class stores information regarding the board representation as
|
// Position class stores information regarding the board representation as
|
||||||
/// pieces, side to move, hash keys, castling info, etc. Important methods are
|
// 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
|
// do_move() and undo_move(), used by the search to update node info when
|
||||||
/// traversing the search tree.
|
// traversing the search tree.
|
||||||
class Thread;
|
|
||||||
|
|
||||||
class Position {
|
class Position {
|
||||||
public:
|
public:
|
||||||
static void init();
|
static void init();
|
||||||
@@ -86,22 +86,26 @@ public:
|
|||||||
Position& operator=(const Position&) = delete;
|
Position& operator=(const Position&) = delete;
|
||||||
|
|
||||||
// FEN string input/output
|
// FEN string input/output
|
||||||
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th);
|
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si);
|
||||||
Position& set(const std::string& code, Color c, StateInfo* si);
|
Position& set(const std::string& code, Color c, StateInfo* si);
|
||||||
std::string fen() const;
|
std::string fen() const;
|
||||||
|
|
||||||
// Position representation
|
// Position representation
|
||||||
Bitboard pieces(PieceType pt) const;
|
Bitboard pieces(PieceType pt = ALL_PIECES) const;
|
||||||
template<typename ...PieceTypes> Bitboard pieces(PieceType pt, PieceTypes... pts) const;
|
template<typename... PieceTypes>
|
||||||
|
Bitboard pieces(PieceType pt, PieceTypes... pts) const;
|
||||||
Bitboard pieces(Color c) const;
|
Bitboard pieces(Color c) const;
|
||||||
template<typename ...PieceTypes> Bitboard pieces(Color c, PieceTypes... pts) const;
|
template<typename... PieceTypes>
|
||||||
|
Bitboard pieces(Color c, PieceTypes... pts) const;
|
||||||
Piece piece_on(Square s) const;
|
Piece piece_on(Square s) const;
|
||||||
Square ep_square() const;
|
Square ep_square() const;
|
||||||
bool empty(Square s) const;
|
bool empty(Square s) const;
|
||||||
template<PieceType Pt> int count(Color c) const;
|
template<PieceType Pt>
|
||||||
template<PieceType Pt> int count() const;
|
int count(Color c) const;
|
||||||
template<PieceType Pt> Square square(Color c) const;
|
template<PieceType Pt>
|
||||||
bool is_on_semiopen_file(Color c, Square s) const;
|
int count() const;
|
||||||
|
template<PieceType Pt>
|
||||||
|
Square square(Color c) const;
|
||||||
|
|
||||||
// Castling
|
// Castling
|
||||||
CastlingRights castling_rights(Color c) const;
|
CastlingRights castling_rights(Color c) const;
|
||||||
@@ -118,8 +122,9 @@ public:
|
|||||||
// Attacks to/from a given square
|
// Attacks to/from a given square
|
||||||
Bitboard attackers_to(Square s) const;
|
Bitboard attackers_to(Square s) const;
|
||||||
Bitboard attackers_to(Square s, Bitboard occupied) const;
|
Bitboard attackers_to(Square s, Bitboard occupied) const;
|
||||||
Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const;
|
void update_slider_blockers(Color c) const;
|
||||||
template<PieceType Pt> Bitboard attacks_by(Color c) const;
|
template<PieceType Pt>
|
||||||
|
Bitboard attacks_by(Color c) const;
|
||||||
|
|
||||||
// Properties of moves
|
// Properties of moves
|
||||||
bool legal(Move m) const;
|
bool legal(Move m) const;
|
||||||
@@ -130,21 +135,15 @@ public:
|
|||||||
Piece moved_piece(Move m) const;
|
Piece moved_piece(Move m) const;
|
||||||
Piece captured_piece() 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
|
// Doing and undoing moves
|
||||||
void do_move(Move m, StateInfo& newSt);
|
void do_move(Move m, StateInfo& newSt);
|
||||||
void do_move(Move m, StateInfo& newSt, bool givesCheck);
|
void do_move(Move m, StateInfo& newSt, bool givesCheck);
|
||||||
void undo_move(Move m);
|
void undo_move(Move m);
|
||||||
void do_null_move(StateInfo& newSt);
|
void do_null_move(StateInfo& newSt, TranspositionTable& tt);
|
||||||
void undo_null_move();
|
void undo_null_move();
|
||||||
|
|
||||||
// Static Exchange Evaluation
|
// Static Exchange Evaluation
|
||||||
bool see_ge(Move m, Value threshold = VALUE_ZERO) const;
|
bool see_ge(Move m, int threshold = 0) const;
|
||||||
bool see_ge(Move m, Bitboard& occupied, Value threshold = VALUE_ZERO) const;
|
|
||||||
|
|
||||||
// Accessing hash keys
|
// Accessing hash keys
|
||||||
Key key() const;
|
Key key() const;
|
||||||
@@ -156,13 +155,10 @@ public:
|
|||||||
Color side_to_move() const;
|
Color side_to_move() const;
|
||||||
int game_ply() const;
|
int game_ply() const;
|
||||||
bool is_chess960() const;
|
bool is_chess960() const;
|
||||||
Thread* this_thread() const;
|
|
||||||
bool is_draw(int ply) const;
|
bool is_draw(int ply) const;
|
||||||
bool has_game_cycle(int ply) const;
|
bool has_game_cycle(int ply) const;
|
||||||
bool has_repeated() const;
|
bool has_repeated() const;
|
||||||
int rule50_count() const;
|
int rule50_count() const;
|
||||||
Score psq_score() const;
|
|
||||||
Value psq_eg_stm() const;
|
|
||||||
Value non_pawn_material(Color c) const;
|
Value non_pawn_material(Color c) const;
|
||||||
Value non_pawn_material() const;
|
Value non_pawn_material() const;
|
||||||
|
|
||||||
@@ -197,75 +193,58 @@ private:
|
|||||||
int castlingRightsMask[SQUARE_NB];
|
int castlingRightsMask[SQUARE_NB];
|
||||||
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
Square castlingRookSquare[CASTLING_RIGHT_NB];
|
||||||
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
Bitboard castlingPath[CASTLING_RIGHT_NB];
|
||||||
Thread* thisThread;
|
|
||||||
StateInfo* st;
|
StateInfo* st;
|
||||||
int gamePly;
|
int gamePly;
|
||||||
Color sideToMove;
|
Color sideToMove;
|
||||||
Score psq;
|
|
||||||
bool chess960;
|
bool chess960;
|
||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
inline Color Position::side_to_move() const { return sideToMove; }
|
||||||
return sideToMove;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Piece Position::piece_on(Square s) const {
|
inline Piece Position::piece_on(Square s) const {
|
||||||
assert(is_ok(s));
|
assert(is_ok(s));
|
||||||
return board[s];
|
return board[s];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool Position::empty(Square s) const {
|
inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; }
|
||||||
return piece_on(s) == NO_PIECE;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Piece Position::moved_piece(Move m) const {
|
inline Piece Position::moved_piece(Move m) const { return piece_on(m.from_sq()); }
|
||||||
return piece_on(from_sq(m));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Bitboard Position::pieces(PieceType pt = ALL_PIECES) const {
|
inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; }
|
||||||
return byTypeBB[pt];
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... PieceTypes>
|
template<typename... PieceTypes>
|
||||||
inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const {
|
inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const {
|
||||||
return pieces(pt) | pieces(pts...);
|
return pieces(pt) | pieces(pts...);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard Position::pieces(Color c) const {
|
inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; }
|
||||||
return byColorBB[c];
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... PieceTypes>
|
template<typename... PieceTypes>
|
||||||
inline Bitboard Position::pieces(Color c, PieceTypes... pts) const {
|
inline Bitboard Position::pieces(Color c, PieceTypes... pts) const {
|
||||||
return pieces(c) & pieces(pts...);
|
return pieces(c) & pieces(pts...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<PieceType Pt> inline int Position::count(Color c) const {
|
template<PieceType Pt>
|
||||||
|
inline int Position::count(Color c) const {
|
||||||
return pieceCount[make_piece(c, Pt)];
|
return pieceCount[make_piece(c, Pt)];
|
||||||
}
|
}
|
||||||
|
|
||||||
template<PieceType Pt> inline int Position::count() const {
|
template<PieceType Pt>
|
||||||
|
inline int Position::count() const {
|
||||||
return count<Pt>(WHITE) + count<Pt>(BLACK);
|
return count<Pt>(WHITE) + count<Pt>(BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<PieceType Pt> inline Square Position::square(Color c) const {
|
template<PieceType Pt>
|
||||||
|
inline Square Position::square(Color c) const {
|
||||||
assert(count<Pt>(c) == 1);
|
assert(count<Pt>(c) == 1);
|
||||||
return lsb(pieces(c, Pt));
|
return lsb(pieces(c, Pt));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Square Position::ep_square() const {
|
inline Square Position::ep_square() const { return st->epSquare; }
|
||||||
return st->epSquare;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool Position::is_on_semiopen_file(Color c, Square s) const {
|
inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; }
|
||||||
return !(pieces(c, PAWN) & file_bb(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool Position::can_castle(CastlingRights cr) const {
|
|
||||||
return st->castlingRights & cr;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline CastlingRights Position::castling_rights(Color c) const {
|
inline CastlingRights Position::castling_rights(Color c) const {
|
||||||
return c & CastlingRights(st->castlingRights);
|
return c & CastlingRights(st->castlingRights);
|
||||||
@@ -273,19 +252,15 @@ inline CastlingRights Position::castling_rights(Color c) const {
|
|||||||
|
|
||||||
inline bool Position::castling_impeded(CastlingRights cr) const {
|
inline bool Position::castling_impeded(CastlingRights cr) const {
|
||||||
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
|
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
|
||||||
|
|
||||||
return pieces() & castlingPath[cr];
|
return pieces() & castlingPath[cr];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Square Position::castling_rook_square(CastlingRights cr) const {
|
inline Square Position::castling_rook_square(CastlingRights cr) const {
|
||||||
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
|
assert(cr == WHITE_OO || cr == WHITE_OOO || cr == BLACK_OO || cr == BLACK_OOO);
|
||||||
|
|
||||||
return castlingRookSquare[cr];
|
return castlingRookSquare[cr];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard Position::attackers_to(Square s) const {
|
inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); }
|
||||||
return attackers_to(s, pieces());
|
|
||||||
}
|
|
||||||
|
|
||||||
template<PieceType Pt>
|
template<PieceType Pt>
|
||||||
inline Bitboard Position::attacks_by(Color c) const {
|
inline Bitboard Position::attacks_by(Color c) const {
|
||||||
@@ -303,104 +278,51 @@ inline Bitboard Position::attacks_by(Color c) const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Bitboard Position::checkers() const {
|
inline Bitboard Position::checkers() const { return st->checkersBB; }
|
||||||
return st->checkersBB;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Bitboard Position::blockers_for_king(Color c) const {
|
inline Bitboard Position::blockers_for_king(Color c) const { return st->blockersForKing[c]; }
|
||||||
return st->blockersForKing[c];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Bitboard Position::pinners(Color c) const {
|
inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; }
|
||||||
return st->pinners[c];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Bitboard Position::check_squares(PieceType pt) const {
|
inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; }
|
||||||
return st->checkSquares[pt];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool Position::pawn_passed(Color c, Square s) const {
|
inline Key Position::key() const { return adjust_key50<false>(st->key); }
|
||||||
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 adjust_key50<false>(st->key);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<bool AfterMove>
|
template<bool AfterMove>
|
||||||
inline Key Position::adjust_key50(Key k) const
|
inline Key Position::adjust_key50(Key k) const {
|
||||||
{
|
return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
|
||||||
return st->rule50 < 14 - AfterMove
|
|
||||||
? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Key Position::pawn_key() const {
|
inline Key Position::pawn_key() const { return st->pawnKey; }
|
||||||
return st->pawnKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Key Position::material_key() const {
|
inline Key Position::material_key() const { return st->materialKey; }
|
||||||
return st->materialKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Score Position::psq_score() const {
|
inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; }
|
||||||
return psq;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Value Position::psq_eg_stm() const {
|
|
||||||
return (sideToMove == WHITE ? 1 : -1) * eg_value(psq);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Value Position::non_pawn_material(Color c) const {
|
|
||||||
return st->nonPawnMaterial[c];
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Value Position::non_pawn_material() const {
|
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 {
|
inline int Position::game_ply() const { return gamePly; }
|
||||||
return gamePly;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline int Position::rule50_count() const {
|
inline int Position::rule50_count() const { return st->rule50; }
|
||||||
return st->rule50;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool Position::opposite_bishops() const {
|
inline bool Position::is_chess960() const { return chess960; }
|
||||||
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(Move m) const {
|
inline bool Position::capture(Move m) const {
|
||||||
assert(is_ok(m));
|
assert(m.is_ok());
|
||||||
return (!empty(to_sq(m)) && type_of(m) != CASTLING)
|
return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT;
|
||||||
|| type_of(m) == EN_PASSANT;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if a move is generated from the capture stage
|
// Returns true if a move is generated from the capture stage, having also
|
||||||
// having also queen promotions covered, i.e. consistency with the capture stage move generation
|
// queen promotions covered, i.e. consistency with the capture stage move generation
|
||||||
// is needed to avoid the generation of duplicate moves.
|
// is needed to avoid the generation of duplicate moves.
|
||||||
inline bool Position::capture_stage(Move m) const {
|
inline bool Position::capture_stage(Move m) const {
|
||||||
assert(is_ok(m));
|
assert(m.is_ok());
|
||||||
return capture(m) || promotion_type(m) == QUEEN;
|
return capture(m) || m.promotion_type() == QUEEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Piece Position::captured_piece() const {
|
inline Piece Position::captured_piece() const { return st->capturedPiece; }
|
||||||
return st->capturedPiece;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Thread* Position::this_thread() const {
|
|
||||||
return thisThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void Position::put_piece(Piece pc, Square s) {
|
inline void Position::put_piece(Piece pc, Square s) {
|
||||||
|
|
||||||
@@ -409,7 +331,6 @@ inline void Position::put_piece(Piece pc, Square s) {
|
|||||||
byColorBB[color_of(pc)] |= s;
|
byColorBB[color_of(pc)] |= s;
|
||||||
pieceCount[pc]++;
|
pieceCount[pc]++;
|
||||||
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
|
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
|
||||||
psq += PSQT::psq[pc][s];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Position::remove_piece(Square s) {
|
inline void Position::remove_piece(Square s) {
|
||||||
@@ -421,7 +342,6 @@ inline void Position::remove_piece(Square s) {
|
|||||||
board[s] = NO_PIECE;
|
board[s] = NO_PIECE;
|
||||||
pieceCount[pc]--;
|
pieceCount[pc]--;
|
||||||
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
|
pieceCount[make_piece(color_of(pc), ALL_PIECES)]--;
|
||||||
psq -= PSQT::psq[pc][s];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Position::move_piece(Square from, Square to) {
|
inline void Position::move_piece(Square from, Square to) {
|
||||||
@@ -433,17 +353,11 @@ inline void Position::move_piece(Square from, Square to) {
|
|||||||
byColorBB[color_of(pc)] ^= fromTo;
|
byColorBB[color_of(pc)] ^= fromTo;
|
||||||
board[from] = NO_PIECE;
|
board[from] = NO_PIECE;
|
||||||
board[to] = pc;
|
board[to] = pc;
|
||||||
psq += PSQT::psq[pc][to] - PSQT::psq[pc][from];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void Position::do_move(Move m, StateInfo& newSt) {
|
inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); }
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
-131
@@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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
|
|
||||||
-38
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
||||||
Copyright (C) 2004-2023 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 PSQT_H_INCLUDED
|
|
||||||
#define PSQT_H_INCLUDED
|
|
||||||
|
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace Stockfish::PSQT
|
|
||||||
{
|
|
||||||
|
|
||||||
extern Score psq[PIECE_NB][SQUARE_NB];
|
|
||||||
|
|
||||||
// Fill psqt array from a set of internally linked parameters
|
|
||||||
void init();
|
|
||||||
|
|
||||||
} // namespace Stockfish::PSQT
|
|
||||||
|
|
||||||
|
|
||||||
#endif // PSQT_H_INCLUDED
|
|
||||||
+847
-836
File diff suppressed because it is too large
Load Diff
+207
-27
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,23 +19,46 @@
|
|||||||
#ifndef SEARCH_H_INCLUDED
|
#ifndef SEARCH_H_INCLUDED
|
||||||
#define SEARCH_H_INCLUDED
|
#define SEARCH_H_INCLUDED
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "cluster.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "movepick.h"
|
#include "movepick.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "syzygy/tbprobe.h"
|
||||||
|
#include "timeman.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
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 {
|
namespace Search {
|
||||||
|
|
||||||
|
// Stack struct keeps track of the information we need to remember from nodes
|
||||||
/// 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
|
||||||
/// shallower and deeper in the tree during the search. Each search thread has
|
// its own array of Stack objects, indexed by the current ply.
|
||||||
/// its own array of Stack objects, indexed by the current ply.
|
|
||||||
|
|
||||||
struct Stack {
|
struct Stack {
|
||||||
Move* pv;
|
Move* pv;
|
||||||
PieceToHistory* continuationHistory;
|
PieceToHistory* continuationHistory;
|
||||||
@@ -49,25 +72,26 @@ struct Stack {
|
|||||||
bool inCheck;
|
bool inCheck;
|
||||||
bool ttPv;
|
bool ttPv;
|
||||||
bool ttHit;
|
bool ttHit;
|
||||||
int doubleExtensions;
|
int multipleExtensions;
|
||||||
int cutoffCnt;
|
int cutoffCnt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// RootMove struct is used for moves at the root of the tree. For each root move
|
// 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
|
// 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.
|
// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.
|
||||||
|
|
||||||
struct RootMove {
|
struct RootMove {
|
||||||
|
|
||||||
explicit RootMove(Move m) : pv(1, m) {}
|
explicit RootMove(Move m) :
|
||||||
bool extract_ponder_from_tt(Position& pos);
|
pv(1, m) {}
|
||||||
|
bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos);
|
||||||
bool operator==(const Move& m) const { return pv[0] == m; }
|
bool operator==(const Move& m) const { return pv[0] == m; }
|
||||||
bool operator<(const RootMove& m) const { // Sort in descending order
|
// Sort in descending order
|
||||||
return m.score != score ? m.score < score
|
bool operator<(const RootMove& m) const {
|
||||||
: m.previousScore < previousScore;
|
return m.score != score ? m.score < score : m.previousScore < previousScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t effort = 0;
|
||||||
Value score = -VALUE_INFINITE;
|
Value score = -VALUE_INFINITE;
|
||||||
Value previousScore = -VALUE_INFINITE;
|
Value previousScore = -VALUE_INFINITE;
|
||||||
Value averageScore = -VALUE_INFINITE;
|
Value averageScore = -VALUE_INFINITE;
|
||||||
@@ -83,32 +107,188 @@ struct RootMove {
|
|||||||
using RootMoves = std::vector<RootMove>;
|
using RootMoves = std::vector<RootMove>;
|
||||||
|
|
||||||
|
|
||||||
/// LimitsType struct stores information sent by GUI about available time to
|
// 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.
|
// search the current move, maximum depth/time, or if we are in analysis mode.
|
||||||
|
|
||||||
struct LimitsType {
|
struct LimitsType {
|
||||||
|
|
||||||
LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC
|
// 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);
|
time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = npmsec = movetime = TimePoint(0);
|
||||||
movestogo = depth = mate = perft = infinite = 0;
|
movestogo = depth = mate = perft = infinite = 0;
|
||||||
nodes = 0;
|
nodes = 0;
|
||||||
|
ponderMode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool use_time_management() const {
|
bool use_time_management() const { return Cluster::is_root() && (time[WHITE] || time[BLACK]); }
|
||||||
return time[WHITE] || time[BLACK];
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Move> searchmoves;
|
std::vector<Move> searchmoves;
|
||||||
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
|
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
|
||||||
int movestogo, depth, mate, perft, infinite;
|
int movestogo, depth, mate, perft, infinite;
|
||||||
int64_t nodes;
|
uint64_t nodes;
|
||||||
|
bool ponderMode;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern LimitsType Limits;
|
|
||||||
|
|
||||||
void init();
|
// 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) {}
|
||||||
|
|
||||||
|
|
||||||
|
const OptionsMap& options;
|
||||||
|
ThreadPool& threads;
|
||||||
|
TranspositionTable& tt;
|
||||||
|
const Eval::NNUE::Networks& networks;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Worker;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
void check_time(Search::Worker& worker) override;
|
||||||
|
|
||||||
|
std::string 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
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;
|
||||||
|
|
||||||
|
#ifdef USE_MPI
|
||||||
|
struct {
|
||||||
|
std::mutex mutex;
|
||||||
|
Cluster::TTCache<Cluster::TTCacheSize> buffer = {};
|
||||||
|
} ttCache;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::atomic<uint64_t> TTsaves;
|
||||||
|
|
||||||
|
friend void Cluster::save(TranspositionTable&,
|
||||||
|
ThreadPool&,
|
||||||
|
Search::Worker*,
|
||||||
|
TTEntry* tte,
|
||||||
|
Key k,
|
||||||
|
Value v,
|
||||||
|
bool PvHit,
|
||||||
|
Bound b,
|
||||||
|
Depth d,
|
||||||
|
Move m,
|
||||||
|
Value ev,
|
||||||
|
uint8_t generation8);
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
friend class Stockfish::ThreadPool;
|
||||||
|
friend class SearchManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace Search
|
} // namespace Search
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|||||||
+311
-195
File diff suppressed because it is too large
Load Diff
+26
-27
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,12 +19,31 @@
|
|||||||
#ifndef TBPROBE_H
|
#ifndef TBPROBE_H
|
||||||
#define 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 {
|
namespace Stockfish::Tablebases {
|
||||||
|
|
||||||
|
struct Config {
|
||||||
|
int cardinality = 0;
|
||||||
|
bool rootInTB = false;
|
||||||
|
bool useRule50 = false;
|
||||||
|
Depth probeDepth = 0;
|
||||||
|
};
|
||||||
|
|
||||||
enum WDLScore {
|
enum WDLScore {
|
||||||
WDLLoss = -2, // Loss
|
WDLLoss = -2, // Loss
|
||||||
WDLBlessedLoss = -1, // Loss, but draw under 50-move rule
|
WDLBlessedLoss = -1, // Loss, but draw under 50-move rule
|
||||||
@@ -43,33 +62,13 @@ enum ProbeState {
|
|||||||
|
|
||||||
extern int MaxCardinality;
|
extern int MaxCardinality;
|
||||||
|
|
||||||
|
|
||||||
void init(const std::string& paths);
|
void init(const std::string& paths);
|
||||||
WDLScore probe_wdl(Position& pos, ProbeState* result);
|
WDLScore probe_wdl(Position& pos, ProbeState* result);
|
||||||
int probe_dtz(Position& pos, ProbeState* result);
|
int probe_dtz(Position& pos, ProbeState* result);
|
||||||
bool root_probe(Position& pos, Search::RootMoves& rootMoves);
|
bool root_probe(Position& pos, Search::RootMoves& rootMoves, bool rule50);
|
||||||
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves);
|
bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50);
|
||||||
void rank_root_moves(Position& pos, Search::RootMoves& rootMoves);
|
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
|
||||||
|
|
||||||
|
|||||||
+137
-93
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,33 +16,43 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
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 "cluster.h"
|
||||||
|
#include "misc.h"
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread.h"
|
|
||||||
#include "uci.h"
|
|
||||||
#include "syzygy/tbprobe.h"
|
#include "syzygy/tbprobe.h"
|
||||||
|
#include "timeman.h"
|
||||||
#include "tt.h"
|
#include "tt.h"
|
||||||
|
#include "types.h"
|
||||||
|
#include "ucioption.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
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,
|
||||||
/// Thread constructor launches the thread and waits until it goes to sleep
|
std::unique_ptr<Search::ISearchManager> sm,
|
||||||
/// in idle_loop(). Note that 'searching' and 'exit' should be already set.
|
size_t n) :
|
||||||
|
worker(std::make_unique<Search::Worker>(sharedState, std::move(sm), n)),
|
||||||
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) {
|
idx(n),
|
||||||
|
nthreads(sharedState.options["Threads"]),
|
||||||
|
stdThread(&Thread::idle_loop, this) {
|
||||||
|
|
||||||
wait_for_search_finished();
|
wait_for_search_finished();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread destructor wakes up the thread in idle_loop() and waits
|
// Destructor wakes up the thread in idle_loop() and waits
|
||||||
/// for its termination. Thread should be already waiting.
|
// for its termination. Thread should be already waiting.
|
||||||
|
|
||||||
Thread::~Thread() {
|
Thread::~Thread() {
|
||||||
|
|
||||||
assert(!searching);
|
assert(!searching);
|
||||||
@@ -53,24 +63,7 @@ Thread::~Thread() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::clear() reset histories, usually before a new game
|
// Wakes up the thread that will start the search
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Thread::start_searching() wakes up the thread that will start the search
|
|
||||||
|
|
||||||
void Thread::start_searching() {
|
void Thread::start_searching() {
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
searching = true;
|
searching = true;
|
||||||
@@ -79,9 +72,8 @@ void Thread::start_searching() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::wait_for_search_finished() blocks on the condition variable
|
// Blocks on the condition variable
|
||||||
/// until the thread has finished searching.
|
// until the thread has finished searching.
|
||||||
|
|
||||||
void Thread::wait_for_search_finished() {
|
void Thread::wait_for_search_finished() {
|
||||||
|
|
||||||
std::unique_lock<std::mutex> lk(mutex);
|
std::unique_lock<std::mutex> lk(mutex);
|
||||||
@@ -89,18 +81,18 @@ void Thread::wait_for_search_finished() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Thread::idle_loop() is where the thread is parked, blocked on the
|
// Thread gets parked here, blocked on the
|
||||||
/// condition variable, when it has no work to do.
|
// condition variable, when it has no work to do.
|
||||||
|
|
||||||
void Thread::idle_loop() {
|
void Thread::idle_loop() {
|
||||||
|
|
||||||
// If OS already scheduled us on a different group than 0 then don't overwrite
|
// 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
|
// 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,
|
// 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
|
// just check if running threads are below a threshold, in this case, all this
|
||||||
// NUMA machinery is not needed.
|
// NUMA machinery is not needed.
|
||||||
if (Options["Threads"] > 8)
|
if (nthreads > 8)
|
||||||
WinProcGroup::bindThisThread(idx);
|
WinProcGroup::bind_this_thread(idx);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -114,67 +106,84 @@ void Thread::idle_loop() {
|
|||||||
|
|
||||||
lk.unlock();
|
lk.unlock();
|
||||||
|
|
||||||
search();
|
worker->start_searching();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ThreadPool::set() creates/destroys threads to match the requested number.
|
Search::SearchManager* ThreadPool::main_manager() {
|
||||||
/// Created and launched threads will immediately go to sleep in idle_loop.
|
return static_cast<Search::SearchManager*>(main_thread()->worker.get()->manager.get());
|
||||||
/// Upon resizing, threads are recreated to allow for binding if necessary.
|
}
|
||||||
|
|
||||||
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); }
|
||||||
|
uint64_t ThreadPool::TT_saves() const { return accumulate(&Search::Worker::TTsaves); }
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
|
||||||
if (threads.size() > 0) // destroy any existing thread(s)
|
if (threads.size() > 0) // destroy any existing thread(s)
|
||||||
{
|
{
|
||||||
main()->wait_for_search_finished();
|
main_thread()->wait_for_search_finished();
|
||||||
|
|
||||||
while (threads.size() > 0)
|
while (threads.size() > 0)
|
||||||
delete threads.back(), threads.pop_back();
|
delete threads.back(), threads.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const size_t requested = sharedState.options["Threads"];
|
||||||
|
|
||||||
if (requested > 0) // create new thread(s)
|
if (requested > 0) // create new thread(s)
|
||||||
{
|
{
|
||||||
threads.push_back(new MainThread(0));
|
threads.push_back(new Thread(
|
||||||
|
sharedState, std::unique_ptr<Search::ISearchManager>(new Search::SearchManager()), 0));
|
||||||
|
|
||||||
|
|
||||||
while (threads.size() < requested)
|
while (threads.size() < requested)
|
||||||
threads.push_back(new Thread(threads.size()));
|
threads.push_back(new Thread(
|
||||||
|
sharedState, std::unique_ptr<Search::ISearchManager>(new Search::NullSearchManager()),
|
||||||
|
threads.size()));
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
|
main_thread()->wait_for_search_finished();
|
||||||
|
|
||||||
// Reallocate the hash with the new threadpool size
|
// Reallocate the hash with the new threadpool size
|
||||||
TT.resize(size_t(Options["Hash"]));
|
sharedState.tt.resize(sharedState.options["Hash"], requested);
|
||||||
|
|
||||||
// Init thread number dependent search params.
|
// Adjust cluster buffers
|
||||||
Search::init();
|
Cluster::ttSendRecvBuff_resize(requested);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// ThreadPool::clear() sets threadPool data to initial values
|
// Sets threadPool data to initial values
|
||||||
|
|
||||||
void ThreadPool::clear() {
|
void ThreadPool::clear() {
|
||||||
|
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
th->clear();
|
th->worker->clear();
|
||||||
|
|
||||||
main()->callsCnt = 0;
|
main_manager()->callsCnt = 0;
|
||||||
main()->bestPreviousScore = VALUE_INFINITE;
|
main_manager()->bestPreviousScore = VALUE_INFINITE;
|
||||||
main()->bestPreviousAverageScore = VALUE_INFINITE;
|
main_manager()->bestPreviousAverageScore = VALUE_INFINITE;
|
||||||
main()->previousTimeReduction = 1.0;
|
main_manager()->previousTimeReduction = 1.0;
|
||||||
|
main_manager()->tm.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
|
// Wakes up main thread waiting in idle_loop() and
|
||||||
/// returns immediately. Main thread will wake up other threads and start the search.
|
// 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,
|
main_thread()->wait_for_search_finished();
|
||||||
const Search::LimitsType& limits, bool ponderMode) {
|
|
||||||
|
|
||||||
main()->wait_for_search_finished();
|
main_manager()->stopOnPonderhit = stop = abortedSearch = false;
|
||||||
|
main_manager()->ponder = limits.ponderMode;
|
||||||
|
|
||||||
main()->stopOnPonderhit = stop = false;
|
|
||||||
increaseDepth = true;
|
increaseDepth = true;
|
||||||
main()->ponder = ponderMode;
|
|
||||||
Search::Limits = limits;
|
|
||||||
Search::RootMoves rootMoves;
|
Search::RootMoves rootMoves;
|
||||||
|
|
||||||
for (const auto& m : MoveList<LEGAL>(pos))
|
for (const auto& m : MoveList<LEGAL>(pos))
|
||||||
@@ -182,8 +191,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|
|||||||
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
|
||||||
rootMoves.emplace_back(m);
|
rootMoves.emplace_back(m);
|
||||||
|
|
||||||
if (!rootMoves.empty())
|
Tablebases::Config tbConfig = Tablebases::rank_root_moves(options, pos, rootMoves);
|
||||||
Tablebases::rank_root_moves(pos, rootMoves);
|
|
||||||
|
|
||||||
// After ownership transfer 'states' becomes empty, so if we stop the search
|
// After ownership transfer 'states' becomes empty, so if we stop the search
|
||||||
// and call 'go' again without setting a new position states.get() == nullptr.
|
// and call 'go' again without setting a new position states.get() == nullptr.
|
||||||
@@ -199,55 +207,91 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
|
|||||||
// since they are read-only.
|
// since they are read-only.
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
{
|
{
|
||||||
th->nodes = th->tbHits = th->nmpMinPly = th->bestMoveChanges = 0;
|
th->worker->limits = limits;
|
||||||
th->rootDepth = th->completedDepth = 0;
|
th->worker->nodes = th->worker->tbHits = th->worker->nmpMinPly =
|
||||||
th->rootMoves = rootMoves;
|
th->worker->bestMoveChanges = 0;
|
||||||
th->rootPos.set(pos.fen(), pos.is_chess960(), &th->rootState, th);
|
th->worker->TTsaves = 0;
|
||||||
th->rootState = setupStates->back();
|
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()->start_searching();
|
Cluster::signals_init();
|
||||||
|
|
||||||
|
main_thread()->start_searching();
|
||||||
}
|
}
|
||||||
|
|
||||||
Thread* ThreadPool::get_best_thread() const {
|
Thread* ThreadPool::get_best_thread() const {
|
||||||
|
|
||||||
Thread* bestThread = threads.front();
|
Thread* bestThread = threads.front();
|
||||||
std::map<Move, int64_t> votes;
|
|
||||||
Value minScore = VALUE_NONE;
|
Value minScore = VALUE_NONE;
|
||||||
|
|
||||||
// Find minimum score of all threads
|
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)
|
for (Thread* th : threads)
|
||||||
minScore = std::min(minScore, th->rootMoves[0].score);
|
minScore = std::min(minScore, th->worker->rootMoves[0].score);
|
||||||
|
|
||||||
// Vote according to score and depth, and select the best thread
|
// Vote according to score and depth, and select the best thread
|
||||||
auto thread_value = [minScore](Thread* th) {
|
auto thread_voting_value = [minScore](Thread* th) {
|
||||||
return (th->rootMoves[0].score - minScore + 14) * int(th->completedDepth);
|
return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth);
|
||||||
};
|
};
|
||||||
|
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
votes[th->rootMoves[0].pv[0]] += thread_value(th);
|
votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th);
|
||||||
|
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
if (abs(bestThread->rootMoves[0].score) >= VALUE_TB_WIN_IN_MAX_PLY)
|
|
||||||
{
|
{
|
||||||
// Make sure we pick the shortest mate / TB conversion or stave off mate the longest
|
const auto bestThreadScore = bestThread->worker->rootMoves[0].score;
|
||||||
if (th->rootMoves[0].score > bestThread->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
|
||||||
|
if (newThreadScore > bestThreadScore)
|
||||||
bestThread = th;
|
bestThread = th;
|
||||||
}
|
}
|
||||||
else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|
else if (bestThreadInProvenLoss)
|
||||||
|| ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
|
{
|
||||||
&& ( votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]
|
// Make sure we pick the shortest mated / TB conversion
|
||||||
|| ( votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]]
|
if (newThreadInProvenLoss && newThreadScore < bestThreadScore)
|
||||||
&& thread_value(th) * int(th->rootMoves[0].pv.size() > 2)
|
|
||||||
> thread_value(bestThread) * int(bestThread->rootMoves[0].pv.size() > 2)))))
|
|
||||||
bestThread = th;
|
bestThread = th;
|
||||||
|
}
|
||||||
|
else if (newThreadInProvenWin || newThreadInProvenLoss
|
||||||
|
|| (newThreadScore > VALUE_TB_LOSS_IN_MAX_PLY
|
||||||
|
&& (newThreadMoveVote > bestThreadMoveVote
|
||||||
|
|| (newThreadMoveVote == bestThreadMoveVote && betterVotingValue))))
|
||||||
|
bestThread = th;
|
||||||
|
}
|
||||||
|
|
||||||
return bestThread;
|
return bestThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Start non-main threads
|
// Start non-main threads
|
||||||
|
// Will be invoked by main thread after it has started searching
|
||||||
void ThreadPool::start_searching() {
|
void ThreadPool::start_searching() {
|
||||||
|
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
@@ -256,7 +300,7 @@ void ThreadPool::start_searching() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Wait for non-main threads
|
// Wait for non-main threads
|
||||||
|
|
||||||
void ThreadPool::wait_for_search_finished() const {
|
void ThreadPool::wait_for_search_finished() const {
|
||||||
|
|
||||||
|
|||||||
+45
-65
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,98 +21,80 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "material.h"
|
|
||||||
#include "movepick.h"
|
#include "movepick.h"
|
||||||
#include "pawns.h"
|
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread_win32_osx.h"
|
#include "thread_win32_osx.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
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 {
|
class Thread {
|
||||||
|
|
||||||
std::mutex mutex;
|
|
||||||
std::condition_variable cv;
|
|
||||||
size_t idx;
|
|
||||||
bool exit = false, searching = true; // Set before starting std::thread
|
|
||||||
NativeThread stdThread;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Thread(size_t);
|
Thread(Search::SharedState&, std::unique_ptr<Search::ISearchManager>, size_t);
|
||||||
virtual ~Thread();
|
virtual ~Thread();
|
||||||
virtual void search();
|
|
||||||
void clear();
|
|
||||||
void idle_loop();
|
void idle_loop();
|
||||||
void start_searching();
|
void start_searching();
|
||||||
void wait_for_search_finished();
|
void wait_for_search_finished();
|
||||||
size_t id() const { return idx; }
|
size_t id() const { return idx; }
|
||||||
|
|
||||||
Pawns::Table pawnsTable;
|
std::unique_ptr<Search::Worker> worker;
|
||||||
Material::Table materialTable;
|
|
||||||
size_t pvIdx, pvLast;
|
|
||||||
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
|
|
||||||
int selDepth, nmpMinPly;
|
|
||||||
Value bestValue, optimism[COLOR_NB];
|
|
||||||
|
|
||||||
Position rootPos;
|
private:
|
||||||
StateInfo rootState;
|
std::mutex mutex;
|
||||||
Search::RootMoves rootMoves;
|
std::condition_variable cv;
|
||||||
Depth rootDepth, completedDepth;
|
size_t idx, nthreads;
|
||||||
Value rootDelta;
|
bool exit = false, searching = true; // Set before starting std::thread
|
||||||
CounterMoveHistory counterMoves;
|
NativeThread stdThread;
|
||||||
ButterflyHistory mainHistory;
|
|
||||||
CapturePieceToHistory captureHistory;
|
|
||||||
ContinuationHistory continuationHistory[2][2];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// 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 start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType);
|
||||||
void check_time();
|
|
||||||
|
|
||||||
double previousTimeReduction;
|
|
||||||
Value bestPreviousScore;
|
|
||||||
Value bestPreviousAverageScore;
|
|
||||||
Value iterValue[4];
|
|
||||||
int callsCnt;
|
|
||||||
bool stopOnPonderhit;
|
|
||||||
std::atomic_bool ponder;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/// 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 {
|
|
||||||
|
|
||||||
void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false);
|
|
||||||
void clear();
|
void clear();
|
||||||
void set(size_t);
|
void set(Search::SharedState);
|
||||||
|
|
||||||
MainThread* main() const { return static_cast<MainThread*>(threads.front()); }
|
Search::SearchManager* main_manager();
|
||||||
uint64_t nodes_searched() const { return accumulate(&Thread::nodes); }
|
Thread* main_thread() const { return threads.front(); }
|
||||||
uint64_t tb_hits() const { return accumulate(&Thread::tbHits); }
|
uint64_t nodes_searched() const;
|
||||||
|
uint64_t tb_hits() const;
|
||||||
|
uint64_t TT_saves() const;
|
||||||
Thread* get_best_thread() const;
|
Thread* get_best_thread() const;
|
||||||
void start_searching();
|
void start_searching();
|
||||||
void wait_for_search_finished() const;
|
void wait_for_search_finished() const;
|
||||||
|
|
||||||
std::atomic_bool stop, increaseDepth;
|
std::atomic_bool stop, abortedSearch, increaseDepth;
|
||||||
|
|
||||||
auto cbegin() const noexcept { return threads.cbegin(); }
|
auto cbegin() const noexcept { return threads.cbegin(); }
|
||||||
auto begin() noexcept { return threads.begin(); }
|
auto begin() noexcept { return threads.begin(); }
|
||||||
@@ -125,17 +107,15 @@ private:
|
|||||||
StateListPtr setupStates;
|
StateListPtr setupStates;
|
||||||
std::vector<Thread*> threads;
|
std::vector<Thread*> threads;
|
||||||
|
|
||||||
uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {
|
uint64_t accumulate(std::atomic<uint64_t> Search::Worker::*member) const {
|
||||||
|
|
||||||
uint64_t sum = 0;
|
uint64_t sum = 0;
|
||||||
for (Thread* th : threads)
|
for (Thread* th : threads)
|
||||||
sum += (th->*member).load(std::memory_order_relaxed);
|
sum += (th->worker.get()->*member).load(std::memory_order_relaxed);
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern ThreadPool Threads;
|
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef THREAD_H_INCLUDED
|
#endif // #ifndef THREAD_H_INCLUDED
|
||||||
|
|||||||
+25
-21
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -21,41 +21,45 @@
|
|||||||
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
/// On OSX threads other than the main thread are created with a reduced stack
|
// 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
|
// 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.
|
// somewhat more than 1MB stack, so adjust it to TH_STACK_SIZE.
|
||||||
/// The implementation calls pthread_create() with the stack size parameter
|
// The implementation calls pthread_create() with the stack size parameter
|
||||||
/// equal to the linux 8MB default, on platforms that support it.
|
// equal to the Linux 8MB default, on platforms that support it.
|
||||||
|
|
||||||
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
|
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
namespace Stockfish {
|
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 nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
class NativeThread {
|
class NativeThread {
|
||||||
|
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
|
|
||||||
|
static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
template<class T, class P = std::pair<T*, void(T::*)()>>
|
template<class Function, class... Args>
|
||||||
explicit NativeThread(void(T::*fun)(), T* obj) {
|
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_t attr_storage, *attr = &attr_storage;
|
||||||
pthread_attr_init(attr);
|
pthread_attr_init(attr);
|
||||||
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
pthread_attr_setstacksize(attr, TH_STACK_SIZE);
|
||||||
pthread_create(&thread, attr, start_routine<T>, new P(obj, fun));
|
|
||||||
|
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); }
|
void join() { pthread_join(thread, nullptr); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+56
-33
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,35 +16,49 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "timeman.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cfloat>
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "timeman.h"
|
#include "ucioption.h"
|
||||||
#include "uci.h"
|
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
TimeManagement Time; // Our global time management object
|
TimePoint TimeManagement::optimum() const { return optimumTime; }
|
||||||
|
TimePoint TimeManagement::maximum() const { return maximumTime; }
|
||||||
|
TimePoint TimeManagement::elapsed(size_t nodes) const {
|
||||||
|
return useNodesTime ? TimePoint(nodes) : now() - startTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimeManagement::clear() {
|
||||||
|
availableNodes = 0; // When in 'nodes as time' mode
|
||||||
|
}
|
||||||
|
|
||||||
/// TimeManagement::init() is called at the beginning of the search and calculates
|
void TimeManagement::advance_nodes_time(std::int64_t nodes) {
|
||||||
/// the bounds of time allowed for the current game ply. We currently support:
|
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)
|
// 1) x basetime (+ z increment)
|
||||||
// 2) x moves in y seconds (+ z increment)
|
// 2) x moves in y seconds (+ z increment)
|
||||||
|
void TimeManagement::init(Search::LimitsType& limits,
|
||||||
void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
Color us,
|
||||||
|
int ply,
|
||||||
// if we have no time, no need to initialize TM, except for the start time,
|
const OptionsMap& options) {
|
||||||
|
// If we have no time, no need to initialize TM, except for the start time,
|
||||||
// which is used by movetime.
|
// which is used by movetime.
|
||||||
startTime = limits.startTime;
|
startTime = limits.startTime;
|
||||||
if (limits.time[us] == 0)
|
if (limits.time[us] == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TimePoint moveOverhead = TimePoint(Options["Move Overhead"]);
|
TimePoint moveOverhead = TimePoint(options["Move Overhead"]);
|
||||||
TimePoint slowMover = TimePoint(Options["Slow Mover"]);
|
TimePoint npmsec = TimePoint(options["nodestime"]);
|
||||||
TimePoint npmsec = TimePoint(Options["nodestime"]);
|
|
||||||
|
|
||||||
// optScale is a percentage of available time to use for the current move.
|
// optScale is a percentage of available time to use for the current move.
|
||||||
// maxScale is a multiplier applied to optimumTime.
|
// maxScale is a multiplier applied to optimumTime.
|
||||||
@@ -56,6 +70,8 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||||||
// must be much lower than the real engine speed.
|
// must be much lower than the real engine speed.
|
||||||
if (npmsec)
|
if (npmsec)
|
||||||
{
|
{
|
||||||
|
useNodesTime = true;
|
||||||
|
|
||||||
if (!availableNodes) // Only once at game start
|
if (!availableNodes) // Only once at game start
|
||||||
availableNodes = npmsec * limits.time[us]; // Time is in msec
|
availableNodes = npmsec * limits.time[us]; // Time is in msec
|
||||||
|
|
||||||
@@ -68,41 +84,48 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
|
|||||||
// Maximum move horizon of 50 moves
|
// Maximum move horizon of 50 moves
|
||||||
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
|
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
|
// Make sure timeLeft is > 0 since we may use it as a divisor
|
||||||
TimePoint timeLeft = std::max(TimePoint(1),
|
TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1)
|
||||||
limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg));
|
- 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)
|
// x basetime (+ z increment)
|
||||||
// If there is a healthy increment, timeLeft can exceed actual available
|
// If there is a healthy increment, timeLeft can exceed the actual available
|
||||||
// game time for the current move, so also cap to 20% of available game time.
|
// game time for the current move, so also cap to a percentage of available game time.
|
||||||
if (limits.movestogo == 0)
|
if (limits.movestogo == 0)
|
||||||
{
|
{
|
||||||
optScale = std::min(0.0120 + std::pow(ply + 3.0, 0.45) * 0.0039,
|
// Use extra time with larger increments
|
||||||
0.2 * limits.time[us] / double(timeLeft))
|
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;
|
* optExtra;
|
||||||
maxScale = std::min(7.0, 4.0 + ply / 12.0);
|
maxScale = std::min(6.64, maxConstant + ply / 12.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// x moves in y seconds (+ z increment)
|
// x moves in y seconds (+ z increment)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
optScale = std::min((0.88 + ply / 116.4) / mtg,
|
optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft));
|
||||||
0.88 * limits.time[us] / double(timeLeft));
|
|
||||||
maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
|
maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Never use more than 80% of the available time for this move
|
// Limit the maximum possible time for this move
|
||||||
optimumTime = TimePoint(optScale * timeLeft);
|
optimumTime = TimePoint(optScale * timeLeft);
|
||||||
maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime));
|
maximumTime =
|
||||||
|
TimePoint(std::min(0.825 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
|
||||||
|
|
||||||
if (Options["Ponder"])
|
if (options["Ponder"])
|
||||||
optimumTime += optimumTime / 4;
|
optimumTime += optimumTime / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-13
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,32 +19,41 @@
|
|||||||
#ifndef TIMEMAN_H_INCLUDED
|
#ifndef TIMEMAN_H_INCLUDED
|
||||||
#define TIMEMAN_H_INCLUDED
|
#define TIMEMAN_H_INCLUDED
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "cluster.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "search.h"
|
|
||||||
#include "thread.h"
|
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
/// The TimeManagement class computes the optimal time to think depending on
|
class OptionsMap;
|
||||||
/// the maximum available time, the game move number and other parameters.
|
|
||||||
|
|
||||||
|
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 {
|
class TimeManagement {
|
||||||
public:
|
public:
|
||||||
void init(Search::LimitsType& limits, Color us, int ply);
|
void init(Search::LimitsType& limits, Color us, int ply, const OptionsMap& options);
|
||||||
TimePoint optimum() const { return optimumTime; }
|
|
||||||
TimePoint maximum() const { return maximumTime; }
|
|
||||||
TimePoint elapsed() const { return Search::Limits.npmsec ?
|
|
||||||
TimePoint(Threads.nodes_searched()) : now() - startTime; }
|
|
||||||
|
|
||||||
int64_t availableNodes; // When in 'nodes as time' mode
|
TimePoint optimum() const;
|
||||||
|
TimePoint maximum() const;
|
||||||
|
TimePoint elapsed(std::size_t nodes) const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
void advance_nodes_time(std::int64_t nodes);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TimePoint startTime;
|
TimePoint startTime;
|
||||||
TimePoint optimumTime;
|
TimePoint optimumTime;
|
||||||
TimePoint maximumTime;
|
TimePoint maximumTime;
|
||||||
};
|
|
||||||
|
|
||||||
extern TimeManagement Time;
|
std::int64_t availableNodes = 0; // When in 'nodes as time' mode
|
||||||
|
bool useNodesTime = false; // True if we are in 'nodes as time' mode
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
||||||
|
|||||||
+61
-64
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,54 +16,60 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
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 <iostream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "bitboard.h"
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "thread.h"
|
|
||||||
#include "tt.h"
|
|
||||||
#include "uci.h"
|
|
||||||
|
|
||||||
namespace Stockfish {
|
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.
|
||||||
/// TTEntry::save() populates the TTEntry with a new node's data, possibly
|
void TTEntry::save(
|
||||||
/// overwriting an old position. Update is not atomic and can be racy.
|
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {
|
||||||
|
|
||||||
void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev) {
|
|
||||||
|
|
||||||
// Preserve any existing move for the same position
|
// Preserve any existing move for the same position
|
||||||
if (m || (uint16_t)k != key16)
|
if (m || uint16_t(k) != key16)
|
||||||
move16 = (uint16_t)m;
|
move16 = m;
|
||||||
|
|
||||||
// Overwrite less valuable entries (cheapest checks first)
|
// Overwrite less valuable entries (cheapest checks first)
|
||||||
if ( b == BOUND_EXACT
|
if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4)
|
||||||
|| (uint16_t)k != key16
|
|
||||||
|| d - DEPTH_OFFSET + 2 * pv > depth8 - 4)
|
|
||||||
{
|
{
|
||||||
assert(d > DEPTH_OFFSET);
|
assert(d > DEPTH_OFFSET);
|
||||||
assert(d < 256 + DEPTH_OFFSET);
|
assert(d < 256 + DEPTH_OFFSET);
|
||||||
|
|
||||||
key16 = (uint16_t)k;
|
key16 = uint16_t(k);
|
||||||
depth8 = (uint8_t)(d - DEPTH_OFFSET);
|
depth8 = uint8_t(d - DEPTH_OFFSET);
|
||||||
genBound8 = (uint8_t)(TT.generation8 | uint8_t(pv) << 2 | b);
|
genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b);
|
||||||
value16 = (int16_t)v;
|
value16 = int16_t(v);
|
||||||
eval16 = (int16_t)ev;
|
eval16 = int16_t(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// TranspositionTable::resize() sets the size of the transposition table,
|
uint8_t TTEntry::relative_age(const uint8_t generation8) const {
|
||||||
/// measured in megabytes. Transposition table consists of a power of 2 number
|
// Due to our packed storage format for generation and its cyclic
|
||||||
/// of clusters and each cluster consists of ClusterSize number of TTEntry.
|
// 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) {
|
return (TranspositionTable::GENERATION_CYCLE + generation8 - genBound8)
|
||||||
|
& TranspositionTable::GENERATION_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
Threads.main()->wait_for_search_finished();
|
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
void TranspositionTable::resize(size_t mbSize, int threadCount) {
|
||||||
aligned_large_pages_free(table);
|
aligned_large_pages_free(table);
|
||||||
|
|
||||||
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
|
||||||
@@ -71,35 +77,29 @@ void TranspositionTable::resize(size_t mbSize) {
|
|||||||
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
|
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
|
||||||
if (!table)
|
if (!table)
|
||||||
{
|
{
|
||||||
std::cerr << "Failed to allocate " << mbSize
|
std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl;
|
||||||
<< "MB for transposition table." << std::endl;
|
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear();
|
clear(threadCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// TranspositionTable::clear() initializes the entire transposition table to zero,
|
// Initializes the entire transposition table to zero,
|
||||||
// in a multi-threaded way.
|
// in a multi-threaded way.
|
||||||
|
void TranspositionTable::clear(size_t threadCount) {
|
||||||
void TranspositionTable::clear() {
|
|
||||||
|
|
||||||
std::vector<std::thread> threads;
|
std::vector<std::thread> threads;
|
||||||
|
|
||||||
for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx)
|
for (size_t idx = 0; idx < size_t(threadCount); ++idx)
|
||||||
{
|
{
|
||||||
threads.emplace_back([this, idx]() {
|
threads.emplace_back([this, idx, threadCount]() {
|
||||||
|
|
||||||
// Thread binding gives faster search on systems with a first-touch policy
|
// Thread binding gives faster search on systems with a first-touch policy
|
||||||
if (Options["Threads"] > 8)
|
if (threadCount > 8)
|
||||||
WinProcGroup::bindThisThread(idx);
|
WinProcGroup::bind_this_thread(idx);
|
||||||
|
|
||||||
// Each thread will zero its part of the hash table
|
// Each thread will zero its part of the hash table
|
||||||
const size_t stride = size_t(clusterCount / Options["Threads"]),
|
const size_t stride = size_t(clusterCount / threadCount), start = size_t(stride * idx),
|
||||||
start = size_t(stride * idx),
|
len = idx != size_t(threadCount) - 1 ? stride : clusterCount - start;
|
||||||
len = idx != size_t(Options["Threads"]) - 1 ?
|
|
||||||
stride : clusterCount - start;
|
|
||||||
|
|
||||||
std::memset(&table[start], 0, len * sizeof(Cluster));
|
std::memset(&table[start], 0, len * sizeof(Cluster));
|
||||||
});
|
});
|
||||||
@@ -110,51 +110,48 @@ void TranspositionTable::clear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// TranspositionTable::probe() looks up the current position in the transposition
|
// Looks up the current position in the transposition
|
||||||
/// table. It returns true and a pointer to the TTEntry if the position is found.
|
// 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
|
// 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
|
// 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
|
// 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 t2 if its replace value is greater than that of t2.
|
||||||
|
|
||||||
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
|
TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
|
||||||
|
|
||||||
TTEntry* const tte = first_entry(key);
|
TTEntry* const tte = first_entry(key);
|
||||||
const uint16_t key16 = (uint16_t)key; // Use the low 16 bits as key inside the cluster
|
const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster
|
||||||
|
|
||||||
for (int i = 0; i < ClusterSize; ++i)
|
for (int i = 0; i < ClusterSize; ++i)
|
||||||
if (tte[i].key16 == key16 || !tte[i].depth8)
|
if (tte[i].key16 == key16 || !tte[i].depth8)
|
||||||
{
|
{
|
||||||
tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh
|
constexpr uint8_t lowerBits = GENERATION_DELTA - 1;
|
||||||
|
|
||||||
return found = (bool)tte[i].depth8, &tte[i];
|
// Refresh with new generation, keeping the lower bits the same.
|
||||||
|
tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & lowerBits));
|
||||||
|
return found = bool(tte[i].depth8), &tte[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find an entry to be replaced according to the replacement strategy
|
// Find an entry to be replaced according to the replacement strategy
|
||||||
TTEntry* replace = tte;
|
TTEntry* replace = tte;
|
||||||
for (int i = 1; i < ClusterSize; ++i)
|
for (int i = 1; i < ClusterSize; ++i)
|
||||||
// Due to our packed storage format for generation and its cyclic
|
if (replace->depth8 - replace->relative_age(generation8) * 2
|
||||||
// nature we add GENERATION_CYCLE (256 is the modulus, plus what
|
> tte[i].depth8 - tte[i].relative_age(generation8) * 2)
|
||||||
// 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];
|
replace = &tte[i];
|
||||||
|
|
||||||
return found = false, replace;
|
return found = false, replace;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// TranspositionTable::hashfull() returns an approximation of the hashtable
|
// Returns an approximation of the hashtable
|
||||||
/// occupation during a search. The hash is x permill full, as per UCI protocol.
|
// 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 TranspositionTable::hashfull() const {
|
||||||
|
|
||||||
int cnt = 0;
|
int cnt = 0;
|
||||||
for (int i = 0; i < 1000; ++i)
|
for (int i = 0; i < 1000; ++i)
|
||||||
for (int j = 0; j < ClusterSize; ++j)
|
for (int j = 0; j < ClusterSize; ++j)
|
||||||
cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
|
cnt += table[i].entry[j].depth8
|
||||||
|
&& (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
|
||||||
|
|
||||||
return cnt / ClusterSize;
|
return cnt / ClusterSize;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,11 +19,18 @@
|
|||||||
#ifndef TT_H_INCLUDED
|
#ifndef TT_H_INCLUDED
|
||||||
#define TT_H_INCLUDED
|
#define TT_H_INCLUDED
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
|
namespace Cluster {
|
||||||
|
void init();
|
||||||
|
}
|
||||||
|
|
||||||
/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
|
/// TTEntry struct is the 10 bytes transposition table entry, defined as below:
|
||||||
///
|
///
|
||||||
/// key 16 bit
|
/// key 16 bit
|
||||||
@@ -37,34 +44,39 @@ namespace Stockfish {
|
|||||||
|
|
||||||
struct TTEntry {
|
struct TTEntry {
|
||||||
|
|
||||||
Move move() const { return (Move )move16; }
|
Move move() const { return Move(move16); }
|
||||||
Value value() const { return (Value)value16; }
|
Value value() const { return Value(value16); }
|
||||||
Value eval() const { return (Value)eval16; }
|
Value eval() const { return Value(eval16); }
|
||||||
Depth depth() const { return (Depth)depth8 + DEPTH_OFFSET; }
|
Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); }
|
||||||
bool is_pv() const { return (bool)(genBound8 & 0x4); }
|
bool is_pv() const { return bool(genBound8 & 0x4); }
|
||||||
Bound bound() const { return (Bound)(genBound8 & 0x3); }
|
Bound bound() const { return Bound(genBound8 & 0x3); }
|
||||||
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev);
|
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:
|
private:
|
||||||
friend class TranspositionTable;
|
friend class TranspositionTable;
|
||||||
|
friend void Cluster::init();
|
||||||
|
|
||||||
|
|
||||||
uint16_t key16;
|
uint16_t key16;
|
||||||
uint8_t depth8;
|
uint8_t depth8;
|
||||||
uint8_t genBound8;
|
uint8_t genBound8;
|
||||||
uint16_t move16;
|
Move move16;
|
||||||
int16_t value16;
|
int16_t value16;
|
||||||
int16_t eval16;
|
int16_t eval16;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// A TranspositionTable is an array of Cluster, of size clusterCount. Each
|
// A TranspositionTable is an array of Cluster, of size clusterCount. Each
|
||||||
/// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry
|
// cluster consists of ClusterSize number of TTEntry. Each non-empty TTEntry
|
||||||
/// contains information on exactly one position. The size of a Cluster should
|
// 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
|
// divide the size of a cache line for best performance, as the cacheline is
|
||||||
/// prefetched when possible.
|
// prefetched when possible.
|
||||||
|
|
||||||
class TranspositionTable {
|
class TranspositionTable {
|
||||||
|
|
||||||
|
friend void Cluster::init();
|
||||||
|
|
||||||
static constexpr int ClusterSize = 3;
|
static constexpr int ClusterSize = 3;
|
||||||
|
|
||||||
struct Cluster {
|
struct Cluster {
|
||||||
@@ -75,33 +87,44 @@ class TranspositionTable {
|
|||||||
static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
|
static_assert(sizeof(Cluster) == 32, "Unexpected Cluster size");
|
||||||
|
|
||||||
// Constants used to refresh the hash table periodically
|
// 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
|
// We have 8 bits available where the lowest 3 bits are
|
||||||
static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length
|
// reserved for other things.
|
||||||
static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number
|
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;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
~TranspositionTable() { aligned_large_pages_free(table); }
|
~TranspositionTable() { aligned_large_pages_free(table); }
|
||||||
void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things
|
|
||||||
|
void new_search() {
|
||||||
|
// increment by delta to keep lower bits as is
|
||||||
|
generation8 += GENERATION_DELTA;
|
||||||
|
}
|
||||||
|
|
||||||
TTEntry* probe(const Key key, bool& found) const;
|
TTEntry* probe(const Key key, bool& found) const;
|
||||||
int hashfull() const;
|
int hashfull() const;
|
||||||
void resize(size_t mbSize);
|
void resize(size_t mbSize, int threadCount);
|
||||||
void clear();
|
void clear(size_t threadCount);
|
||||||
|
|
||||||
TTEntry* first_entry(const Key key) const {
|
TTEntry* first_entry(const Key key) const {
|
||||||
return &table[mul_hi64(key, clusterCount)].entry[0];
|
return &table[mul_hi64(key, clusterCount)].entry[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t generation() const { return generation8; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend struct TTEntry;
|
friend struct TTEntry;
|
||||||
|
|
||||||
size_t clusterCount;
|
size_t clusterCount;
|
||||||
Cluster* table;
|
Cluster* table = nullptr;
|
||||||
uint8_t generation8; // Size must be not bigger than TTEntry::genBound8
|
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
|
||||||
|
|||||||
+48
-57
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,48 +16,36 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "tune.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <map>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "types.h"
|
#include "ucioption.h"
|
||||||
#include "misc.h"
|
|
||||||
#include "uci.h"
|
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
bool Tune::update_on_last;
|
bool Tune::update_on_last;
|
||||||
const UCI::Option* LastOption = nullptr;
|
const Option* LastOption = nullptr;
|
||||||
static std::map<std::string, int> TuneResults;
|
OptionsMap* Tune::options;
|
||||||
|
|
||||||
string Tune::next(string& names, bool pop) {
|
|
||||||
|
|
||||||
string name;
|
namespace {
|
||||||
|
std::map<std::string, int> TuneResults;
|
||||||
|
|
||||||
do {
|
void on_tune(const Option& o) {
|
||||||
string token = names.substr(0, names.find(','));
|
|
||||||
|
|
||||||
if (pop)
|
|
||||||
names.erase(0, token.size() + 1);
|
|
||||||
|
|
||||||
std::stringstream ws(token);
|
|
||||||
name += (ws >> token, token); // Remove trailing whitespace
|
|
||||||
|
|
||||||
} while ( std::count(name.begin(), name.end(), '(')
|
|
||||||
- std::count(name.begin(), name.end(), ')'));
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void on_tune(const UCI::Option& o) {
|
|
||||||
|
|
||||||
if (!Tune::update_on_last || LastOption == &o)
|
if (!Tune::update_on_last || LastOption == &o)
|
||||||
Tune::read_options();
|
Tune::read_options();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void make_option(const string& n, int v, const SetRange& r) {
|
|
||||||
|
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)
|
// Do not generate option when there is nothing to tune (ie. min = max)
|
||||||
if (r(v).first == r(v).second)
|
if (r(v).first == r(v).second)
|
||||||
@@ -66,48 +54,54 @@ static void make_option(const string& n, int v, const SetRange& r) {
|
|||||||
if (TuneResults.count(n))
|
if (TuneResults.count(n))
|
||||||
v = TuneResults[n];
|
v = TuneResults[n];
|
||||||
|
|
||||||
Options[n] << UCI::Option(v, r(v).first, r(v).second, on_tune);
|
(*options)[n] << Option(v, r(v).first, r(v).second, on_tune);
|
||||||
LastOption = &Options[n];
|
LastOption = &((*options)[n]);
|
||||||
|
|
||||||
// Print formatted parameters, ready to be copy-pasted in Fishtest
|
// Print formatted parameters, ready to be copy-pasted in Fishtest
|
||||||
std::cout << n << ","
|
std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << ","
|
||||||
<< v << ","
|
|
||||||
<< r(v).first << "," << r(v).second << ","
|
|
||||||
<< (r(v).second - r(v).first) / 20.0 << ","
|
<< (r(v).second - r(v).first) / 20.0 << ","
|
||||||
<< "0.0020"
|
<< "0.0020" << std::endl;
|
||||||
<< std::endl;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void Tune::Entry<int>::init_option() { make_option(name, value, range); }
|
string Tune::next(string& names, bool pop) {
|
||||||
|
|
||||||
template<> void Tune::Entry<int>::read_option() {
|
string name;
|
||||||
if (Options.count(name))
|
|
||||||
value = int(Options[name]);
|
do
|
||||||
|
{
|
||||||
|
string token = names.substr(0, names.find(','));
|
||||||
|
|
||||||
|
if (pop)
|
||||||
|
names.erase(0, token.size() + 1);
|
||||||
|
|
||||||
|
std::stringstream ws(token);
|
||||||
|
name += (ws >> token, token); // Remove trailing whitespace
|
||||||
|
|
||||||
|
} while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')'));
|
||||||
|
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void Tune::Entry<Value>::init_option() { make_option(name, value, range); }
|
|
||||||
|
|
||||||
template<> void Tune::Entry<Value>::read_option() {
|
template<>
|
||||||
if (Options.count(name))
|
void Tune::Entry<int>::init_option() {
|
||||||
value = Value(int(Options[name]));
|
make_option(options, name, value, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> void Tune::Entry<Score>::init_option() {
|
template<>
|
||||||
make_option("m" + name, mg_value(value), range);
|
void Tune::Entry<int>::read_option() {
|
||||||
make_option("e" + name, eg_value(value), range);
|
if (options->count(name))
|
||||||
}
|
value = int((*options)[name]);
|
||||||
|
|
||||||
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]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instead of a variable here we have a PostUpdate function: just call it
|
// Instead of a variable here we have a PostUpdate function: just call it
|
||||||
template<> void Tune::Entry<Tune::PostUpdate>::init_option() {}
|
template<>
|
||||||
template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
|
void Tune::Entry<Tune::PostUpdate>::init_option() {}
|
||||||
|
template<>
|
||||||
|
void Tune::Entry<Tune::PostUpdate>::read_option() {
|
||||||
|
value();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
||||||
@@ -121,13 +115,10 @@ template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
|
|||||||
//
|
//
|
||||||
// Then paste the output below, as the function body
|
// Then paste the output below, as the function body
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
void Tune::read_results() {
|
void Tune::read_results() { /* ...insert your values here... */
|
||||||
|
|
||||||
/* ...insert your values here... */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|||||||
+62
-44
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,24 +19,29 @@
|
|||||||
#ifndef TUNE_H_INCLUDED
|
#ifndef TUNE_H_INCLUDED
|
||||||
#define TUNE_H_INCLUDED
|
#define TUNE_H_INCLUDED
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits> // IWYU pragma: keep
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
|
class OptionsMap;
|
||||||
|
|
||||||
using Range = std::pair<int, int>; // Option's min-max values
|
using Range = std::pair<int, int>; // Option's min-max values
|
||||||
using RangeFun = Range(int);
|
using RangeFun = Range(int);
|
||||||
|
|
||||||
// Default Range function, to calculate Option's min-max values
|
// Default Range function, to calculate Option's min-max values
|
||||||
inline Range default_range(int v) {
|
inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); }
|
||||||
return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SetRange {
|
struct SetRange {
|
||||||
explicit SetRange(RangeFun f) : fun(f) {}
|
explicit SetRange(RangeFun f) :
|
||||||
SetRange(int min, int max) : fun(nullptr), range(min, max) {}
|
fun(f) {}
|
||||||
|
SetRange(int min, int max) :
|
||||||
|
fun(nullptr),
|
||||||
|
range(min, max) {}
|
||||||
Range operator()(int v) const { return fun ? fun(v) : range; }
|
Range operator()(int v) const { return fun ? fun(v) : range; }
|
||||||
|
|
||||||
RangeFun* fun;
|
RangeFun* fun;
|
||||||
@@ -46,32 +51,30 @@ struct SetRange {
|
|||||||
#define SetDefaultRange SetRange(default_range)
|
#define SetDefaultRange SetRange(default_range)
|
||||||
|
|
||||||
|
|
||||||
/// Tune class implements the 'magic' code that makes the setup of a fishtest
|
// Tune class implements the 'magic' code that makes the setup of a fishtest tuning
|
||||||
/// tuning session as easy as it can be. Mainly you have just to remove const
|
// session as easy as it can be. Mainly you have just to remove const qualifiers
|
||||||
/// qualifiers from the variables you want to tune and flag them for tuning, so
|
// from the variables you want to tune and flag them for tuning, so if you have:
|
||||||
/// if you have:
|
//
|
||||||
///
|
// const Value myValue[][2] = { { V(100), V(20) }, { V(7), V(78) } };
|
||||||
/// 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
|
||||||
/// If you have a my_post_update() function to run after values have been updated,
|
// remove the 'const' qualifiers and write somewhere below in the file:
|
||||||
/// 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);
|
||||||
///
|
//
|
||||||
/// TUNE(SetRange(my_range), myScore, myValue, my_post_update);
|
// You can also set the range directly, and restore the default at the end
|
||||||
///
|
//
|
||||||
/// You can also set the range directly, and restore the default at the end
|
// TUNE(SetRange(-100, 100), myValue, SetDefaultRange);
|
||||||
///
|
//
|
||||||
/// TUNE(SetRange(-100, 100), myScore, SetDefaultRange);
|
// In case update function is slow and you have many parameters, you can add:
|
||||||
///
|
//
|
||||||
/// In case update function is slow and you have many parameters, you can add:
|
// UPDATE_ON_LAST();
|
||||||
///
|
//
|
||||||
/// 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 the values update, including post update function call, will be done only
|
// and created as the last one, so the GUI should send the options in the same
|
||||||
/// once, after the engine receives the last UCI option, that is the one defined
|
// order in which have been 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 {
|
class Tune {
|
||||||
|
|
||||||
@@ -82,7 +85,10 @@ class Tune {
|
|||||||
void operator=(const Tune&) = delete;
|
void operator=(const Tune&) = delete;
|
||||||
void read_results();
|
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
|
// Use polymorphism to accommodate Entry of different types in the same vector
|
||||||
struct EntryBase {
|
struct EntryBase {
|
||||||
@@ -94,14 +100,15 @@ class Tune {
|
|||||||
template<typename T>
|
template<typename T>
|
||||||
struct Entry: public EntryBase {
|
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
|
static_assert(std::is_same_v<T, int> || std::is_same_v<T, PostUpdate>,
|
||||||
|| std::is_same<T, Value>::value
|
"Parameter type not supported!");
|
||||||
|| std::is_same<T, Score>::value
|
|
||||||
|| std::is_same<T, PostUpdate>::value, "Parameter type not supported!");
|
|
||||||
|
|
||||||
Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {}
|
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 operator=(const Entry&) = delete; // Because 'value' is a reference
|
||||||
void init_option() override;
|
void init_option() override;
|
||||||
void read_option() override;
|
void read_option() override;
|
||||||
@@ -143,14 +150,25 @@ class Tune {
|
|||||||
public:
|
public:
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
static int add(const std::string& names, Args&&... args) {
|
static int add(const std::string& names, Args&&... args) {
|
||||||
return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis
|
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 init(OptionsMap& o) {
|
||||||
static void read_options() { for (auto& e : instance().list) e->read_option(); }
|
options = &o;
|
||||||
|
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;
|
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 STRINGIFY(x) #x
|
||||||
#define UNIQUE2(x, y) x##y
|
#define UNIQUE2(x, y) x##y
|
||||||
#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__
|
#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__
|
||||||
|
|||||||
+175
-255
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,45 +19,44 @@
|
|||||||
#ifndef TYPES_H_INCLUDED
|
#ifndef TYPES_H_INCLUDED
|
||||||
#define TYPES_H_INCLUDED
|
#define TYPES_H_INCLUDED
|
||||||
|
|
||||||
/// When compiling with provided Makefile (e.g. for Linux and OSX), configuration
|
// When compiling with provided Makefile (e.g. for Linux and OSX), configuration
|
||||||
/// is done automatically. To get started type 'make help'.
|
// is done automatically. To get started type 'make help'.
|
||||||
///
|
//
|
||||||
/// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches
|
// When Makefile is not used (e.g. with Microsoft Visual Studio) some switches
|
||||||
/// need to be set manually:
|
// need to be set manually:
|
||||||
///
|
//
|
||||||
/// -DNDEBUG | Disable debugging mode. Always use this for release.
|
// -DNDEBUG | Disable debugging mode. Always use this for release.
|
||||||
///
|
//
|
||||||
/// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to
|
// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to
|
||||||
/// | run on some very old machines.
|
// | run on some very old machines.
|
||||||
///
|
//
|
||||||
/// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works
|
// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works
|
||||||
/// | only in 64-bit mode and requires hardware with popcnt support.
|
// | only in 64-bit mode and requires hardware with popcnt support.
|
||||||
///
|
//
|
||||||
/// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works
|
// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works
|
||||||
/// | only in 64-bit mode and requires hardware with pext support.
|
// | only in 64-bit mode and requires hardware with pext support.
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cctype>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#if defined(_MSC_VER)
|
#if defined(_MSC_VER)
|
||||||
// Disable some silly and noisy warning from MSVC compiler
|
// Disable some silly and noisy warnings from MSVC compiler
|
||||||
#pragma warning(disable: 4127) // Conditional expression is constant
|
#pragma warning(disable: 4127) // Conditional expression is constant
|
||||||
#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type
|
#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type
|
||||||
#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false'
|
#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false'
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Predefined macros hell:
|
// Predefined macros hell:
|
||||||
///
|
//
|
||||||
/// __GNUC__ Compiler is gcc, Clang or Intel on Linux
|
// __GNUC__ Compiler is GCC, Clang or ICX
|
||||||
/// __INTEL_COMPILER Compiler is Intel
|
// __clang__ Compiler is Clang or ICX
|
||||||
/// _MSC_VER Compiler is MSVC or Intel on Windows
|
// __INTEL_LLVM_COMPILER Compiler is ICX
|
||||||
/// _WIN32 Building on Windows (any)
|
// _MSC_VER Compiler is MSVC
|
||||||
/// _WIN64 Building on Windows 64 bit
|
// _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__)
|
#if defined(__GNUC__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) \
|
||||||
|
&& defined(_WIN32) && !defined(__clang__)
|
||||||
#define ALIGNAS_ON_STACK_VARIABLES_BROKEN
|
#define ALIGNAS_ON_STACK_VARIABLES_BROKEN
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -68,12 +67,12 @@
|
|||||||
#define IS_64BIT
|
#define IS_64BIT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_POPCNT) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
|
#if defined(USE_POPCNT) && defined(_MSC_VER)
|
||||||
# include <nmmintrin.h> // Intel and Microsoft header for _mm_popcnt_u64()
|
#include <nmmintrin.h> // Microsoft header for _mm_popcnt_u64()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if !defined(NO_PREFETCH) && (defined(__INTEL_COMPILER) || defined(_MSC_VER))
|
#if !defined(NO_PREFETCH) && defined(_MSC_VER)
|
||||||
# include <xmmintrin.h> // Intel and Microsoft header for _mm_prefetch()
|
#include <xmmintrin.h> // Microsoft header for _mm_prefetch()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(USE_PEXT)
|
#if defined(USE_PEXT)
|
||||||
@@ -109,32 +108,10 @@ using Bitboard = uint64_t;
|
|||||||
constexpr int MAX_MOVES = 256;
|
constexpr int MAX_MOVES = 256;
|
||||||
constexpr int MAX_PLY = 246;
|
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 {
|
enum Color {
|
||||||
WHITE, BLACK, COLOR_NB = 2
|
WHITE,
|
||||||
|
BLACK,
|
||||||
|
COLOR_NB = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
enum CastlingRights {
|
enum CastlingRights {
|
||||||
@@ -153,19 +130,6 @@ enum CastlingRights {
|
|||||||
CASTLING_RIGHT_NB = 16
|
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
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Bound {
|
enum Bound {
|
||||||
BOUND_NONE,
|
BOUND_NONE,
|
||||||
BOUND_UPPER,
|
BOUND_UPPER,
|
||||||
@@ -173,31 +137,35 @@ enum Bound {
|
|||||||
BOUND_EXACT = BOUND_UPPER | BOUND_LOWER
|
BOUND_EXACT = BOUND_UPPER | BOUND_LOWER
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Value : int {
|
// Value is used as an alias for int16_t, this is done to differentiate between
|
||||||
VALUE_ZERO = 0,
|
// a search value and any other integer value. The values used in search are always
|
||||||
VALUE_DRAW = 0,
|
// supposed to be in the range (-VALUE_NONE, VALUE_NONE] and should not exceed this range.
|
||||||
VALUE_KNOWN_WIN = 10000,
|
using Value = int;
|
||||||
VALUE_MATE = 32000,
|
|
||||||
VALUE_INFINITE = 32001,
|
|
||||||
VALUE_NONE = 32002,
|
|
||||||
|
|
||||||
VALUE_TB_WIN_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY,
|
constexpr Value VALUE_ZERO = 0;
|
||||||
VALUE_TB_LOSS_IN_MAX_PLY = -VALUE_TB_WIN_IN_MAX_PLY,
|
constexpr Value VALUE_DRAW = 0;
|
||||||
VALUE_MATE_IN_MAX_PLY = VALUE_MATE - MAX_PLY,
|
constexpr Value VALUE_NONE = 32002;
|
||||||
VALUE_MATED_IN_MAX_PLY = -VALUE_MATE_IN_MAX_PLY,
|
constexpr Value VALUE_INFINITE = 32001;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
// In the code, we make the assumption that these values
|
||||||
// are such that non_pawn_material() can be used to uniquely
|
// are such that non_pawn_material() can be used to uniquely
|
||||||
// identify the material on the board.
|
// identify the material on the board.
|
||||||
PawnValueMg = 126, PawnValueEg = 208,
|
constexpr Value PawnValue = 208;
|
||||||
KnightValueMg = 781, KnightValueEg = 854,
|
constexpr Value KnightValue = 781;
|
||||||
BishopValueMg = 825, BishopValueEg = 915,
|
constexpr Value BishopValue = 825;
|
||||||
RookValueMg = 1276, RookValueEg = 1380,
|
constexpr Value RookValue = 1276;
|
||||||
QueenValueMg = 2538, QueenValueEg = 2682,
|
constexpr Value QueenValue = 2538;
|
||||||
|
|
||||||
MidgameLimit = 15258, EndgameLimit = 3915
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
enum PieceType {
|
enum PieceType {
|
||||||
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
|
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
|
||||||
ALL_PIECES = 0,
|
ALL_PIECES = 0,
|
||||||
@@ -210,26 +178,24 @@ enum Piece {
|
|||||||
B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
|
B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
|
||||||
PIECE_NB = 16
|
PIECE_NB = 16
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
constexpr Value PieceValue[PHASE_NB][PIECE_NB] = {
|
constexpr Value PieceValue[PIECE_NB] = {
|
||||||
{ VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO,
|
VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO,
|
||||||
VALUE_ZERO, PawnValueMg, KnightValueMg, BishopValueMg, RookValueMg, QueenValueMg, VALUE_ZERO, VALUE_ZERO },
|
VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, 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 }
|
|
||||||
};
|
|
||||||
|
|
||||||
using Depth = int;
|
using Depth = int;
|
||||||
|
|
||||||
enum : int {
|
enum : int {
|
||||||
DEPTH_QS_CHECKS = 0,
|
DEPTH_QS_CHECKS = 0,
|
||||||
DEPTH_QS_NO_CHECKS = -1,
|
DEPTH_QS_NO_CHECKS = -1,
|
||||||
DEPTH_QS_RECAPTURES = -5,
|
|
||||||
|
|
||||||
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 {
|
enum Square : int {
|
||||||
SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1,
|
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_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2,
|
||||||
@@ -244,6 +210,7 @@ enum Square : int {
|
|||||||
SQUARE_ZERO = 0,
|
SQUARE_ZERO = 0,
|
||||||
SQUARE_NB = 64
|
SQUARE_NB = 64
|
||||||
};
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
enum Direction : int {
|
enum Direction : int {
|
||||||
NORTH = 8,
|
NORTH = 8,
|
||||||
@@ -258,11 +225,27 @@ enum Direction : int {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum File : int {
|
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 {
|
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)
|
// Keep track of what a move changes on the board (used by NNUE)
|
||||||
@@ -281,209 +264,146 @@ struct DirtyPiece {
|
|||||||
Square to[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 };
|
|
||||||
|
|
||||||
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) \
|
#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); } \
|
||||||
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(PieceType)
|
||||||
ENABLE_INCR_OPERATORS_ON(Square)
|
ENABLE_INCR_OPERATORS_ON(Square)
|
||||||
ENABLE_INCR_OPERATORS_ON(File)
|
ENABLE_INCR_OPERATORS_ON(File)
|
||||||
ENABLE_INCR_OPERATORS_ON(Rank)
|
ENABLE_INCR_OPERATORS_ON(Rank)
|
||||||
|
|
||||||
ENABLE_BASE_OPERATORS_ON(Score)
|
|
||||||
|
|
||||||
#undef ENABLE_FULL_OPERATORS_ON
|
|
||||||
#undef ENABLE_INCR_OPERATORS_ON
|
#undef ENABLE_INCR_OPERATORS_ON
|
||||||
#undef ENABLE_BASE_OPERATORS_ON
|
|
||||||
|
|
||||||
/// Additional operators to add a Direction to a Square
|
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
|
||||||
constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
|
constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
|
||||||
constexpr Square operator-(Square s, Direction d) { return 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
|
// Toggle color
|
||||||
/// a very high risk of overflow. So user should explicitly convert to integer.
|
constexpr Color operator~(Color c) { return Color(c ^ BLACK); }
|
||||||
Score operator*(Score, Score) = delete;
|
|
||||||
|
|
||||||
/// Division of a Score must be handled separately for each term
|
// Swap A1 <-> A8
|
||||||
inline Score operator/(Score s, int i) {
|
constexpr Square flip_rank(Square s) { return Square(s ^ SQ_A8); }
|
||||||
return make_score(mg_value(s) / i, eg_value(s) / i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Multiplication of a Score by an integer. We check for overflow in debug mode.
|
// Swap A1 <-> H1
|
||||||
inline Score operator*(Score s, int i) {
|
constexpr Square flip_file(Square s) { return Square(s ^ SQ_H1); }
|
||||||
|
|
||||||
Score result = Score(int(s) * i);
|
// Swap color of piece B_KNIGHT <-> W_KNIGHT
|
||||||
|
constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); }
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr CastlingRights operator&(Color c, CastlingRights cr) {
|
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) {
|
constexpr Value mate_in(int ply) { return VALUE_MATE - ply; }
|
||||||
return VALUE_MATE - ply;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Value mated_in(int ply) {
|
constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; }
|
||||||
return -VALUE_MATE + ply;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Square make_square(File f, Rank r) {
|
constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); }
|
||||||
return Square((r << 3) + f);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Piece make_piece(Color c, PieceType pt) {
|
constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); }
|
||||||
return Piece((c << 3) + pt);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr PieceType type_of(Piece pc) {
|
constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); }
|
||||||
return PieceType(pc & 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline Color color_of(Piece pc) {
|
inline Color color_of(Piece pc) {
|
||||||
assert(pc != NO_PIECE);
|
assert(pc != NO_PIECE);
|
||||||
return Color(pc >> 3);
|
return Color(pc >> 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool is_ok(Move m) {
|
constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; }
|
||||||
return m != MOVE_NONE && m != MOVE_NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr bool is_ok(Square s) {
|
constexpr File file_of(Square s) { return File(s & 7); }
|
||||||
return s >= SQ_A1 && s <= SQ_H8;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr File file_of(Square s) {
|
constexpr Rank rank_of(Square s) { return Rank(s >> 3); }
|
||||||
return File(s & 7);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Rank rank_of(Square s) {
|
constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); }
|
||||||
return Rank(s >> 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Square relative_square(Color c, Square s) {
|
constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); }
|
||||||
return Square(s ^ (c * 56));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Rank relative_rank(Color c, Rank r) {
|
constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); }
|
||||||
return Rank(r ^ (c * 7));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Rank relative_rank(Color c, Square s) {
|
constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; }
|
||||||
return relative_rank(c, rank_of(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Direction pawn_push(Color c) {
|
|
||||||
return c == WHITE ? NORTH : SOUTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Square from_sq(Move m) {
|
// Based on a congruential pseudo-random number generator
|
||||||
assert(is_ok(m));
|
|
||||||
return Square((m >> 6) & 0x3F);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Square to_sq(Move m) {
|
|
||||||
assert(is_ok(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Based on a congruential pseudo random number generator
|
|
||||||
constexpr Key make_key(uint64_t seed) {
|
constexpr Key make_key(uint64_t seed) {
|
||||||
return seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
return seed * 6364136223846793005ULL + 1442695040888963407ULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum MoveType {
|
||||||
|
NORMAL,
|
||||||
|
PROMOTION = 1 << 14,
|
||||||
|
EN_PASSANT = 2 << 14,
|
||||||
|
CASTLING = 3 << 14
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef TYPES_H_INCLUDED
|
#endif // #ifndef TYPES_H_INCLUDED
|
||||||
|
|||||||
+345
-276
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,43 +16,314 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "uci.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cctype>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <deque>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "benchmark.h"
|
#include "benchmark.h"
|
||||||
|
#include "cluster.h"
|
||||||
#include "evaluate.h"
|
#include "evaluate.h"
|
||||||
#include "movegen.h"
|
#include "movegen.h"
|
||||||
|
#include "nnue/network.h"
|
||||||
|
#include "nnue/nnue_common.h"
|
||||||
|
#include "perft.h"
|
||||||
#include "position.h"
|
#include "position.h"
|
||||||
#include "search.h"
|
#include "search.h"
|
||||||
#include "thread.h"
|
|
||||||
#include "timeman.h"
|
|
||||||
#include "tt.h"
|
|
||||||
#include "uci.h"
|
|
||||||
#include "syzygy/tbprobe.h"
|
#include "syzygy/tbprobe.h"
|
||||||
#include "nnue/evaluate_nnue.h"
|
#include "types.h"
|
||||||
|
#include "ucioption.h"
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
namespace {
|
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||||
|
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
|
||||||
// FEN string for the initial position in standard chess
|
|
||||||
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
||||||
|
|
||||||
|
|
||||||
// position() is called when the engine receives the "position" UCI command.
|
namespace NN = Eval::NNUE;
|
||||||
// It sets up the position that is described in the given FEN string ("fen") or
|
|
||||||
// the initial position ("startpos") and then makes the moves given in the following
|
|
||||||
// move list ("moves").
|
|
||||||
|
|
||||||
void position(Position& pos, istringstream& is, StateListPtr& states) {
|
|
||||||
|
|
||||||
|
UCI::UCI(int argc, char** argv) :
|
||||||
|
networks(NN::Networks(
|
||||||
|
NN::NetworkBig({EvalFileDefaultNameBig, "None", ""}, NN::EmbeddedNNUEType::BIG),
|
||||||
|
NN::NetworkSmall({EvalFileDefaultNameSmall, "None", ""}, NN::EmbeddedNNUEType::SMALL))),
|
||||||
|
cli(argc, argv) {
|
||||||
|
|
||||||
|
options["Debug Log File"] << Option("", [](const Option& o) { start_logger(o); });
|
||||||
|
|
||||||
|
options["Threads"] << Option(1, 1, 1024, [this](const Option&) {
|
||||||
|
threads.set({options, threads, tt, networks});
|
||||||
|
});
|
||||||
|
|
||||||
|
options["Hash"] << Option(16, 1, MaxHashMB, [this](const Option& o) {
|
||||||
|
threads.main_thread()->wait_for_search_finished();
|
||||||
|
tt.resize(o, options["Threads"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
options["Clear Hash"] << Option([this](const Option&) { 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) {
|
||||||
|
networks.big.load(cli.binaryDirectory, o);
|
||||||
|
});
|
||||||
|
options["EvalFileSmall"] << Option(EvalFileDefaultNameSmall, [this](const Option& o) {
|
||||||
|
networks.small.load(cli.binaryDirectory, o);
|
||||||
|
});
|
||||||
|
|
||||||
|
networks.big.load(cli.binaryDirectory, options["EvalFile"]);
|
||||||
|
networks.small.load(cli.binaryDirectory, options["EvalFileSmall"]);
|
||||||
|
|
||||||
|
threads.set({options, threads, tt, networks});
|
||||||
|
|
||||||
|
search_clear(); // After threads are up
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::loop() {
|
||||||
|
|
||||||
|
Position pos;
|
||||||
|
std::string token, cmd;
|
||||||
|
StateListPtr states(new std::deque<StateInfo>(1));
|
||||||
|
|
||||||
|
pos.set(StartFEN, false, &states->back());
|
||||||
|
|
||||||
|
for (int i = 1; i < cli.argc; ++i)
|
||||||
|
cmd += std::string(cli.argv[i]) + " ";
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (cli.argc == 1
|
||||||
|
&& !Cluster::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")
|
||||||
|
threads.stop = true;
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
threads.main_manager()->ponder = false; // Switch to the normal search
|
||||||
|
|
||||||
|
else if (token == "uci" && Cluster::is_root())
|
||||||
|
sync_cout << "id name " << engine_info(true) << "\n"
|
||||||
|
<< options << "\nuciok" << sync_endl;
|
||||||
|
|
||||||
|
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" && Cluster::is_root())
|
||||||
|
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")
|
||||||
|
pos.flip();
|
||||||
|
else if (token == "bench")
|
||||||
|
bench(pos, is, states);
|
||||||
|
else if (token == "d" && Cluster::is_root())
|
||||||
|
sync_cout << pos << sync_endl;
|
||||||
|
else if (token == "eval" && Cluster::is_root())
|
||||||
|
trace_eval(pos);
|
||||||
|
else if (token == "compiler" && Cluster::is_root())
|
||||||
|
sync_cout << compiler_info() << sync_endl;
|
||||||
|
else if (token == "export_net" && Cluster::is_root())
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
|
||||||
|
networks.big.save(files[0].first);
|
||||||
|
networks.small.save(files[1].first);
|
||||||
|
}
|
||||||
|
else if ((token == "--help" || token == "help" || token == "--license"
|
||||||
|
|| token == "license")
|
||||||
|
&& Cluster::is_root())
|
||||||
|
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] != '#' && Cluster::is_root())
|
||||||
|
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 UCI::parse_limits(const Position& pos, 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_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")
|
||||||
|
limits.ponderMode = true;
|
||||||
|
|
||||||
|
return limits;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::go(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||||
|
|
||||||
|
Search::LimitsType limits = parse_limits(pos, is);
|
||||||
|
|
||||||
|
networks.big.verify(options["EvalFile"]);
|
||||||
|
networks.small.verify(options["EvalFileSmall"]);
|
||||||
|
|
||||||
|
if (limits.perft)
|
||||||
|
{
|
||||||
|
perft(pos.fen(), limits.perft, options["UCI_Chess960"]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
threads.start_thinking(options, pos, states, limits);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::bench(Position& pos, std::istream& args, StateListPtr& states) {
|
||||||
|
std::string token;
|
||||||
|
uint64_t num, nodes = 0, cnt = 1;
|
||||||
|
|
||||||
|
std::vector<std::string> list = setup_bench(pos, 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")
|
||||||
|
{
|
||||||
|
if (Cluster::is_root())
|
||||||
|
std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")"
|
||||||
|
<< std::endl;
|
||||||
|
if (token == "go")
|
||||||
|
{
|
||||||
|
go(pos, is, states);
|
||||||
|
threads.main_thread()->wait_for_search_finished();
|
||||||
|
nodes += Cluster::nodes_searched(threads);
|
||||||
|
}
|
||||||
|
else if (Cluster::is_root())
|
||||||
|
trace_eval(pos);
|
||||||
|
}
|
||||||
|
else if (token == "setoption")
|
||||||
|
setoption(is);
|
||||||
|
else if (token == "position")
|
||||||
|
position(pos, is, states);
|
||||||
|
else if (token == "ucinewgame")
|
||||||
|
{
|
||||||
|
search_clear(); // Search::clear() may take a while
|
||||||
|
elapsed = now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
|
||||||
|
|
||||||
|
dbg_print();
|
||||||
|
|
||||||
|
if (Cluster::is_root())
|
||||||
|
std::cerr << "\n==========================="
|
||||||
|
<< "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes
|
||||||
|
<< "\nNodes/second : " << 1000 * nodes / elapsed << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::trace_eval(Position& pos) {
|
||||||
|
StateListPtr states(new std::deque<StateInfo>(1));
|
||||||
|
Position p;
|
||||||
|
p.set(pos.fen(), options["UCI_Chess960"], &states->back());
|
||||||
|
|
||||||
|
networks.big.verify(options["EvalFile"]);
|
||||||
|
networks.small.verify(options["EvalFileSmall"]);
|
||||||
|
|
||||||
|
|
||||||
|
sync_cout << "\n" << Eval::trace(p, networks) << sync_endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::search_clear() {
|
||||||
|
threads.main_thread()->wait_for_search_finished();
|
||||||
|
|
||||||
|
tt.clear(options["Threads"]);
|
||||||
|
threads.clear();
|
||||||
|
Tablebases::init(options["SyzygyPath"]); // Free mapped files
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::setoption(std::istringstream& is) {
|
||||||
|
threads.main_thread()->wait_for_search_finished();
|
||||||
|
options.setoption(is);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UCI::position(Position& pos, std::istringstream& is, StateListPtr& states) {
|
||||||
Move m;
|
Move m;
|
||||||
string token, fen;
|
std::string token, fen;
|
||||||
|
|
||||||
is >> token;
|
is >> token;
|
||||||
|
|
||||||
@@ -68,258 +339,62 @@ namespace {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop the old state and create a new one
|
states = StateListPtr(new std::deque<StateInfo>(1)); // Drop the old state and create a new one
|
||||||
pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main());
|
pos.set(fen, options["UCI_Chess960"], &states->back());
|
||||||
|
|
||||||
// Parse the move list, if any
|
// Parse the move list, if any
|
||||||
while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE)
|
while (is >> token && (m = to_move(pos, token)) != Move::none())
|
||||||
{
|
{
|
||||||
states->emplace_back();
|
states->emplace_back();
|
||||||
pos.do_move(m, states->back());
|
pos.do_move(m, states->back());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// trace_eval() prints the evaluation of the current position, consistent with
|
namespace {
|
||||||
// the UCI options set so far.
|
|
||||||
|
|
||||||
void trace_eval(Position& pos) {
|
struct WinRateParams {
|
||||||
|
double a;
|
||||||
|
double b;
|
||||||
|
};
|
||||||
|
|
||||||
StateListPtr states(new std::deque<StateInfo>(1));
|
WinRateParams win_rate_params(const Position& pos) {
|
||||||
Position p;
|
|
||||||
p.set(pos.fen(), Options["UCI_Chess960"], &states->back(), Threads.main());
|
|
||||||
|
|
||||||
Eval::NNUE::verify();
|
int material = pos.count<PAWN>() + 3 * pos.count<KNIGHT>() + 3 * pos.count<BISHOP>()
|
||||||
|
+ 5 * pos.count<ROOK>() + 9 * pos.count<QUEEN>();
|
||||||
|
|
||||||
sync_cout << "\n" << Eval::trace(p) << sync_endl;
|
// 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;
|
||||||
|
|
||||||
|
// Return a = p_a(material) and b = p_b(material), see github.com/official-stockfish/WDL_model
|
||||||
// setoption() is called when the engine receives the "setoption" UCI command.
|
constexpr double as[] = {-185.71965483, 504.85014385, -438.58295743, 474.04604627};
|
||||||
// The function updates the UCI option ("name") to the given value ("value").
|
constexpr double bs[] = {89.23542728, -137.02141296, 73.28669021, 47.53376190};
|
||||||
|
|
||||||
void setoption(istringstream& is) {
|
|
||||||
|
|
||||||
string token, name, value;
|
|
||||||
|
|
||||||
is >> token; // Consume the "name" token
|
|
||||||
|
|
||||||
// Read the option name (can contain spaces)
|
|
||||||
while (is >> token && token != "value")
|
|
||||||
name += (name.empty() ? "" : " ") + token;
|
|
||||||
|
|
||||||
// Read the 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 the engine receives the "go" UCI command. The function
|
|
||||||
// sets the thinking time and other parameters from the input string, then starts
|
|
||||||
// with a search.
|
|
||||||
|
|
||||||
void go(Position& pos, istringstream& is, StateListPtr& states) {
|
|
||||||
|
|
||||||
Search::LimitsType limits;
|
|
||||||
string token;
|
|
||||||
bool ponderMode = false;
|
|
||||||
|
|
||||||
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(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 the engine receives the "bench" command.
|
|
||||||
// Firstly, a list of UCI commands is set up according to the 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(), [](const 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 a while
|
|
||||||
}
|
|
||||||
|
|
||||||
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
|
|
||||||
|
|
||||||
dbg_print();
|
|
||||||
|
|
||||||
cerr << "\n==========================="
|
|
||||||
<< "\nTotal time (ms) : " << elapsed
|
|
||||||
<< "\nNodes searched : " << nodes
|
|
||||||
<< "\nNodes/second : " << 1000 * nodes / elapsed << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The win rate model returns the probability of winning (in per mille units) given an
|
|
||||||
// eval and a game ply. It fits the LTC fishtest statistics rather accurately.
|
|
||||||
int win_rate_model(Value v, int ply) {
|
|
||||||
|
|
||||||
// The model only captures up to 240 plies, so limit the input and then rescale
|
|
||||||
double m = std::min(240, ply) / 64.0;
|
|
||||||
|
|
||||||
// The coefficients of a third-order polynomial fit is based on the fishtest data
|
|
||||||
// for two parameters that need to transform eval to the argument of a logistic
|
|
||||||
// function.
|
|
||||||
constexpr double as[] = { 0.38036525, -2.82015070, 23.17882135, 307.36768407};
|
|
||||||
constexpr double bs[] = { -2.29434733, 13.27689788, -14.26828904, 63.45318330 };
|
|
||||||
|
|
||||||
// Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64
|
|
||||||
static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3]));
|
|
||||||
|
|
||||||
double a = (((as[0] * m + as[1]) * m + as[2]) * m) + as[3];
|
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];
|
double b = (((bs[0] * m + bs[1]) * m + bs[2]) * m) + bs[3];
|
||||||
|
|
||||||
// Transform the eval to centipawns with limited range
|
return {a, b};
|
||||||
double x = std::clamp(double(v), -4000.0, 4000.0);
|
|
||||||
|
|
||||||
// Return the win rate in per mille units rounded to the nearest value
|
|
||||||
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
// 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) {
|
||||||
|
|
||||||
|
auto [a, b] = win_rate_params(pos);
|
||||||
|
|
||||||
/// UCI::loop() waits for a command from the stdin, parses it and then calls the appropriate
|
// Return the win rate in per mille units, rounded to the nearest integer.
|
||||||
/// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a
|
return int(0.5 + 1000 / (1 + std::exp((a - double(v)) / b)));
|
||||||
/// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments,
|
|
||||||
/// like running 'bench', the function returns immediately after the command is executed.
|
|
||||||
/// In addition to the UCI ones, some additional debug commands are also supported.
|
|
||||||
|
|
||||||
void UCI::loop(int argc, char* argv[]) {
|
|
||||||
|
|
||||||
Position pos;
|
|
||||||
string token, cmd;
|
|
||||||
StateListPtr states(new std::deque<StateInfo>(1));
|
|
||||||
|
|
||||||
pos.set(StartFEN, false, &states->back(), Threads.main());
|
|
||||||
|
|
||||||
for (int i = 1; i < argc; ++i)
|
|
||||||
cmd += std::string(argv[i]) + " ";
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (argc == 1 && !getline(cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
|
|
||||||
cmd = "quit";
|
|
||||||
|
|
||||||
istringstream is(cmd);
|
|
||||||
|
|
||||||
token.clear(); // Avoid a stale if getline() returns nothing or a blank line
|
|
||||||
is >> skipws >> token;
|
|
||||||
|
|
||||||
if ( token == "quit"
|
|
||||||
|| token == "stop")
|
|
||||||
Threads.stop = true;
|
|
||||||
|
|
||||||
// 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")
|
|
||||||
Threads.main()->ponder = false; // Switch to the normal search
|
|
||||||
|
|
||||||
else if (token == "uci")
|
|
||||||
sync_cout << "id name " << engine_info(true)
|
|
||||||
<< "\n" << Options
|
|
||||||
<< "\nuciok" << sync_endl;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Add custom non-UCI commands, mainly for debugging purposes.
|
|
||||||
// These commands must not be used 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 == "--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" && argc == 1); // The command-line arguments are one-shot
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string UCI::to_score(Value v, const Position& pos) {
|
||||||
/// UCI::value() converts a Value to a string by adhering to 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,
|
|
||||||
/// uses negative values for 'y'.
|
|
||||||
|
|
||||||
string UCI::value(Value v) {
|
|
||||||
|
|
||||||
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
|
assert(-VALUE_INFINITE < v && v < VALUE_INFINITE);
|
||||||
|
|
||||||
stringstream ss;
|
std::stringstream ss;
|
||||||
|
|
||||||
if (abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
|
if (std::abs(v) < VALUE_TB_WIN_IN_MAX_PLY)
|
||||||
ss << "cp " << v * 100 / NormalizeToPawnValue;
|
ss << "cp " << to_cp(v, pos);
|
||||||
else if (abs(v) < VALUE_MATE_IN_MAX_PLY)
|
else if (std::abs(v) <= VALUE_TB)
|
||||||
{
|
{
|
||||||
const int ply = VALUE_MATE_IN_MAX_PLY - 1 - std::abs(v); // recompute ss->ply
|
const int ply = VALUE_TB - std::abs(v); // recompute ss->ply
|
||||||
ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply);
|
ss << "cp " << (v > 0 ? 20000 - ply : -20000 + ply);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -328,71 +403,65 @@ string UCI::value(Value v) {
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Turns a Value to an integer centipawn number,
|
||||||
|
// without treatment of mate and similar special scores.
|
||||||
|
int UCI::to_cp(Value v, const Position& pos) {
|
||||||
|
|
||||||
/// UCI::wdl() reports the win-draw-loss (WDL) statistics given an evaluation
|
// In general, the score can be defined via the the WDL as
|
||||||
/// and a game ply based on the data gathered for fishtest LTC games.
|
// (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.
|
||||||
|
|
||||||
string UCI::wdl(Value v, int ply) {
|
auto [a, b] = win_rate_params(pos);
|
||||||
|
|
||||||
stringstream ss;
|
return std::round(100 * int(v) / a);
|
||||||
|
}
|
||||||
|
|
||||||
int wdl_w = win_rate_model( v, ply);
|
std::string UCI::wdl(Value v, const Position& pos) {
|
||||||
int wdl_l = win_rate_model(-v, ply);
|
std::stringstream ss;
|
||||||
|
|
||||||
|
int wdl_w = win_rate_model(v, pos);
|
||||||
|
int wdl_l = win_rate_model(-v, pos);
|
||||||
int wdl_d = 1000 - wdl_w - wdl_l;
|
int wdl_d = 1000 - wdl_w - wdl_l;
|
||||||
ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l;
|
ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l;
|
||||||
|
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.)
|
|
||||||
|
|
||||||
std::string UCI::square(Square s) {
|
std::string UCI::square(Square s) {
|
||||||
return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};
|
return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string UCI::move(Move m, bool chess960) {
|
||||||
/// UCI::move() converts a Move to a string in coordinate notation (g1f3, a7a8q).
|
if (m == Move::none())
|
||||||
/// The only special case is castling where the e1g1 notation is printed in
|
|
||||||
/// standard chess mode and in e1h1 notation it is printed in Chess960 mode.
|
|
||||||
/// Internally, all castling moves are always encoded as 'king captures rook'.
|
|
||||||
|
|
||||||
string UCI::move(Move m, bool chess960) {
|
|
||||||
|
|
||||||
if (m == MOVE_NONE)
|
|
||||||
return "(none)";
|
return "(none)";
|
||||||
|
|
||||||
if (m == MOVE_NULL)
|
if (m == Move::null())
|
||||||
return "0000";
|
return "0000";
|
||||||
|
|
||||||
Square from = from_sq(m);
|
Square from = m.from_sq();
|
||||||
Square to = to_sq(m);
|
Square to = m.to_sq();
|
||||||
|
|
||||||
if (type_of(m) == CASTLING && !chess960)
|
if (m.type_of() == CASTLING && !chess960)
|
||||||
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
|
to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
|
||||||
|
|
||||||
string move = UCI::square(from) + UCI::square(to);
|
std::string move = square(from) + square(to);
|
||||||
|
|
||||||
if (type_of(m) == PROMOTION)
|
if (m.type_of() == PROMOTION)
|
||||||
move += " pnbrqk"[promotion_type(m)];
|
move += " pnbrqk"[m.promotion_type()];
|
||||||
|
|
||||||
return move;
|
return move;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// UCI::to_move() converts a string representing a move in coordinate notation
|
Move UCI::to_move(const Position& pos, std::string& str) {
|
||||||
/// (g1f3, a7a8q) to the corresponding legal Move, if any.
|
|
||||||
|
|
||||||
Move UCI::to_move(const Position& pos, string& str) {
|
|
||||||
|
|
||||||
if (str.length() == 5)
|
if (str.length() == 5)
|
||||||
str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased
|
str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased
|
||||||
|
|
||||||
for (const auto& m : MoveList<LEGAL>(pos))
|
for (const auto& m : MoveList<LEGAL>(pos))
|
||||||
if (str == UCI::move(m, pos.is_chess960()))
|
if (str == move(m, pos.is_chess960()))
|
||||||
return m;
|
return m;
|
||||||
|
|
||||||
return MOVE_NONE;
|
return Move::none();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,74 +19,56 @@
|
|||||||
#ifndef UCI_H_INCLUDED
|
#ifndef UCI_H_INCLUDED
|
||||||
#define UCI_H_INCLUDED
|
#define UCI_H_INCLUDED
|
||||||
|
|
||||||
#include <map>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "types.h"
|
#include "misc.h"
|
||||||
|
#include "nnue/network.h"
|
||||||
|
#include "position.h"
|
||||||
|
#include "search.h"
|
||||||
|
#include "thread.h"
|
||||||
|
#include "tt.h"
|
||||||
|
#include "ucioption.h"
|
||||||
|
|
||||||
namespace Stockfish {
|
namespace Stockfish {
|
||||||
|
|
||||||
class Position;
|
class Move;
|
||||||
|
enum Square : int;
|
||||||
namespace UCI {
|
using Value = int;
|
||||||
|
|
||||||
// Normalizes the internal value as reported by evaluate or search
|
|
||||||
// to the UCI centipawn result used in output. This value is derived from
|
|
||||||
// the win_rate_model() such that Stockfish outputs an advantage of
|
|
||||||
// "100 centipawns" for a position if the engine has a 50% probability to win
|
|
||||||
// from this position in selfplay at fishtest LTC time control.
|
|
||||||
const int NormalizeToPawnValue = 328;
|
|
||||||
|
|
||||||
class Option;
|
|
||||||
|
|
||||||
/// Define a custom comparator, because the UCI options should be case-insensitive
|
|
||||||
struct CaseInsensitiveLess {
|
|
||||||
bool operator() (const std::string&, const std::string&) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// The options container is defined as a std::map
|
|
||||||
using OptionsMap = std::map<std::string, Option, CaseInsensitiveLess>;
|
|
||||||
|
|
||||||
/// The Option class implements each option as specified by the UCI protocol
|
|
||||||
class Option {
|
|
||||||
|
|
||||||
using OnChange = void (*)(const Option&);
|
|
||||||
|
|
||||||
|
class UCI {
|
||||||
public:
|
public:
|
||||||
Option(OnChange = nullptr);
|
UCI(int argc, char** argv);
|
||||||
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 loop();
|
||||||
void operator<<(const Option&);
|
|
||||||
operator int() const;
|
static int to_cp(Value v, const Position& pos);
|
||||||
operator std::string() const;
|
static std::string to_score(Value v, const Position& pos);
|
||||||
bool operator==(const char*) const;
|
static std::string square(Square s);
|
||||||
|
static std::string move(Move m, bool chess960);
|
||||||
|
static std::string wdl(Value v, const Position& pos);
|
||||||
|
static Move to_move(const Position& pos, std::string& str);
|
||||||
|
|
||||||
|
static Search::LimitsType parse_limits(const Position& pos, std::istream& is);
|
||||||
|
|
||||||
|
const std::string& working_directory() const { return cli.workingDirectory; }
|
||||||
|
|
||||||
|
OptionsMap options;
|
||||||
|
Eval::NNUE::Networks networks;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
|
TranspositionTable tt;
|
||||||
|
ThreadPool threads;
|
||||||
|
CommandLine cli;
|
||||||
|
|
||||||
std::string defaultValue, currentValue, type;
|
void go(Position& pos, std::istringstream& is, StateListPtr& states);
|
||||||
int min, max;
|
void bench(Position& pos, std::istream& args, StateListPtr& states);
|
||||||
size_t idx;
|
void position(Position& pos, std::istringstream& is, StateListPtr& states);
|
||||||
OnChange on_change;
|
void trace_eval(Position& pos);
|
||||||
|
void search_clear();
|
||||||
|
void setoption(std::istringstream& is);
|
||||||
};
|
};
|
||||||
|
|
||||||
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);
|
|
||||||
std::string wdl(Value v, int ply);
|
|
||||||
Move to_move(const Position& pos, std::string& str);
|
|
||||||
|
|
||||||
} // namespace UCI
|
|
||||||
|
|
||||||
extern UCI::OptionsMap Options;
|
|
||||||
|
|
||||||
} // namespace Stockfish
|
} // namespace Stockfish
|
||||||
|
|
||||||
#endif // #ifndef UCI_H_INCLUDED
|
#endif // #ifndef UCI_H_INCLUDED
|
||||||
|
|||||||
+92
-100
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
||||||
Copyright (C) 2004-2023 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
|
Stockfish is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
@@ -16,117 +16,93 @@
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include "ucioption.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <ostream>
|
#include <cctype>
|
||||||
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "evaluate.h"
|
#include "cluster.h"
|
||||||
#include "misc.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 {
|
namespace Stockfish {
|
||||||
|
|
||||||
UCI::OptionsMap Options; // Global object
|
bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s2) const {
|
||||||
|
|
||||||
namespace UCI {
|
return std::lexicographical_compare(
|
||||||
|
s1.begin(), s1.end(), s2.begin(), s2.end(),
|
||||||
/// 'On change' actions, triggered by an option's value change
|
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
|
||||||
static void on_clear_hash(const Option&) { Search::clear(); }
|
|
||||||
static void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
|
|
||||||
static void on_logger(const Option& o) { start_logger(o); }
|
|
||||||
static void on_threads(const Option& o) { Threads.set(size_t(o)); }
|
|
||||||
static void on_tb_path(const Option& o) { Tablebases::init(o); }
|
|
||||||
static void on_use_NNUE(const Option&) { Eval::NNUE::init(); }
|
|
||||||
static 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); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
if (options_map.count(name))
|
||||||
o["Threads"] << Option(1, 1, 1024, on_threads);
|
options_map[name] = value;
|
||||||
o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size);
|
else if (Cluster::is_root())
|
||||||
o["Clear Hash"] << Option(on_clear_hash);
|
sync_cout << "No such option: " << name << sync_endl;
|
||||||
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(1320, 1320, 3190);
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Option OptionsMap::operator[](const std::string& name) const {
|
||||||
/// operator<<() is used to print all the options default values in chronological
|
auto it = options_map.find(name);
|
||||||
/// insertion order (the idx field) and in the format defined by the UCI protocol.
|
return it != options_map.end() ? it->second : Option();
|
||||||
|
|
||||||
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) { return options_map[name]; }
|
||||||
|
|
||||||
|
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(std::move(f)) {
|
||||||
|
defaultValue = currentValue = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Option::Option(bool v, OnChange f) :
|
||||||
|
type("check"),
|
||||||
|
min(0),
|
||||||
|
max(0),
|
||||||
|
on_change(std::move(f)) {
|
||||||
|
defaultValue = currentValue = (v ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
/// Option class constructors and conversion operators
|
Option::Option(OnChange f) :
|
||||||
|
type("button"),
|
||||||
|
min(0),
|
||||||
|
max(0),
|
||||||
|
on_change(std::move(f)) {}
|
||||||
|
|
||||||
Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f)
|
Option::Option(double v, int minv, int maxv, OnChange f) :
|
||||||
{ defaultValue = currentValue = v; }
|
type("spin"),
|
||||||
|
min(minv),
|
||||||
|
max(maxv),
|
||||||
|
on_change(std::move(f)) {
|
||||||
|
defaultValue = currentValue = std::to_string(v);
|
||||||
|
}
|
||||||
|
|
||||||
Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f)
|
Option::Option(const char* v, const char* cur, OnChange f) :
|
||||||
{ defaultValue = currentValue = (v ? "true" : "false"); }
|
type("combo"),
|
||||||
|
min(0),
|
||||||
Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f)
|
max(0),
|
||||||
{}
|
on_change(std::move(f)) {
|
||||||
|
defaultValue = v;
|
||||||
Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f)
|
currentValue = cur;
|
||||||
{ 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::operator int() const {
|
Option::operator int() const {
|
||||||
assert(type == "check" || type == "spin");
|
assert(type == "check" || type == "spin");
|
||||||
@@ -140,12 +116,11 @@ Option::operator std::string() const {
|
|||||||
|
|
||||||
bool Option::operator==(const char* s) const {
|
bool Option::operator==(const char* s) const {
|
||||||
assert(type == "combo");
|
assert(type == "combo");
|
||||||
return !CaseInsensitiveLess()(currentValue, s)
|
return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue);
|
||||||
&& !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) {
|
void Option::operator<<(const Option& o) {
|
||||||
|
|
||||||
@@ -156,23 +131,22 @@ void Option::operator<<(const Option& o) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// operator=() updates currentValue and triggers on_change() action. It's up to
|
// 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
|
// 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.
|
// 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())
|
if ((type != "button" && type != "string" && v.empty())
|
||||||
|| (type == "check" && v != "true" && v != "false")
|
|| (type == "check" && v != "true" && v != "false")
|
||||||
|| (type == "spin" && (stof(v) < min || stof(v) > max)))
|
|| (type == "spin" && (std::stof(v) < min || std::stof(v) > max)))
|
||||||
return *this;
|
return *this;
|
||||||
|
|
||||||
if (type == "combo")
|
if (type == "combo")
|
||||||
{
|
{
|
||||||
OptionsMap comboMap; // To have case insensitive compare
|
OptionsMap comboMap; // To have case insensitive compare
|
||||||
string token;
|
std::string token;
|
||||||
std::istringstream ss(defaultValue);
|
std::istringstream ss(defaultValue);
|
||||||
while (ss >> token)
|
while (ss >> token)
|
||||||
comboMap[token] << Option();
|
comboMap[token] << Option();
|
||||||
@@ -189,6 +163,24 @@ Option& Option::operator=(const string& v) {
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
+60
-5
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# check for errors under valgrind or sanitizers.
|
# check for errors under Valgrind or sanitizers.
|
||||||
|
|
||||||
error()
|
error()
|
||||||
{
|
{
|
||||||
@@ -8,6 +8,13 @@ error()
|
|||||||
}
|
}
|
||||||
trap 'error ${LINENO}' ERR
|
trap 'error ${LINENO}' ERR
|
||||||
|
|
||||||
|
# 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
|
||||||
|
sudo sysctl -w vm.mmap_rnd_bits=28
|
||||||
|
|
||||||
|
|
||||||
# define suitable post and prefixes for testing options
|
# define suitable post and prefixes for testing options
|
||||||
case $1 in
|
case $1 in
|
||||||
--valgrind)
|
--valgrind)
|
||||||
@@ -64,14 +71,32 @@ EOF
|
|||||||
;;
|
;;
|
||||||
esac
|
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
|
# simple command line testing
|
||||||
for args in "eval" \
|
for args in "eval" \
|
||||||
"go nodes 1000" \
|
"go nodes 1000" \
|
||||||
"go depth 10" \
|
"go depth 10" \
|
||||||
|
"go perft 4" \
|
||||||
"go movetime 1000" \
|
"go movetime 1000" \
|
||||||
"go wtime 8000 btime 8000 winc 500 binc 500" \
|
"go wtime 8000 btime 8000 winc 500 binc 500" \
|
||||||
|
"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 8 default depth" \
|
||||||
"export_net verify.nnue"
|
"bench 128 $threads 3 bench_tmp.epd depth" \
|
||||||
|
"export_net verify.nnue" \
|
||||||
|
"d" \
|
||||||
|
"compiler" \
|
||||||
|
"license" \
|
||||||
|
"uci"
|
||||||
do
|
do
|
||||||
|
|
||||||
echo "$prefix $exeprefix ./stockfish $args $postfix"
|
echo "$prefix $exeprefix ./stockfish $args $postfix"
|
||||||
@@ -92,6 +117,7 @@ cat << EOF > game.exp
|
|||||||
send "uci\n"
|
send "uci\n"
|
||||||
expect "uciok"
|
expect "uciok"
|
||||||
|
|
||||||
|
# send "setoption name Debug Log File value debug.log\n"
|
||||||
send "setoption name Threads value $threads\n"
|
send "setoption name Threads value $threads\n"
|
||||||
|
|
||||||
send "ucinewgame\n"
|
send "ucinewgame\n"
|
||||||
@@ -107,10 +133,32 @@ cat << EOF > game.exp
|
|||||||
send "go depth 10\n"
|
send "go depth 10\n"
|
||||||
expect "bestmove"
|
expect "bestmove"
|
||||||
|
|
||||||
|
send "setoption name UCI_ShowWDL value true\n"
|
||||||
|
send "position startpos\n"
|
||||||
|
send "flip\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 Clear Hash\n"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
send "quit\n"
|
send "quit\n"
|
||||||
expect eof
|
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
|
lassign [wait] pid spawnid os_error_flag value
|
||||||
exit \$value
|
exit \$value
|
||||||
EOF
|
EOF
|
||||||
@@ -128,10 +176,17 @@ cat << EOF > syzygy.exp
|
|||||||
send "setoption name SyzygyPath value ../tests/syzygy/\n"
|
send "setoption name SyzygyPath value ../tests/syzygy/\n"
|
||||||
expect "info string Found 35 tablebases" {} timeout {exit 1}
|
expect "info string Found 35 tablebases" {} timeout {exit 1}
|
||||||
send "bench 128 1 8 default depth\n"
|
send "bench 128 1 8 default depth\n"
|
||||||
|
send "ucinewgame\n"
|
||||||
|
send "position fen 4k3/PP6/8/8/8/8/8/4K3 w - - 0 1\n"
|
||||||
|
send "go depth 5\n"
|
||||||
|
expect "bestmove"
|
||||||
|
send "position fen 8/1P6/2B5/8/4K3/8/6k1/8 w - - 0 1\n"
|
||||||
|
send "go depth 5\n"
|
||||||
|
expect "bestmove"
|
||||||
send "quit\n"
|
send "quit\n"
|
||||||
expect eof
|
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
|
lassign [wait] pid spawnid os_error_flag value
|
||||||
exit \$value
|
exit \$value
|
||||||
EOF
|
EOF
|
||||||
@@ -146,6 +201,6 @@ do
|
|||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
rm -f tsan.supp
|
rm -f tsan.supp bench_tmp.epd
|
||||||
|
|
||||||
echo "instrumented testing OK"
|
echo "instrumented testing OK"
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ trap 'error ${LINENO}' ERR
|
|||||||
|
|
||||||
# obtain
|
# 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
|
if [ $# -gt 0 ]; then
|
||||||
# compare to given reference
|
# compare to given reference
|
||||||
|
|||||||
Reference in New Issue
Block a user