Compare commits

..
247 Commits
Author SHA1 Message Date
f4bcd40409 Update nnue architecture to SFNNv16 and net nn-89cb98a217f7.nnue
Failed STC (https://tests.stockfishchess.org/tests/view/6a4f36355529b8472df7ff7e):
LLR: -2.94 (-2.94,2.94) <0.00,2.00>
Total: 144768 W: 38003 L: 38051 D: 68714
Ptnml(0-2): 521, 17154, 37050, 17170, 489

Passed LTC (https://tests.stockfishchess.org/tests/view/6a501dad5529b8472df80103):
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 189888 W: 49613 L: 48984 D: 91291
Ptnml(0-2): 122, 20406, 53256, 21041, 119

Passed VLTC (https://tests.stockfishchess.org/tests/view/6a52892c5529b8472df80481):
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 46124 W: 12156 L: 11865 D: 22103
Ptnml(0-2): 12, 4523, 13696, 4824, 7

Passed VVLTC (https://tests.stockfishchess.org/tests/view/6a566a285529b8472df80b84):
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 44698 W: 11805 L: 11507 D: 21386
Ptnml(0-2): 9, 3819, 14395, 4117, 9

This PR adds pawn pair features, a new set of features that is indexed by pairs of pawns occupying the same or adjacent files (hence "3-wide"). Because these are a superset of pawn–pawn interactions in threat inputs (including the recently added pawn-pusher inputs), those are removed. HalfKA features remain unchanged.

Pawn-pair features were invented by Jonathan for Pawnocchio, based on his observation that in a net trained on *all* pairs of pawns, the most important pawn pairs were those differing by at most one file.

sscg13 made the necessary changes to the trainer (https://github.com/official-stockfish/nnue-pytorch/pull/502) and initial changes to inference. The slowdown was originally far too large (some 10%); following major optimizations from Jonathan and a bit from me, the slowdown is small enough for a comfortable LTC pass; on my machine I get a 3.5% slowdown overall. I expect there are further "easy" speedups in the new code, so this gap should narrow a bit.

Nettest PR: https://github.com/vondele/nettest/pull/359

closes https://github.com/official-stockfish/Stockfish/pull/6982

Bench: 2718396

Co-Authored-By: anematode <timothy.herchen@gmail.com>
Co-Authored-By: Jonathan Hallström <lmj.hallstrom@gmail.com>
2026-07-20 11:06:41 +02:00
Syine MinetaandJoost VandeVondele 4d75fd4f70 Always use pthread
After a858defd33, the pthread implementation of `NativeThread` is always used (except MSVC), as:

1. `-DUSE_PTHREADS` is defined when `comp` is not `mingw`
2. the pthread implementation is used anyways because MinGW defines `__MINGW32__` or `__MINGW64__` macro

closes https://github.com/official-stockfish/Stockfish/pull/6985

No functional change
2026-07-20 11:01:40 +02:00
mstemberaandJoost VandeVondele d70dec7d6f Optimize attacks
STC:  https://tests.stockfishchess.org/tests/view/6a57f12c5529b8472df80e2a
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 85856 W: 22400 L: 22037 D: 41419
Ptnml(0-2): 153, 8999, 24290, 9304, 182

closes https://github.com/official-stockfish/Stockfish/pull/6983

No functional change
2026-07-20 10:59:45 +02:00
anematodeandJoost VandeVondele b4ea9205a6 Allow trailing whitespace in str_to_size_t
Fixes #6984, first reported by sqrmax on the Discord, causing incorrect detection of NUMA and L3 domains on Linux due to trailing whitespace when reading from sysfs.

This was introduced by #6844 which made `str_to_size_t` too strict, since `std::stoull` allows trailing characters (and leading whitespace, although this part is not relevant for us).

Before:
$ ./bins/stockfish.master speedtest
Stockfish dev-20260716-ebcea3ef by the Stockfish developers (see AUTHORS file)
info string Using 224 threads with NUMA node thread binding: 64/32:64/32:64/32:32/16

After:
$ ./stockfish speedtest
Stockfish dev-20260718-m-cb702902 by the Stockfish developers (see AUTHORS file)
info string Using 224 threads with NUMA node thread binding: 32/32:32/32:32/32:32/32:32/32:32/32:32/32

closes https://github.com/official-stockfish/Stockfish/pull/6989

No functional change
2026-07-20 10:58:54 +02:00
FauziAkramandJoost VandeVondele ebcea3efe9 VVLTC parameters tweak
Passed VVLTC with stc bounds:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 91508 W: 24212 L: 23887 D: 43409
Ptnml(0-2): 6, 7863, 29690, 8190, 5
https://tests.stockfishchess.org/tests/view/6a522f515529b8472df803a4

Passed VVLTC with ltc bounds:
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 62296 W: 16446 L: 16117 D: 29733
Ptnml(0-2): 6, 5333, 20143, 5658, 8
https://tests.stockfishchess.org/tests/view/6a52d9e65529b8472df805a5

closes https://github.com/official-stockfish/Stockfish/pull/6981

Bench: 2792255
2026-07-16 18:02:05 +02:00
mstemberaandJoost VandeVondele 86beb0f540 Remove some dead fused code
closes https://github.com/official-stockfish/Stockfish/pull/6971

No functional change
2026-07-16 08:28:24 +02:00
mstemberaandJoost VandeVondele 3e3ee33a75 Simplify HalfKAv2_hm::write_indices()
STC: https://tests.stockfishchess.org/tests/view/6a4b4b32f97ff95f78795de9
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 196064 W: 51063 L: 51020 D: 93981
Ptnml(0-2): 367, 21491, 54292, 21496, 386

closes https://github.com/official-stockfish/Stockfish/pull/6970

No functional change
2026-07-16 08:28:24 +02:00
Guy VreulsandJoost VandeVondele 151e204cc8 Simplify bitboards
This PR:
- retires the unused distance() functions in bitboard.h including the 4K SquareDistance LUT,
- makes the 64K PopCnt16 LUT constexpr and only compiles it in if we don't use  a popcount intrinsic (we can't fully remove the LUT because it's faster than the intrinsic in the absence of a popcount machine instruction),
- retires Bitboards::init().

Passed STC:
https://tests.stockfishchess.org/tests/view/6a48e6d3f97ff95f78795a8f
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 162016 W: 42054 L: 41976 D: 77986
Ptnml(0-2): 383, 17375, 45454, 17373, 423

I didn't run LTC because the changes only really impact workers without popcount and there aren't any present on fishtest ATM. The correctness of the constexpr PopCnt16 LUT was verified locally with a x86-64 build; the build yields the same signature as master.

closes https://github.com/official-stockfish/Stockfish/pull/6952

No functional change
2026-07-16 08:28:24 +02:00
Linmiao XuandJoost VandeVondele 1c384d3a87 AVX2 fused SqrClippedReLU + ClippedReLU
Speedup measured with profile-build x86-64-avx2:
```
Result of  50 runs
==================
base (./sf-master    ) =     777661  +/- 8102
test (./sf-fused     ) =     796739  +/- 9127
diff                   =     +19077  +/- 3164

speedup        = +0.0245
P(speedup > 0) =  1.0000

CPU: 8 x AMD EPYC 7R13 Processor
```

Passed STC:
https://tests.stockfishchess.org/tests/view/6a5049335529b8472df8013c
LLR: 2.96 (-2.94,2.94) <0.00,2.00>
Total: 262624 W: 68444 L: 67815 D: 126365
Ptnml(0-2): 724, 28976, 71325, 29521, 766

closes https://github.com/official-stockfish/Stockfish/pull/6976

No functional change
2026-07-16 08:28:24 +02:00
Linmiao XuandJoost VandeVondele 4c78ba8920 AVX-512 squared clipped ReLU
Passed STC:
https://tests.stockfishchess.org/tests/view/6a4dd1075529b8472df7fd5e
LLR: 3.44 (-2.94,2.94) <0.00,2.00>
Total: 327136 W: 84861 L: 84124 D: 158151
Ptnml(0-2): 692, 34323, 92827, 35008, 718

closes https://github.com/official-stockfish/Stockfish/pull/6974

No functional change
2026-07-16 08:28:24 +02:00
mstemberaandJoost VandeVondele 8ddea72917 Add -fno-ipa-cp-clone for mingw
Add -fno-ipa-cp-clone for mingw just as we do for gcc.  I get about a 1% speedup.  My machine is pretty unreliable for bench so maybe someone can verify.

closes https://github.com/official-stockfish/Stockfish/pull/6973

No functional change
2026-07-16 08:28:24 +02:00
mstemberaandJoost VandeVondele a255ad59e3 Optimize evasions in Position::pseudo_legal()
STC: https://tests.stockfishchess.org/tests/view/6a4be17d5529b8472df7fa60
LLR: 3.68 (-2.94,2.94) <0.00,2.00>
Total: 405888 W: 105701 L: 104837 D: 195350
Ptnml(0-2): 872, 42472, 115373, 43374, 853

closes https://github.com/official-stockfish/Stockfish/pull/6968

No functional change
2026-07-16 08:28:21 +02:00
mstemberaandJoost VandeVondele 057e9bf4da Fix an MSVC debug break
With the latest master I started getting:
"A breakpoint instruction (__debugbreak() statement or a similar call) was executed in Stockfish.exe." when debugging in MSVC.

I don't really understand the issue but Fable fixed it and I verified it works.
Here is its summary:
Bug: Calling _get_wpgmptr() in CommandLine::get_binary_directory() triggered a debug CRT assertion failure (__debugbreak()) on Windows. The CRT only initializes the wide program name _wpgmptr when the application uses a wide entry point (wmain). Since Stockfish uses the narrow main(int, char**) entry point, _wpgmptr remains nullptr, and the debug CRT's parameter validation asserts when _get_wpgmptr is called.

Fix: Replaced _get_wpgmptr() with GetModuleFileNameW(), which queries the executable path directly from the OS and does not depend on the CRT entry point type. The implementation uses a grow-and-retry loop, so paths longer than MAX_PATH are handled correctly. If the API fails, the code falls back to the original argv0-based behavior. The #ifdef _MSC_VER guard was also removed, since the fix no longer relies on the MSVC CRT — non-MSVC Windows builds (e.g., MinGW) now benefit from the accurate path as well.

closes https://github.com/official-stockfish/Stockfish/pull/6972

No functional change
2026-07-16 08:28:08 +02:00
ConKirbyandJoost VandeVondele ca11d43709 Remove dead code left over from earlier refactors
The following have zero references repo-wide:

    Move::to_sq_unchecked() (types.h): added in 542c30c2 for a
    branchless correction-history update; its only caller was removed
    in 5ae13d2b ("Remove Redundant Branchless Execution"), leaving the
    method unused.

    PipeDeleter (misc.h): the custom deleter for the std::unique_ptr<FILE,
    PipeDeleter> that wrapped popen("lscpu"). That pipe was removed in
    c8375c2f ("On linux use sysfs instead of lscpu"), so the struct has
    been unused since.

Dropping PipeDeleter also removes the last user of <cstdio> in misc.h, so that include is dropped as well.

closes https://github.com/official-stockfish/Stockfish/pull/6948

No functional change
2026-07-16 08:16:29 +02:00
Joost VandeVondele 9a8dd81dd7 Revert "Scale Null Move Pruning reduction"
This reverts commit 9d4090e826.

causes very poor mate finding performance, ultimately causing a CI hang on a mate in 2

Testcase:

position fen k1B5/2p5/NbN5/P7/3p4/P2p4/P1prp3/2RbK3 b - - 0 1 setoption name Hash value 16
setoption name Threads value 4
go mate 2
ucinewgame

closes https://github.com/official-stockfish/Stockfish/pull/6966

Bench: 2466447
2026-07-11 20:35:40 +02:00
Joost VandeVondele c18feef187 Enhance version string
Use sha and git diff-index status for full version string.

closes https://github.com/official-stockfish/Stockfish/pull/6965

No functional change
2026-07-11 20:35:16 +02:00
FauziAkramandJoost VandeVondele 924d29d3ca Simplify Reduction formula for first picked move (ttMove)
Passed STC non-reg:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 124864 W: 32270 L: 32151 D: 60443
Ptnml(0-2): 273, 13766, 34278, 13799, 316
https://tests.stockfishchess.org/tests/view/6a44f4d9f97ff95f78795463

Passed LTC non-reg:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 335544 W: 87272 L: 87369 D: 160903
Ptnml(0-2): 157, 34355, 98852, 34244, 164
https://tests.stockfishchess.org/tests/view/6a46a188f97ff95f787956a1

closes https://github.com/official-stockfish/Stockfish/pull/6964

Bench: 2890786
2026-07-10 20:19:33 +02:00
ayushthepiro11andJoost VandeVondele 9d4090e826 Scale Null Move Pruning reduction dynamically based on evaluation margin
This patch introduces a dynamic scaling factor for Null Move Pruning (NMP) reductions. By scaling the reduction depth relative to the static evaluation margin against beta, the engine can afford more aggressive reductions in positions that are overwhelmingly favorable, optimizing CPU cycle utilization without compromising search integrity.

Passed STC:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 85504 W: 22383 L: 22000 D: 41121
Ptnml(0-2): 225, 9855, 22218, 10220, 234
https://tests.stockfishchess.org/tests/view/6a4793daf97ff95f78795812

Passed LTC:
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 205800 W: 53879 L: 53224 D: 98697
Ptnml(0-2): 98, 21747, 58561, 22390, 104
https://tests.stockfishchess.org/tests/view/6a4b864ff97ff95f78795e52

closes https://github.com/official-stockfish/Stockfish/pull/6963

Bench: 2513153
2026-07-10 20:19:02 +02:00
Linmiao XuandJoost VandeVondele fb1d777250 Split AVX-VNNI sparse affine accumulation two ways
Speedup measured by @anematode
```
Result of 100 runs
==================
base (...h.master.exe) = 1504448 +/- 15119
test (./stockfish.exe) = 1519239 +/- 15980
diff = +14791 +/- 4113

speedup = +0.0098
P(speedup > 0) = 1.0000

CPU: Intel Core Ultra 9 285H
```

Passed STC:
https://tests.stockfishchess.org/tests/view/6a4dc4c65529b8472df7fd56
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 48256 W: 12746 L: 12420 D: 23090
Ptnml(0-2): 144, 5310, 12913, 5598, 163

closes https://github.com/official-stockfish/Stockfish/pull/6961

No functional change
2026-07-10 20:18:25 +02:00
tonyandJoost VandeVondele 93ed4b53c4 Dynamic root score ema2
Improves calculation of root move score averages by dynamically calculating weight based on effort

Passed STC
https://tests.stockfishchess.org/tests/view/6a4ada97f97ff95f78795d5a
```
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 157248 W: 41003 L: 40515 D: 75730
Ptnml(0-2): 419, 18320, 40644, 18836, 405
```

Passed LTC
```
LLR: 3.03 (-2.94,2.94) <0.50,2.50>
Total: 197460 W: 51626 L: 50982 D: 94852
Ptnml(0-2): 85, 20849, 56237, 21455, 104
```

closes https://github.com/official-stockfish/Stockfish/pull/6960

Bench: 2388656
2026-07-10 20:17:55 +02:00
Miloslav MacůrekandJoost VandeVondele 9700f6f206 Fix crash with UCI_LimitStrength and tablebases at the root
`Skill::pick_best()` assumes the root moves are sorted by score in descending order:

```cpp
Value topScore = rootMoves[0].score;
int   delta    = std::min(topScore - rootMoves[multiPV - 1].score, int(PawnValue));
```

That does not have to be so with Syzygy tablebases at the root, where the moves are ordered by `tbRank` instead. `rootMoves[0]` is then not the highest score and `delta` can go negative. Since `delta * (rng.rand<unsigned>() % int(weakness))` is evaluated as unsigned, a negative `delta` wraps to a large value and the `int(...)` cast overflows, so the `score + push >= maxScore` check never passes and `best` stays `Move::none()`. The caller

```cpp
std::swap(rootMoves[0],
          *std::find(rootMoves.begin(), rootMoves.end(),
                     skill.best ? skill.best : skill.pick_best(rootMoves, multiPV)));
```

then dereferences `end()`. The result is a crash, or sometimes `bestmove (none)` / an illegal move, in tablebase endgames when `UCI_LimitStrength` is set (or `Skill Level` is below 20).

The fix computes the score range over the candidate moves directly, so `delta` stays non-negative and a valid move is always returned.

**To reproduce**:
```
setoption name UCI_LimitStrength value true
setoption name UCI_Elo value 2900
setoption name SyzygyPath value <syzygy tablebases path>
position fen 8/8/8/4k3/8/8/3BKN2/8 b - - 0 1
go wtime 250 btime 250 winc 100 binc 100
```

You need to call the go command a couple of times before crash, after which is ends with

```
bestmove a1e5 ponder (none)
```

and on the next `go wtime 250 btime 250 winc 100 binc 100` it crashes printing:

```
info string Available processors: 0-31
info string Using 1 thread
info string NNUE evaluation using nn-af1339a6dea3.nnue (106MiB, (83248, 1024, 31, 32, 1))
info string Network replica 1: Shared memory.
```

Bench is unchanged, since this code only runs under `UCI_LimitStrength` / `Skill Level`.

closes https://github.com/official-stockfish/Stockfish/pull/6957

No functional change
2026-07-10 20:17:23 +02:00
Linmiao XuandJoost VandeVondele 2d239de460 Split NEON dense affine accumulation two ways
Speedup measured locally with apple-silicon M3 Pro:

```
Result of  50 runs
==================
base (./sf-master    ) =    1570076  +/- 27215
test (...-neon-2chain) =    1578923  +/- 29000
diff                   =      +8847  +/- 5700

speedup        = +0.0056
P(speedup > 0) =  0.9988

CPU: 11 x arm
Hyperthreading: off
```

Passed STC:
https://tests.stockfishchess.org/tests/view/6a49678cf97ff95f78795b38
LLR: 4.43 (-2.94,2.94) <0.00,2.00>
Total: 137696 W: 36041 L: 35477 D: 66178
Ptnml(0-2): 289, 14663, 38396, 15195, 305

closes https://github.com/official-stockfish/Stockfish/pull/6956

No functional change
2026-07-10 20:16:42 +02:00
Timothy HerchenandJoost VandeVondele cd75d31127 Improve CLI error handling a bit
Reduce the number of *easy* ways to hit UB or an uncaught exception. This builds on Sopel's work on FEN validation.

Obviously SF is not and will never be a security boundary, but at least we can reduce the number of reports about uncaught `std::stoi` out of range, etc.

I also added a gentle fallback to `NumaConfig` specifically because that's something that (as a dev) I frequently set by hand in the CLI, but happy to revert if that's too annoying of a change.

closes https://github.com/official-stockfish/Stockfish/pull/6844

No functional change
2026-07-10 20:16:02 +02:00
Robert NurnbergandJoost VandeVondele 48a9118251 Minor cleanups June 2026
In the spirit of previous such PRs, this PR proposes a collection of nonfunctional changes.

I am happy to incorporate changes from other devs, and revert some of the proposed changes if the maintainers ask me to.

Apart from trivial changes, the proposed changes so far include:

* A requested edit to `AUTHORS` and a resorting of all entries, following DIN 5007 for the treatment of any special characters.
* Exclude the two recent integer type renaming commits from git blame.
* Tightening of some static asserts in `history.h` to avoid overflows. (Note that rounding errors for floating point types could lead to the assert in `operator<<` triggering at run-time.)
* Re-instate the 0.5s maximal thinking time in case of a single legal move and reword the comment to make it clear that it should not be tuned.
* ~~A small refactoring of the network loading code thanks to @dubslow.~~
* A refactoring of the "dtz is dtm" code, also thanks to @dubslow.

closes https://github.com/official-stockfish/Stockfish/pull/6928

No functional change
2026-07-10 20:12:38 +02:00
mstemberaandJoost VandeVondele eca43a97ef Remove unnecessary memset(), memcpy() from NetworkArcitecture::propagate()
Since https://github.com/official-stockfish/Stockfish/pull/6938 this is no longer needed.

closes https://github.com/official-stockfish/Stockfish/pull/6950

No functional change
2026-07-06 11:38:27 +02:00
Linmiao XuandJoost VandeVondele 2cbf39f845 Split NEON sparse affine accumulation three ways
Speedup measured locally with apple-silicon M3 Pro:

```
Result of  50 runs
==================
base (./sf_master    ) =    1817788  +/- 4388
test (...on_sparse_3c) =    1839826  +/- 5482
diff                   =     +22039  +/- 3215

speedup        = +0.0121
P(speedup > 0) =  1.0000

CPU: 11 x arm
Hyperthreading: off
```

Passed STC:
https://tests.stockfishchess.org/tests/view/6a496a49f97ff95f78795b3d
LLR: 2.92 (-2.94,2.94) <0.00,2.00>
Total: 44384 W: 11659 L: 11351 D: 21374
Ptnml(0-2): 86, 4673, 12406, 4901, 126

closes https://github.com/official-stockfish/Stockfish/pull/6951

No functional change
2026-07-06 11:33:51 +02:00
Adrian LadoniandJoost VandeVondele 7da31a1f1d Simplify futility multiplier
this patch simplifies the Value futility multiplier and changes pruning behaviour

passed stc: https://tests.stockfishchess.org/tests/view/6a462265f97ff95f78795600
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 38112 W: 10071 L: 9758 D: 18283
Ptnml(0-2): 88, 4341, 9903, 4618, 106

passed ltc: https://tests.stockfishchess.org/tests/view/6a470bc5f97ff95f7879576a
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 67482 W: 17738 L: 17363 D: 32381
Ptnml(0-2): 36, 7061, 19164, 7452, 28

patch rebased to latest master

closes https://github.com/official-stockfish/Stockfish/pull/6946

Bench: 2353997
2026-07-06 11:33:03 +02:00
Syine MinetaandJoost VandeVondele bc079c46a9 Fix Clang 22 codegen regression
Clang 22 apparently has a codegen regression in capture() and capture_stage() functions. GCC and the other versions of Clang also showed a marginal speedup which however could be due to noise.

Combined results of two STC tests:
https://tests.stockfishchess.org/tests/view/6a45b104f97ff95f78795570
https://tests.stockfishchess.org/tests/view/6a474aaff97ff95f787957a9

STC (clang++ 22):
LLR: 2.811 (-2.944,2.944) <0.00,2.00>
Ptnml(0-2): 67, 3094, 7975, 3306, 102

Non-regression STC (g++, clang++ ~21):
LLR: 6.617 (-2.944,2.944) <-1.75,0.25>
Ptnml(0-2): 428, 22211, 56555, 22553, 429

Local test results (Zen 4, x86-64-avx512icl, bench 512 1 16, 50 runs):

Clang (22.1.2): PASSED: speedup = +0.0169, P(speedup > 0) = 1.0000
GCC (15.2.0):   FAILED: speedup = +0.0031, P(speedup > 0) = 0.9597

closes https://github.com/official-stockfish/Stockfish/pull/6945

No functional change
2026-07-06 11:32:15 +02:00
Timothy HerchenandJoost VandeVondele 4436524a3e add rbit fallback
GCC 12.2 added `__rbitll` intrinsic to `arm_acle.h`, so we can add a fallback for older versions

Clang 10 (our new minimum supported version) is unaffected by all this

closes https://github.com/official-stockfish/Stockfish/pull/6949

No functional change
2026-07-06 11:31:28 +02:00
22548cb0c0 S人ving Privàte Ryän
Fixes problems with non-ASCII characters in paths. Fixes #3424.

Changes
- Bump the minimum supported Clang version to 11 to support std::filesystem
- Use std::optional for empty values instead of string sentinels like "None"
- Remove the FixedString class
- Move the EvalFile network path from the network struct to the engine
- Simplify parts of the command-line handling
- Remove the old dual-net logic from export_net
- And ofc we now support non-ascii paths for windows

Behavior Change
- Because the EvalFile is no longer part of the network, it won't be included in the hashing, thus the network sharing is no longer dependent on the EvalFile path
- As a result, if the binary version and network are the same, changing only the EvalFile location will now reuse the same shared network

This also fixes a master bug where:

```
export_net None
setoption name EvalFile value None
export_net None
Failed to export a net
```

would fail on the second export. With this patch, the second export succeeds.

Quick Fishtest Check that nets load like normal.
https://tests.stockfishchess.org/tests/view/6a455593f97ff95f787954da

<img width="833" height="1137" alt="image" src="https://github.com/user-attachments/assets/fffee63b-c63a-4d5e-b13d-87d58bc28c88" />

closes https://github.com/official-stockfish/Stockfish/pull/6937

No functional change

Co-authored-by: anematode <timothy.herchen@gmail.com>
2026-07-06 11:30:51 +02:00
anematodeandJoost VandeVondele d5bbc6b67d [RfC] RISC-V port and universal binary
Performance on Spacemit K3, thanks @edolnx for testing

master:
Total time (ms) : 65200
Nodes searched  : 3493826
Nodes/second    : 53586

riscv-scalable-port:
Total time (ms) : 15834
Nodes searched  : 3493826
Nodes/second    : 220653

Also thanks to @camel-cdr for guidance on RVV programming, and https://cloud-v.co for supplying an RVV instance to test with

passed STC:
LLR: 2.81 (-2.94,2.94) <0.00,2.00>
Total: 1152 W: 527 L: 108 D: 517
Ptnml(0-2): 0, 17, 167, 348, 44
https://tests.stockfishchess.org/tests/view/6a39895b3036e45021aeb368

## Summary

We've had a `riscv64` target for a while, but haven't really optimized for it, in particular the vector extension (RVV).

RVV, like SVE, is based on a scalable vector system where the vector length ranges from 128 to 65536. In practice implementations are between 128 and 2048, and 256 bits is quite common (e.g. the Spacemit K3 system above). Unfortunately this doesn't fit well into the rest of our code which assumes a fixed vector length, so what I've done is bypass the `VECTOR` ifdef (which now basically means "FIXED_LENGTH_VECTOR") and just have RVV-specific paths.

The ability to explicitly control `vl` makes the code quite readable, in my opinion. We use LMUL>1 in most places to take advantage of multi-vector instructions. Generally the LMULs were chosen to best support a 256-bit vlen, which is very common, but by virtue of how the vlen control works, the code works with any vlen. In a couple places, i.e., `get_changed_pieces` and `AffineTransformSparseInput::propagate`, we have separate implementations depending on the vlen, because the optimal LMUL varies a lot between implementations.

One little wrinkle is that `load_as` is compiled to a sequence of byte loads, because although unaligned loads are legal in RVA23, the spec says that they *may* be extremely slow (even though they usually aren't, in actual hw), so compilers are conservative. Thus I aligned the relevant buffers and made the semantics of `load_as` that the operand is aligned, by adding a runtime assertion.

### Universal binary

Adding a universal binary is pretty easy and we can just cross-compile. There are two targets: baseline rv64gc and riscv64-rva23, which is actually a smaller subset of RVA23 that also works on some older processors that don't support the full thing. We use clang because GCC, until recently, has a nasty bug with LTO and RVV.

Like the universal ARM and x86 builds, we check all the builds in CI. In this case we run bench with multiple vlens, 128 through 1024.

In the meantime I deleted the existing broken and unused riscv64 tests.

### Follow-ups

- Optimizations
- zvdot4a8i path

closes https://github.com/official-stockfish/Stockfish/pull/6920

No functional change
2026-07-03 20:29:50 +02:00
99489f57dd Simplify out pext attacks
Passed STC non-regression on avx512icl (https://tests.stockfishchess.org/tests/view/6a421b46f97ff95f78795061)
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 81632 W: 21240 L: 21075 D: 39317
Ptnml(0-2): 184, 8907, 22464, 9082, 179

Passed STC non-regression on bmi2 (https://tests.stockfishchess.org/tests/view/6a41f6f9f97ff95f7879503e)
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 78112 W: 20411 L: 20245 D: 37456
Ptnml(0-2): 188, 8449, 21632, 8583, 204

After #6845 , pext attacks are slower or equal to parallel-hyperbola attacks which works on all architectures that pext supports. SVE2 supports pext/pdep in vector registers, but it's much slower than the `rbit`-based HQ that we have on ARM, so I don't foresee the code being useful there.

Ofc I don't think we should remove the `bmi2` build or anything, because it's nice for people to be able to freely use pext when testing ideas, and I'm sure we'll some day find another use for it!

closes https://github.com/official-stockfish/Stockfish/pull/6939

No functional change

Co-authored-by: Dubslow <bunslow@gmail.com>
2026-07-03 20:27:33 +02:00
MFWandJoost VandeVondele 46428b9346 Allow cross-compilation of Android version on Mac
When compiling for Android (COMP=ndk) on macOS, the build fails because the
NDK's LLVM toolchain does not recognize the macOS-specific flags
-mdynamic-no-pic and -mmacosx-version-min.

This PR wraps these flags with ifneq ($(COMP),ndk) condition to skip them
during Android cross-compilation.

Please squash and merge this PR.

closes https://github.com/official-stockfish/Stockfish/pull/6927

No functional change
2026-07-03 20:26:34 +02:00
DisservinandJoost VandeVondele 3e1e8e38a4 Fix Clang-Format Action Inputs
The inputs have a different name.. https://github.com/thollander/actions-comment-pull-request#action-inputs

closes https://github.com/official-stockfish/Stockfish/pull/6940

No functional change
2026-07-03 20:25:35 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele 9449162d58 Simplify thread selection
The depth dependency in best thread selection no longer seems to gain sufficiently and so can be simplified away.

Passed non-reg STC SMP:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 437904 W: 111841 L: 112051 D: 214012
Ptnml(0-2): 485, 49454, 119332, 49148, 533
https://tests.stockfishchess.org/tests/view/6a3158590d5d4b19d08055cd

Passed non-reg LTC SMP:
LLR: 3.11 (-2.94,2.94) <-1.75,0.25>
Total: 484778 W: 123864 L: 124093 D: 236821
Ptnml(0-2): 121, 49237, 143920, 48972, 139
https://tests.stockfishchess.org/tests/view/6a377f3ba4f63d3271af0920

closes https://github.com/official-stockfish/Stockfish/pull/6935

No functional change
2026-07-03 20:24:56 +02:00
mstemberaandJoost VandeVondele 32619a0294 Enable MoveSorter for AVX512
STC: https://tests.stockfishchess.org/tests/view/6a42f773f97ff95f7879518a
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 132992 W: 34679 L: 34248 D: 64065
Ptnml(0-2): 318, 14004, 37436, 14405, 333

No functional change

Credit to @anematode for the original PR!

closes https://github.com/official-stockfish/Stockfish/pull/6941

No functional change
2026-07-03 20:24:27 +02:00
Syine MinetaandJoost VandeVondele 83514e4970 Filter invalid threat pairs early
Passed STC:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 246400 W: 63981 L: 63391 D: 119028
Ptnml(0-2): 612, 26055, 69299, 26599, 635
https://tests.stockfishchess.org/tests/view/6a3fafbf3036e45021aebaa0

Measured 1% speedup locally on x86-64-avx512icl build (bench 512 1 16, 50 runs):

PASSED: speedup = +0.0104, P(speedup > 0) = 1.0000

closes https://github.com/official-stockfish/Stockfish/pull/6936

No functional change
2026-07-03 20:22:16 +02:00
Shawn XuandJoost VandeVondele d91c7f6eac Update main network to nn-0ee0657fb25e.nnue
Passed STC:
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 26976 W: 7185 L: 6883 D: 12908
Ptnml(0-2): 89, 3058, 6895, 3354, 92
https://tests.stockfishchess.org/tests/view/6a46dd46f97ff95f78795707

Passed LTC:
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 101394 W: 26460 L: 26009 D: 48925
Ptnml(0-2): 74, 10871, 28367, 11300, 85
https://tests.stockfishchess.org/tests/view/6a46eff2f97ff95f7879573b

fixes a potential overflow when rescaling network evals.

nettest: vondele/nettest#403

closes https://github.com/official-stockfish/Stockfish/pull/6944

Bench: 2752030
2026-07-03 20:19:10 +02:00
e33bb26ee7 Update nnue-architecture to SFNNv15
Updating nnue to a new architecture including a newly trained net using recipe https://github.com/vondele/nettest/pull/395 and trainer https://github.com/official-stockfish/nnue-pytorch/pull/480.

The recipe contains many small improvements additionally the newly relabled data. Thus this is a combined effort.

Passed STC
https://tests.stockfishchess.org/tests/view/6a429fd8f97ff95f78795110
```
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 25312 W: 6725 L: 6422 D: 12165
Ptnml(0-2): 78, 2915, 6397, 3158, 108
```

Passed LTC
https://tests.stockfishchess.org/tests/view/6a434c4cf97ff95f78795235
```
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 32814 W: 8655 L: 8345 D: 15814
Ptnml(0-2): 17, 3469, 9132, 3765, 24
```

closes https://github.com/official-stockfish/Stockfish/pull/6938

Bench: 2639962

Co-authored-by: anematode <anematode@users.noreply.github.com>
Co-authored-by: Joost VandeVondele <vondele@users.noreply.github.com>
Co-authored-by: xu-shawn <xu-shawn@users.noreply.github.com>
2026-07-03 20:07:49 +02:00
yl25946andJoost VandeVondele 6088838797 Yeet psqt weights
Simplifying the ratio in the eval between the psqt and the positional eval to a basic addition

Passed Nonreg STC:
https://tests.stockfishchess.org/tests/view/6a3eaac63036e45021aeb937
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 207392 W: 53521 L: 53489 D: 100382
Ptnml(0-2): 585, 24412, 53748, 24288, 663

Passed Nonreg LTC:
https://tests.stockfishchess.org/tests/view/6a423666f97ff95f7879508e
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 27198 W: 7200 L: 6989 D: 13009
Ptnml(0-2): 12, 2794, 7779, 2999, 15

closes https://github.com/official-stockfish/Stockfish/pull/6934

Bench: 2067208
2026-06-30 06:22:29 +02:00
Shawn XuandJoost VandeVondele 4488343cf6 Update main network to nn-af1339a6dea3.nnue
This network is trained by adding the following binpacks, relabeled by @vondele and totaling 74B positions, to the distillation fine-tuning stage:
```
  - vondele/from_kaggle_1_relabel/leela96-filt-v2.min.split_0.relabel-BT4-tf13tune.binpack
  - vondele/from_kaggle_1_relabel/leela96-filt-v2.min.split_1.relabel-BT4-tf13tune.binpack
  - vondele/from_kaggle_1_relabel/leela96-filt-v2.min.split_2.relabel-BT4-tf13tune.binpack
  - vondele/from_kaggle_1_relabel/leela96-filt-v2.min.split_3.relabel-BT4-tf13tune.binpack
  - vondele/from_kaggle_1_relabel/leela96-filt-v2.min.split_4.relabel-BT4-tf13tune.binpack
  - vondele/from_kaggle_2_relabel/T60T70wIsRightFarseerT60T74T75T76.split_0.relabel-BT4-tf13tune.binpack
  - vondele/from_kaggle_2_relabel/T60T70wIsRightFarseerT60T74T75T76.split_1.relabel-BT4-tf13tune.binpack
  - vondele/from_kaggle_2_relabel/T60T70wIsRightFarseerT60T74T75T76.split_2.relabel-BT4-tf13tune.binpack
  - vondele/from_kaggle_2_relabel/T60T70wIsRightFarseerT60T74T75T76.split_3.relabel-BT4-tf13tune.binpack
  - vondele/from_kaggle_2_relabel/T60T70wIsRightFarseerT60T74T75T76.split_4.relabel-BT4-tf13tune.binpack
```

The relabeling effort has been completed during the testing of this patch, and a full training run is on the way. Thanks to @vondele, @anematode, @Disservin, @yl25946, and all who've contributed to the process.

Passed STC:
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 24512 W: 6500 L: 6201 D: 11811
Ptnml(0-2): 69, 2772, 6300, 3021, 94
https://tests.stockfishchess.org/tests/view/6a40b7083036e45021aebbd6

Passed LTC:
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 15708 W: 4232 L: 3960 D: 7516
Ptnml(0-2): 8, 1572, 4420, 1848, 6
https://tests.stockfishchess.org/tests/view/6a413d293036e45021aebc84

nettest PR: https://github.com/vondele/nettest/pull/388

closes https://github.com/official-stockfish/Stockfish/pull/6932

Bench: 2102535

Co-authored-by: Joost VandeVondele <Joost.VandeVondele@gmail.com>
2026-06-29 06:32:19 +02:00
Shawn XuandDisservin 9d2ba2bed5 Update main network to nn-1b6a82263149.nnue
Add `test80-2024-01-jan-2tb7p.min-v2.v6.relabel.binpack` to the distillation fine tuning stage, an additional 3.5B (2.9B non-skipped) positions.

nettest PR: https://github.com/vondele/nettest/pull/375

Passed STC (vs #6924):
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 57952 W: 15000 L: 14656 D: 28296
Ptnml(0-2): 164, 6651, 15003, 6993, 165
https://tests.stockfishchess.org/tests/view/6a3cca103036e45021aeb6f8

Passed LTC:
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 81456 W: 21265 L: 20858 D: 39333
Ptnml(0-2): 52, 8630, 22958, 9035, 53
https://tests.stockfishchess.org/tests/view/6a3dbe203036e45021aeb828

closes https://github.com/official-stockfish/Stockfish/pull/6929

Bench: 2703604
2026-06-27 15:30:39 +02:00
DavidandDisservin feb932533d Update bug report template
closes https://github.com/official-stockfish/Stockfish/pull/6926

No functional change
2026-06-27 15:30:23 +02:00
FauziAkramandJoost VandeVondele 47575ebd8b Deduplicating Color-Specific Piece Validation
Deduplicating Color-Specific Piece Validation.

The validation checks for the number of pawns and additional promoted pieces are duplicated for WHITE and BLACK. We can combine this logic into a single range-based for loop over both colors.

closes https://github.com/official-stockfish/Stockfish/pull/6922

No functional change
2026-06-25 13:11:17 +02:00
Daniel MonroeandJoost VandeVondele 3c858c19e6 Two small simplifications
Passed simplification STC
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 206368 W: 52592 L: 52560 D: 101216
Ptnml(0-2): 537, 24201, 53669, 24247, 530
https://tests.stockfishchess.org/tests/view/6a2c5ab00d5d4b19d0805326

Passed simplification LTC
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 109944 W: 27839 L: 27709 D: 54396
Ptnml(0-2): 61, 11938, 30844, 12068, 61
https://tests.stockfishchess.org/tests/view/6a2dc6e70d5d4b19d08053aa

closes https://github.com/official-stockfish/Stockfish/pull/6913

Bench: 2767133
2026-06-25 13:10:08 +02:00
zungurandJoost VandeVondele 782852b26a Clear capturedPiece in do_null_move
do_null_move copies the whole StateInfo from the previous state, which leaves capturedPiece holding the piece captured by the last real move, so inside the null-move subtree captured_piece() reports a stale capture. The priorCapture consumers in search are all guarded by prevSq != SQ_NONE or (ss-1)->currentMove.is_ok(), which are false at the null-move child, but the stalemate verification gate in qsearch reads captured_piece() unguarded and can be spuriously triggered by the stale value.

Clear the field, since a null move captures nothing.

Passed non-regression STC:
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 82784 W: 21172 L: 21011 D: 40601
Ptnml(0-2): 194, 8976, 22923, 9073, 226
https://tests.stockfishchess.org/tests/view/6a2b5b356b4aa63ddbf31518

Passed non-regression LTC:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 118134 W: 30007 L: 29891 D: 58236
Ptnml(0-2): 66, 11856, 35108, 11970, 67
https://tests.stockfishchess.org/tests/view/6a2c4c9c0d5d4b19d0805301

closes https://github.com/official-stockfish/Stockfish/pull/6910

No functional change
2026-06-25 13:09:32 +02:00
zungurandJoost VandeVondele 5b87bc48cd Remove unused search-layer APIs
The following have zero call sites repo-wide:

    SearchManager:🆔 never read or written (also never initialized, nor was it ever used).
    Search::Worker::elapsed_time(): never called. PV output uses tm.elapsed_time() (TimeManager) directly. (removed callers on 25361e5)
    MovePicker::begin()/end(): unused private accessors. (removed callers on 8c2d21f)

closes https://github.com/official-stockfish/Stockfish/pull/6909

No functional change
2026-06-25 13:09:03 +02:00
FauziAkramandJoost VandeVondele 1ece3c030d Simplify Evasion Logic
When the king is in check, a pseudo-legal move must be a valid evasion. Instead of duplicating the checking rules for king and non-king moves, we can leverage the existing MoveList<EVASIONS> class.

Passed non-reg STC:
LLR: 2.98 (-2.94,2.94) <-1.75,0.25>
Total: 187360 W: 47579 L: 47524 D: 92257
Ptnml(0-2): 418, 20266, 52263, 20309, 424
https://tests.stockfishchess.org/tests/view/6a28aa5b7c758d82accea13c

closes https://github.com/official-stockfish/Stockfish/pull/6902

No functional change
2026-06-25 13:07:50 +02:00
9fcd47a717 Update main network to nn-f8a759c05f9f.nnue
This network is further trained on a new BT4 distillation stage, fine tuning on ~2 billion positions relabeled with the value head output of `BT4-tf13tune.pb.gz`. The dataset can be found at https://huggingface.co/datasets/xushawn/test80-bt4-relabel. A modified branch of lc0 was used to derive this data: https://github.com/xu-shawn/lc0/tree/relabel_dual_stream_test

2 billion positions represent a tiny subset of the total training data, and BT4 relabeling is inherently computationally expensive. I expect a lot more gains as more data are relabeled, but it will likely require coordinated community effort. Everyone is welcome to contribute, and yl25946 has made a spreadsheet to track progress: https://docs.google.com/spreadsheets/d/1yanofhusEzDg8ZnurAw799ikoTY6GcqsNMYfpswOIbw/edit.

Special thanks to Viren6, who performed policy/value distillation experiments on Monty, and created the lc0 distillation fork that the current relabeler is based on; yl25946 for proposing the idea of large network distillations back in February 2025, running distillation experiments on the HL4096 network, and working on fine tuning attempts; vondele for nettest and suggesting the fine-tuning approach; and many others on the knowledge distillation thread in the SF Discord #ideas channel.

nettest PR: https://github.com/vondele/nettest/pull/369

Ongoing STC:
 LLR: -0.01 (-2.94,2.94) <0.00,2.00>
Total: 72224 W: 18891 L: 18784 D: 34549
Ptnml(0-2): 336, 8437, 18332, 8798, 209
https://tests.stockfishchess.org/tests/view/6a3ae7913036e45021aeb4a0

Passed LTC:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 25110 W: 6566 L: 6288 D: 12256
Ptnml(0-2): 27, 2625, 6957, 2935, 11
https://tests.stockfishchess.org/tests/view/6a3b73513036e45021aeb51e

Passed VLTC:
 LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 18544 W: 4924 L: 4658 D: 8962
Ptnml(0-2): 5, 1730, 5533, 2002, 2
https://tests.stockfishchess.org/tests/view/6a3bbe233036e45021aeb56e

closes https://github.com/official-stockfish/Stockfish/pull/6924

Bench: 2710209

Co-authored-by: Li Ying <121075683+yl25946@users.noreply.github.com>
Co-authored-by: Viren6 <94880762+Viren6@users.noreply.github.com>
2026-06-25 13:06:10 +02:00
Michael ChalyandJoost VandeVondele d64835051e Do more aggressive probCut when improving.
If improving is true reduce depth of probCut search by 1.
Passed STC:
https://tests.stockfishchess.org/tests/view/6a2a6ceb17167cbe7100a909
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 119136 W: 30591 L: 30158 D: 58387
Ptnml(0-2): 323, 13831, 30845, 14228, 341
Passed LTC:
https://tests.stockfishchess.org/tests/view/6a2d65a40d5d4b19d080537f
LLR: 2.96 (-2.94,2.94) <0.50,2.50>
Total: 186552 W: 47377 L: 46755 D: 92420
Ptnml(0-2): 89, 20047, 52395, 20643, 102

closes https://github.com/official-stockfish/Stockfish/pull/6914

Bench: 2422062
2026-06-25 13:04:20 +02:00
Dalton HanawayandJoost VandeVondele 6f97594eda prefetch the TT entry as soon as the move is known
Worker::do_move computes the successor hash key via the new
Position::key_after(m) and prefetches the TT entry one full do_move
earlier than the existing prefetch in Position::do_move. key_after does
not model castling, en passant or promotion keys exactly; for rare
moves the prefetch lands on an unused line.

`key_after` has been around since 2014 (https://github.com/official-stockfish/Stockfish/commit/82d065b0) and was removed in (https://github.com/official-stockfish/Stockfish/pull/5770). Adding back `prefetch_key` helps in common, normal moves at the cost of extra compute.

Speedup (PGO vs PGO, interleaved paired bench, n=48 pairs, Apple M2
Pro / apple-silicon): +0.69% [0.47, 0.91]

Passed STC:
https://tests.stockfishchess.org/tests/view/6a291f8d7c758d82accea17f
LLR: 4.24 (-2.94,2.94) <0.00,2.00>
Total: 473504 W: 121250 L: 120228 D: 232026
Ptnml(0-2): 1112, 51137, 131251, 52121, 1131

No functional change

closes https://github.com/official-stockfish/Stockfish/pull/6911

No functional change
2026-06-25 13:03:44 +02:00
anematodeandJoost VandeVondele 31e89adf70 [RfC] Make CI faster
We can straightforwardly parallelize the `check_universal.sh` script, which takes quite a bit of time for the x86 builds.

closes https://github.com/official-stockfish/Stockfish/pull/6919

No functional change
2026-06-25 13:03:07 +02:00
Robert Nurnberg @ elitebookandDisservin 74a0a73715 Final fix for MultiPV mate PV corner cases
This PR fixes the remaining corner cases in the treatment of MultiPV
mated-in PVs, as well as an oversight in #6886. See the discussion in

In particular:
1. `previousScore` and `previousPV` can only be trusted, if that
   rootmove was indeed fully searched in the previous iteration.
2. A move beyond `pvIdx` (that was hence not fully searched) may have an
   exact loss score that cannot be trusted. So if a MultiPV search gets
   aborted while searching `pvIdx`, we mark all the following loss
   scores as bounds.
3. The forgotten mate logic also got broken in #6886, because the
   `previousPV` of the forgotten mate's bestmove can only be trusted if
   that move was fully searched in the previous iteration, something
   that is not guaranteed. So we now store both `lastBestMoveScore` and
   `lastBestMovePV`.

Here some scenarios for MultiPV = 8 that explain how master was broken:
1. Move A with an inexact mated-in-2 score from the previous iteration
   (so outside the top8 moves) gets flushed into the top8 moves for the
   current iteration, because the previous top8 move B is now scored as
   a mated-in-1. Hence we cannot trust `previousScore` or `previousPV`
   for move A, if the search gets aborted while it is being searched.
2. In the scenario above, move B has `Score != -VALUE_INFINITE` and a
   mated-in-1 score, which cannot be trusted as it was not fully
   searched.
3. Iteration N has bestmove A with mated-in-10, which gets recorded in
   `lastBestMoveScore` (renamed from `lastIterationScore`). Iteration 11
   forgets the mate and has bestmove B with a cp score, move A may have
   an incomplete PV, and may even have a non-mate score. Iteration 12
   gets aborted, and in trying to remember the forgotten mate, master
   recovers the `previousScore` and `previousPV` of move A, which may be
   neither mate nor complete.

Passed STC non-reg:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 69728 W: 17748 L: 17573 D: 34407
Ptnml(0-2): 143, 7571, 19274, 7720, 156
https://tests.stockfishchess.org/tests/view/6a2c40c60d5d4b19d08052f2

closes https://github.com/official-stockfish/Stockfish/pull/6906

No functional change
2026-06-14 09:58:13 +02:00
anematodeandDisservin f9b002cf7d Share continuation history between threads
Passed 8th 5+0.05 https://tests.stockfishchess.org/tests/view/6a0bfdb46524d21ee79b879b
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 23472 W: 6103 L: 5823 D: 11546
Ptnml(0-2): 26, 2501, 6403, 2779, 27

Passed 8th 20+0.2 https://tests.stockfishchess.org/tests/view/6a0c87196524d21ee79b885c
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 44692 W: 11640 L: 11319 D: 21733
Ptnml(0-2): 12, 4418, 13169, 4731, 16

Passed 16th 5+0.05 https://tests.stockfishchess.org/tests/view/6a20533f818cacc1db0ad32b
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 14720 W: 3910 L: 3642 D: 7168
Ptnml(0-2): 6, 1434, 4223, 1680, 17

Passed 64th 10+0.1 https://tests.stockfishchess.org/tests/view/6a20ae8e818cacc1db0ad369

LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 34096 W: 8889 L: 8607 D: 16600
Ptnml(0-2): 4, 2974, 10808, 3260, 2

Continuation history is fixed size so there's a much more false sharing and a larger speed loss here at high thread counts, unfortunately.

vondele's machine, 4x70

```
==== master ====
Average (over 3):  294528128
==== shared-conthist ====
Average (over 3):  282364217 (~4% slowdown)
```

my machine, 8x32
```
Nodes/second               : 243157385
Nodes/second               : 228374554 (~6% slowdown)
```

Evidently it still gains at 64th, but a few followup ideas to try get the speed back:
- Add padding in `PieceToHistory` stats so there's less false sharing.
- Subdivide continuation history more finely than shared correction history.
- Scramble the `PieceToHistory` indexing so there's less false sharing

closes https://github.com/official-stockfish/Stockfish/pull/6905

No functional change
2026-06-14 09:58:13 +02:00
zungurandDisservin f9beec5fa5 Reuse computed ray bitboard in update_piece_threats
Reuse the already-computed ray instead of calling ray_pass_bb a second time with identical arguments.

closes https://github.com/official-stockfish/Stockfish/pull/6899

No functional change
2026-06-14 09:58:13 +02:00
zungurandDisservin ed8e35d759 Remove unused code from the post NNUE accumulator merge
After merging the HalfKA and Threats accumulators (7c7fe322) and the
subsequent removal of the double-incremental/fused update, a number of
NNUE helpers and fields became unreachable. Each was verified to have
zero callers/readers across the source tree:

- FusedUpdateData logic in FullThreats: the fused-update branch of
  append_changed_indices and the FusedUpdateData parameter are unused;
  the accumulator update no longer passes fused data.
- FullThreats::requires_refresh: never called. The live king-bucket
  refresh check is HalfKAv2_hm::requires_refresh (PSQFeatureSet), used
  in nnue_accumulator.
- HalfKAv2_hm::append_active_indices: never called. The live
  active-index builder is FullThreats::append_active_indices
  (ThreatFeatureSet).
- DirtyThreats::us, prevKsq and ksq: written in do_move but only read by
  the now-removed FullThreats::requires_refresh. Removing them also
  drops three stores from the do_move path.
- Unused feature Name constants and the unused FtOneVal / HiddenMaxVal
  constants in nnue_common.h.
- Two stale feature-header banner comments.

closes https://github.com/official-stockfish/Stockfish/pull/6898

No functional change
2026-06-14 09:58:13 +02:00
Disservin 6e4e03fd28 Replace Remaining Types
Somehow a few size_t slipped through https://github.com/official-stockfish/Stockfish/pull/6874

closes https://github.com/official-stockfish/Stockfish/pull/6897

No functional change
2026-06-14 09:58:13 +02:00
Disservin 718a001e6a Fixup RelaxedAtomic operator Types
As pointed out in
https://github.com/official-stockfish/Stockfish/commit/0111d11e2356147bad21c1f890b271a30d10a580#r188197707,
the parameter type should be `T` not `int`

closes https://github.com/official-stockfish/Stockfish/pull/6896

No functional change
2026-06-14 09:58:13 +02:00
Joost VandeVondeleandDisservin 0dc19b61ae More stable matetrack CI
This version handles aborting engine processes more gracefully. This
also test the engine prior to use, as the process is nevertheless not
fully robust.

closes https://github.com/official-stockfish/Stockfish/pull/6893

No functional change
2026-06-14 09:58:05 +02:00
anematodeandJoost VandeVondele 86f1df7134 Fix material key computation error
Fixes an error introduced in 278a755fb5 https://github.com/official-stockfish/Stockfish/pull/6891

Also enables more checking in debug builds

closes https://github.com/official-stockfish/Stockfish/pull/6894

No functional change
2026-06-10 23:01:50 +02:00
zungurandJoost VandeVondele 5595cb20ea Fix FEN validation of pawns on first or eighth rank
The FEN validation check intended to reject pawns on the first or eighth rank uses the `Rank` enum values in a bitwise OR operation:

`if (pieces(PAWN) & (RANK_1 | RANK_8))`

`RANK_1 | RANK_8` evaluates to the integer `0 | 7 == 7` instead of a bitboard, so the expression only tests squares A1, B1 and C1. As a result, unsupported positions with pawns elsewhere on the first or eighth rank are silently accepted. For instance, `position fen 3P3k/8/8/8/8/8/8/3K4 w - - 0 1` is accepted even though the pawn on d8 makes the position unsupported.

Use the `Rank1BB | Rank8BB` bitboard constants so any pawn on the first or eighth rank are correctly rejected.

closes https://github.com/official-stockfish/Stockfish/pull/6887

No functional change
2026-06-10 12:44:57 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele 7b37032885 Stop searching if we cannot find a better move
In a recent CCC event, Stockfish (probably through no fault of its own), lost some games on time when it was winning and when it had already found the move that delivers checkmate.

This patch stops the search when TM is active, and when mainthread can be certain that it is impossible to find a better move. That is if (i) it has found mate-in-1, (ii) it has found a mate-in-2 or (iii) it has found a mated-in-1.

patch:
```
position fen 5K2/8/2qk4/2nPp3/3r4/6B1/B7/3R4 w - e6
go wtime 100000000 winc 100000000
info string Available processors: 0-7
info string Using 1 thread
info string NNUE evaluation using nn-71d6d32cb962.nnue (106MiB, (83248, 1024, 31, 32, 1))
info string Network replica 1: Shared memory.
info depth 1 seldepth 3 multipv 1 score mate 1 nodes 30 nps 30000 hashfull 0 tbhits 0 time 1 pv d5e6
bestmove d5e6
```

master:
```
position fen 5K2/8/2qk4/2nPp3/3r4/6B1/B7/3R4 w - e6
go wtime 100000000 winc 100000000
info string Available processors: 0-7
info string Using 1 thread
info string NNUE evaluation using nn-71d6d32cb962.nnue (106MiB, (83248, 1024, 31, 32, 1))
info string Network replica 1: Shared memory.
info depth 1 seldepth 3 multipv 1 score mate 1 nodes 30 nps 15000 hashfull 0 tbhits 0 time 2 pv d5e6
<snip>
info depth 245 seldepth 2 multipv 1 score mate 1 nodes 5886 nps 367875 hashfull 0 tbhits 0 time 16 pv d5e6
bestmove d5e6
```

Note: In MultiPV analysis (extremely rare with TM active), we take the point of view that the user would like to continue to search until none of the PVs can be improved anymore. This means we only stop if the worst searched line is at least a mate-in-2, or if the best searched line is a mated-in-1.

closes https://github.com/official-stockfish/Stockfish/pull/6879

No functional change
2026-06-10 12:42:01 +02:00
Carlos EsparzaandJoost VandeVondele 278a755fb5 Reorder operations in do_move
Passed STC
 https://tests.stockfishchess.org/tests/view/6a2682ce351b79f679cc47c5
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 57248 W: 14730 L: 14399 D: 28119
Ptnml(0-2): 145, 6159, 15698, 6464, 158

Reordering operations in `do_move` allows us to effectively prefetch the TT entry earlier, since the piece moving helpers don't actually modify the position key.  I suspect that with threat inputs, `put_piece` and friends got a lot more expensive, and so this helps us a lot.

vondele's machine:

==== master ====
1 Nodes/second : 294311526
2 Nodes/second : 297068312
3 Nodes/second : 297418763
Average (over 3):  296266200
==== pfearly ====
1 Nodes/second : 303986449
2 Nodes/second : 304221719
3 Nodes/second : 305302969
Average (over 3):  304503712 (+2.78%)

Locally, `bench`:

Result of 200 runs
speedup         = +0.0158
P(speedup > 0) =  1.0000

As expected it helps even more in a large-hash, NUMA setting.

closes https://github.com/official-stockfish/Stockfish/pull/6891

No functional change
2026-06-10 12:37:12 +02:00
anematodeandJoost VandeVondele 7c7fe322ea Merge HalfKA and Threats accumulators
Passed STC (https://tests.stockfishchess.org/tests/view/6a2893ad7c758d82accea129):
LLR: 3.20 (-2.94,2.94) <0.00,2.00>
Total: 23328 W: 6145 L: 5838 D: 11345
Ptnml(0-2): 50, 2463, 6346, 2740, 65

Instead of repeatedly doing the sum HalfKA + threats at the end, it's profitable to simply store one accumulator per side that combines them. This also avoids an extra/load store of an accumulator, and halves the cache footprint of the accumulators.

For full refreshes, we always compute both halfka and threats simultaneously. Any threat full refresh is always a halfka refresh because it occurs when the king crosses the center line, while halfka refreshes are required for ANY king move, so we don't need a separate detection path for threats.

I get about a 2.5% speedup locally with this, but I'd appreciate other ppl's measurements.

closes https://github.com/official-stockfish/Stockfish/pull/6890

No functional change
2026-06-10 08:40:26 +02:00
anematodeandJoost VandeVondele 0111d11e23 Add ability to disable performance-sensitive relaxed atomic operations at compile time
Adds a `RelaxedAtomic`  wrapper around either `T` or `std::atomic<T>` and `USE_SLOPPY_ATOMICS` preprocessor define. The intent of this flag is to allow easy disabling of atomics on WASM, where even relaxed atomics are expensive because all atomics have `seq_cst` semantics.

Passed non regression STC
LLR: 2.99 (-2.94,2.94) <-1.75,0.25>
Total: 50624 W: 12976 L: 12776 D: 24872
Ptnml(0-2): 112, 5445, 14005, 5631, 119
https://tests.stockfishchess.org/tests/view/6a1f690e818cacc1db0ad2c7

Passed non-regression STC SMP
 LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 163696 W: 41514 L: 41438 D: 80744
Ptnml(0-2): 162, 18272, 44904, 18348, 162
https://tests.stockfishchess.org/tests/view/6a21fb97351b79f679cc44b3

Using this class for the TT also allows us to remove the TSAN suppressions, since the UB is fixed.

closes https://github.com/official-stockfish/Stockfish/pull/6877

No functional change
2026-06-09 21:52:29 +02:00
Abdul KhanandJoost VandeVondele 415ff793a0 Fix test harness timeout enforcement
Fixes #6881.

`timeout_decorator()` used a `ThreadPoolExecutor` context manager around blocking output waits. When `future.result(timeout=...)` timed out, leaving the context manager still waited for the worker thread to finish, so a blocked stdout read could keep the instrumented tests hanging past the configured timeout.

This change removes that executor wrapper for interactive Stockfish output waits. The harness now drains process output on a daemon reader thread, queues received lines, and applies the deadline directly while waiting for the next queued line. `TimeoutException` also initializes the base exception message so failures show useful text.

Validation:
- `python3 -m py_compile tests/testing.py tests/instrumented.py`
- local timeout smoke test: a 0.2s no-output wait raises in ~0.204s
- Stockfish smoke test: startup/`uciok` read succeeds, deliberate no-output wait raises in ~0.205s, engine exits 0
- `make -C src -j4 build`
- `../tests/signature.sh` -> `2814421`

closes https://github.com/official-stockfish/Stockfish/pull/6882

No functional change
2026-06-09 19:38:00 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele 1eff8b0389 Guarantee complete mate PVs in multiPV analysis
This PR introduces the additional `RootMove` attribute `previousPV` so that scores and PVs we send to the GUI in MultiPV analysis always match. This allows us in particular to extend our guarantee of exact mate (and TB win/loss) scores having a complete PV (leading to checkmate in the correct number of plies) to all PV lines. Recall that master fails here, since partially searched root moves may send to the GUI the previous score with the current/modified PV. See #6784.

The PR also uses the new attribute to extend the followPV logic to the analysis of sidelines, building on the idea in #6813 by @joergoster.

Passed non-reg STC:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 166880 W: 42357 L: 42282 D: 82241
Ptnml(0-2): 394, 18685, 45177, 18820, 364
https://tests.stockfishchess.org/tests/view/6a0dea55818cacc1db0abb6a

Failed non-reg LTC:
LLR: -2.97 (-2.94,2.94) <-1.75,0.25>
Total: 890520 W: 224168 L: 225282 D: 441070
Ptnml(0-2): 390, 91902, 261789, 90790, 389
https://tests.stockfishchess.org/tests/view/6a1143ad818cacc1db0ac14c

Opening as draft for discussion on how to proceed. In SinglePV analysis, the patch is completely nonfunctional. But it is maybe a (small?) slowdown because of the increased size of `RootMove`. I am not sure if there as an elegant way to enrich the class only for MultiPV analysis (but the switch can happen at any time through the UCI interface), or to mitigate the speed penalty in some other way.

A local speedup test shows only a small slowdown on my system (but still high error bars):
```
sf_base =  1156928 +/-   1459 (95%)
sf_test =  1155885 +/-   1283 (95%)
diff    =    -1043 +/-   1777 (95%)
speedup = -0.09021% +/- 0.154% (95%)
```

The PR also adds the new MultiPV mate PV correctness check to the CI.

closes https://github.com/official-stockfish/Stockfish/pull/6886

No functional change
2026-06-09 19:36:14 +02:00
anematodeandJoost VandeVondele 8e711c29fe Add wasm32 and wasm32-relaxed-simd targets, and light optimizations
Lichess maintains some patches on top of SF dev to get it working with Emscripten. This PR moves some of these patches into SF and adds WASM to CI. It also adds a few changes in places where the x86 intrinsics don't cleanly map onto WebAssembly SIMD instructions; otherwise, we use Emscripten's x86 compatibility layer and take SSE4.1 code paths.

Summary of the compatibility changes:

- Define `wasm32` and `wasm32-relaxed-simd` targets.
    - We don't support wasm without SIMD; it'd be a waste of time.
- Add option to disable TBs
    - This is required because `tbprobe.cpp` pulls in `mmap`. This option can be used on any target, of course, but it's only enabled by default for wasm.
- Add compilation job + test to CI

And the changes for performance:

- Disable atomics for shared history on wasm
    - Atomics are always `seq_cst` there, which can be quite slow (even on the x86, stores are locked `xchg [mem], reg`)
- Add SSE code path to `get_changed_pieces`, modeled after the AVX2 path
- `_mm_mulhi_epi16` has a complicated emulation sequence, so for the pairwise multiplication, use an approach similar to the NEON impl.
- __int128 is gets lowered to runtime functions on wasm, so use the fallback impl for `mul_hi64`
- V8 does a poor job with the NNZ finding, so use a slightly different sequence there
- Add relaxed simd support for `m128_dpbusd`.

Some local perf figures (single-threaded speedtest):

```
wasm
Nodes/second               : 902523
sse4.1
Nodes/second               : 1155380
avx512icl
Nodes/second               : 1676184
```

Further avenues to explore:
- Optimize for performance under V8's experimental AVX revectorizer (Currently it's about +10% in my testing, but could definittely be more)
- Branch hinting. For example, run bench while collecting branch frequency info, then inject it late in the WASM compilation pipeline. I tried this locally and it didn't help much, but maybe I'm missing something.
- PGO. Gives +1.5% NPS locally, but hard to integrate with WASM compilation wrokflows

closes https://github.com/official-stockfish/Stockfish/pull/6875

No functional change
2026-06-09 19:35:05 +02:00
DisservinandJoost VandeVondele dd3e1c4a50 Consistent Integer Types
example of using, to avoid mixed usage of std::uint/std::int and uint/int...
```cpp
using u64 = std::uint64_t;
using u32 = std::uint32_t;
using u16 = std::uint16_t;
using u8  = std::uint8_t;

using i64 = std::int64_t;
using i32 = std::int32_t;
using i16 = std::int16_t;
using i8  = std::int8_t;

using usize = std::size_t;
using isize = std::ptrdiff_t;

#if defined(__GNUC__) && defined(IS_64BIT)
__extension__ using u128 = unsigned __int128;
__extension__ using i128 = signed __int128;
#endif
```

closes https://github.com/official-stockfish/Stockfish/pull/6874

No functional change
2026-06-08 19:54:33 +02:00
FauziAkramandJoost VandeVondele e4a635486a Simplify margin formula
Passed STC non-reg:
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 46944 W: 11934 L: 11730 D: 23280
Ptnml(0-2): 95, 5403, 12273, 5605, 96
https://tests.stockfishchess.org/tests/view/6a0b018d6524d21ee79b8680

Passed LTC non-reg:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 535980 W: 135398 L: 135702 D: 264880
Ptnml(0-2): 241, 58385, 151023, 58119, 222
https://tests.stockfishchess.org/tests/view/6a0c3ab86524d21ee79b87f2

Passed VLTC non-reg:
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 126632 W: 32068 L: 31961 D: 62603
Ptnml(0-2): 14, 12466, 38244, 12583, 9
https://tests.stockfishchess.org/tests/view/6a1ac130818cacc1db0ad03c

closes https://github.com/official-stockfish/Stockfish/pull/6880

Bench: 3493826
2026-06-06 10:15:00 +02:00
dav1312andJoost VandeVondele 92fe6b6f4c Simplify RankAttacks initialization
Simplify by replacing the manual ray-casting loop with a single call to the canonical sliding_attack() helper.

closes https://github.com/official-stockfish/Stockfish/pull/6869

No functional change
2026-06-06 10:13:46 +02:00
AdrianandJoost VandeVondele 73826352d4 Simplify quiet history update expression
This patch simplifies the quiet history update expression in search.cpp.

The change is behaviour‑identical and removes unnecessary complexity from
the update_quiet_histories call.

Passed STC (non‑regression):
https://tests.stockfishchess.org/tests/view/6a14367b818cacc1db0ac7c3
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 52224 W: 13338 L: 13135 D: 25751
Ptnml(0-2): 118, 6065, 13557, 6240, 132

Passed LTC (non‑regression):
https://tests.stockfishchess.org/tests/view/6a155046818cacc1db0ac94d
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 136812 W: 34689 L: 34586 D: 67537
Ptnml(0-2): 51, 14770, 38668, 14859, 58

Both STC and LTC passed. Patch rebased to latest master.

closes https://github.com/official-stockfish/Stockfish/pull/6863

Bench: 2549662
2026-06-06 10:12:36 +02:00
DubslowandJoost VandeVondele db11ca42d5 Guard tt depth edits from racy underflows
Two recent patches, involving editing TTEs, introduced potential racy underflows. Guarding against these doesn't appear to gain elo but does improve sanity.

fixes https://github.com/official-stockfish/Stockfish/issues/6847

STC: https://tests.stockfishchess.org/tests/view/6a1c8c4b818cacc1db0ad158
LLR: 3.85 (-2.94,2.94) <-1.75,0.25>
Total: 125984 W: 32248 L: 32055 D: 61681
Ptnml(0-2): 292, 13678, 34866, 13857, 299

SMP STC: https://tests.stockfishchess.org/tests/view/6a1fb2ba818cacc1db0ad2e2
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 104272 W: 26524 L: 26385 D: 51363
Ptnml(0-2): 103, 11650, 28494, 11783, 106

closes https://github.com/official-stockfish/Stockfish/pull/6883

No functional change
2026-06-06 10:08:26 +02:00
Stefan GeschwentnerandJoost VandeVondele 645b636dfa Tweak best move history updates.
If not at an PV node, scale history bonus for the found best move up proportionally to the count of the other moves searched.

Passed STC:
LLR: 2.96 (-2.94,2.94) <0.00,2.00>
Total: 219712 W: 56104 L: 55525 D: 108083
Ptnml(0-2): 565, 25482, 57188, 26051, 570
https://tests.stockfishchess.org/tests/view/6a1306fb818cacc1db0ac5b7

Passed LTC:
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 53988 W: 13851 L: 13498 D: 26639
Ptnml(0-2): 22, 5779, 15051, 6108, 34
https://tests.stockfishchess.org/tests/view/6a1b557e818cacc1db0ad0b8

closes https://github.com/official-stockfish/Stockfish/pull/6871

Bench: 2935474
2026-06-06 10:05:50 +02:00
Niklas FiekasandJoost VandeVondele 9eb836b3b5 Compute simplified HQ r/rr at runtime
Hyperbola Quintessence works out without the "... * 2" in r and rr. This then is cheap enough to compute at runtime, instead of looking it up, for a minor speedup.

Passed apple-silicon|armv8|loongarch64 STC:
https://tests.stockfishchess.org/tests/view/6a15f031818cacc1db0aca49
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 410208 W: 104369 L: 103556 D: 202283
Ptnml(0-2): 758, 43951, 114883, 44744, 768

Verified attacks_bb<ROOK>() and attacks_bb<BISHOP>() assembly remains unchanged on x86 (gcc 16.1.1).

closes https://github.com/official-stockfish/Stockfish/pull/6870

No functional change
2026-06-06 10:04:39 +02:00
anematodeandJoost VandeVondele db6cba47d6 More loongarch optimizations
Passed STC:
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 113120 W: 29094 L: 28674 D: 55352
Ptnml(0-2): 362, 12619, 30248, 12899, 432
https://tests.stockfishchess.org/tests/view/6a1486f7818cacc1db0ac832

closes https://github.com/official-stockfish/Stockfish/pull/6862

No functional change
2026-06-06 10:03:25 +02:00
BerektassulyandJoost VandeVondele 8416ccafff fix Makefile universal build selection
and remove armv8 left-over

closes https://github.com/official-stockfish/Stockfish/pull/6876

No functional change
2026-06-06 10:01:37 +02:00
CiekceandJoost VandeVondele 0c1001c93e Disable PEXT on AMD Excavator CPUs
Excavator also supports BMI2, where it has the same microcoded performance implications as on Zen pre-3.

closes https://github.com/official-stockfish/Stockfish/pull/6868

No functional change
2026-06-06 09:58:43 +02:00
Joost VandeVondele 91045b2ff6 [CI] Update SDE action
more robust and efficient installation process.
Thanks @ppetrovt

closes https://github.com/official-stockfish/Stockfish/pull/6885

No functional change
2026-06-06 09:57:45 +02:00
FauziAkramandJoost VandeVondele b1053e60b7 Simplify Internal Iterative Reductions (IIR) Condition
Passed STC non-reg:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 75840 W: 19367 L: 19192 D: 37281
Ptnml(0-2): 186, 8837, 19710, 8990, 197
https://tests.stockfishchess.org/tests/view/6a0b02ba6524d21ee79b8685

Passed LTC non-reg:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 166920 W: 42039 L: 41967 D: 82914
Ptnml(0-2): 78, 18155, 46919, 18233, 75
https://tests.stockfishchess.org/tests/view/6a0c98fb6524d21ee79b8875

Passed VTC non-reg:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 46390 W: 11914 L: 11729 D: 22747
Ptnml(0-2): 8, 4543, 13911, 4722, 11
https://tests.stockfishchess.org/tests/view/6a143877818cacc1db0ac7c5

closes https://github.com/official-stockfish/Stockfish/pull/6861

Bench: 2814421
2026-05-29 19:06:38 +02:00
8cbca11a53 macOS universal binary, take 2
- Add new target `macos-lipo`.
   - Created by compiling a universal x86 binary (no PGO) and a standard Apple silicon binary (with PGO), then combining them into a Mach-O fat binary
   - To keep only one copy of the net, we add custom loading logic in the x86 section. The executable reads its own path and mmaps the net that's in the ARM section.
       - The offset and size (from the executable base) of the mapping is injected after compilation in `patch_x86_slice.sh`
   - avx512 on macOS isn't advertised in the xcr0 register by default. The simple solution I came up with is to execute a dummy AVX512 instruction, which sets up the register, before calling `__builtin_cpu_init`.

Some housekeeping as well:
- Rename `armv8-universal` -> `arm64-universal`.
- Add standard copyright headers to the files we've added recently.

Potential follow-ups:
- Disservin's Makefile cleanup
- Alternative ideas for the net loading. In particular, this will error out if the user strips the binary (since that'll invalidate the offset).

closes https://github.com/official-stockfish/Stockfish/pull/6860

No functional change

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-29 19:05:53 +02:00
Ryan HirschandJoost VandeVondele 24a551899e Speed up feature transform on neon
STC:
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 109344 W: 27919 L: 27520 D: 53905
Ptnml(0-2): 200, 11718, 30434, 12123, 197
https://tests.stockfishchess.org/tests/view/6a123ed0818cacc1db0ac39e

Updated to anematode’s suggestion - apparently clang already optimized it to this.

Apple M1

Result of 100 runs
base (...kfish.master) = 1307276 +/- 10930
test (./stockfish ) = 1320940 +/- 11279
diff = +13664 +/- 2223

speedup = +0.0105
P(speedup > 0) = 1.0000

closes https://github.com/official-stockfish/Stockfish/pull/6855

No functional change
2026-05-29 19:05:08 +02:00
TonyandJoost VandeVondele 313ea4ab04 Update main network to nn-71d6d32cb962.nnue
Using a net trained with Quantization Aware Training (QAT)

See:
- https://github.com/official-stockfish/nnue-pytorch/pull/477
- https://github.com/vondele/nettest/pull/346

CHANGES: To allow QAT some small changes had to be made.
- Quant scheme now only uses perfect power of two scales for anything but the very last output.
- Final output conversion happens in int64 to completely eliminate the chance of overflows.

REFACTORING: Some small refactoring was done to improve maintainability.
- Different weight scales are supported for different layers 64 or 128 instead of only 64).
- Changed order of weight segments in ft weights to simplify serialization.

Passed STC
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 23904 W: 6271 L: 5970 D: 11663
Ptnml(0-2): 87, 2742, 5995, 3039, 89
https://tests.stockfishchess.org/tests/view/6a0f6911818cacc1db0abdea

Passed LTC
LLR: 3.01 (-2.94,2.94) <0.50,2.50>
Total: 82950 W: 21276 L: 20857 D: 40817
Ptnml(0-2): 50, 8976, 23015, 9373, 61
https://tests.stockfishchess.org/tests/view/6a0fe40d818cacc1db0abe88

closes https://github.com/official-stockfish/Stockfish/pull/6856

Bench: 3003571

Co-authored-by: Joost VandeVondele <Joost.VandeVondele@gmail.com>
2026-05-27 07:06:17 +02:00
anematodeandJoost VandeVondele 564f9f9c85 Fix sloppy L3 cache detection code
Fixes https://github.com/official-stockfish/Stockfish/issues/6857

Many thanks to Andrew for the repro

closes https://github.com/official-stockfish/Stockfish/pull/6858

No functional change
2026-05-27 07:05:56 +02:00
anematodeandJoost VandeVondele 77a8f6ccf3 HQ attacks for AVX2
passed STC (https://tests.stockfishchess.org/tests/view/6a1157dc818cacc1db0ac172):

LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 29792 W: 7759 L: 7465 D: 14568
Ptnml(0-2): 75, 3206, 8033, 3514, 68

Also passed STC after cleanups https://tests.stockfishchess.org/tests/view/6a121beb818cacc1db0ac35e

vondele's local test:

Result of 100 runs
base (./stockfish.master       ) =    1136025  +/- 2816
test (./stockfish.patch2       ) =    1171370  +/- 2848
diff                             =     +35346  +/- 3275

speedup        = +0.0311
P(speedup > 0) =  1.0000

Basically we just do hyperbola quintessence in parallel. AVX2 doesn't have efficient bit reversal so we only do it for file and bishop attacks, then do rank attacks separately with a lookup table. This LUT is much smaller which is why this seems to be faster than standard magics.

closes https://github.com/official-stockfish/Stockfish/pull/6845

No functional change
2026-05-25 11:20:24 +02:00
Joost VandeVondele 41f3557db9 Further binary releases fixes
* .tar -> .tar.gz: save a few more MB in distribution.
* drop android armv7 without NEON support: there should be almost no devices around that do not support this.

closes https://github.com/official-stockfish/Stockfish/pull/6850

No functional change
2026-05-25 11:20:01 +02:00
anematodeandJoost VandeVondele 05f48577e1 Fix partial MADV_POPULATE_WRITE
Potentially fixes https://github.com/official-stockfish/Stockfish/issues/6846

I'm not a Linux guru but here's my understanding of what's going on, from looking at strace logs.

If `/dev/shm` is undersized or too full, `MADV_POPULATE_WRITE` can partially fault in the pages and then return an error. The crash then happens in the second process, on the first read to `header_ptr_->initialized`. There's a TOCTOU between when the region is attachable (the second process can see it in `/dev/shm`) and when we can actually safely read from it. The SIGBUS happens because `/dev/shm` is so full from partially faulted pages that we can't even fault in a single additional page.

This didn't happen before because a failed `posix_fallocate` *rolls back any faulted pages*. Then the second process faults in one page (which succeeds), reads a `0` from `header_ptr_->initialized`, declares the region invalid, and continues to try (and fail) to set up its own region.

There's still a problem here, methinks, if the process gets killed/interrupted between the failed `madvise` and the `ftruncate`. I think a more robust approach is to create the file anonymously first (to prevent other processes from attaching to it), then `linkat` to publish it atomically, but that would be more implementation work. (Could create a follow-up issue for that as an nice first issue for new contribs?)

closes https://github.com/official-stockfish/Stockfish/pull/6852

No functional change
2026-05-25 11:19:40 +02:00
Robert NurnbergandJoost VandeVondele 5b068c96d2 Play the optimal DTM move if we can
These days Stockfish at TCEC often plays trivial endgames badly, failing to find a mate score as well. This patch helps Stockfish in those situations, by forcing it to play an optimal DTM move if we can deduce it from the DTZ tables. That is, when the only zeroing move is the checkmate itself.

Hence this PR checks if there are no pawns left to push and if there are no captures or sacrifices possible that would maintain a win. Example positions where this applies are KQvK, KRvK, KBBvK and KNBvK.

The change will also lead to optimal Syzygy PV extensions once the PV enters the "known DTM" territory.

The patch should help mitigate issue #6742.

closes https://github.com/official-stockfish/Stockfish/pull/6843

No functional change
2026-05-24 14:20:31 +02:00
disservinandJoost VandeVondele 50cf901c33 drop old macos binaries
It is unlikely that people really depend on these binaries, as the before bmi2 macos systems are rather old.

closes https://github.com/official-stockfish/Stockfish/pull/6836

No functional change
2026-05-24 14:19:59 +02:00
DisservinandJoost VandeVondele 1ab9a11afe fix avx512icl variable shadows warning
```
nnue/layers/../nnz_helper.h:82:21: warning: declaration shadows a field of 'NNZInfo<Dimensions>' [-Wshadow]
   82 |             __m512i nnz = _mm512_maskz_compress_epi16(nnzMask, indices);
      |                     ^
nnue/layers/../nnz_helper.h:35:14: note: previous declaration is here
   35 |     uint16_t nnz[Dimensions / 4];
      |              ^
```

closes https://github.com/official-stockfish/Stockfish/pull/6834

No functional change
2026-05-24 14:19:24 +02:00
DisservinandJoost VandeVondele 24d6398490 Move Attacks out of Bitboard File
Over the past few months our attacks and bitboard logic got a bit more involved and target specific, so I think it makes sense to split this up a bit more into separate files.

While it's not in an ideal state I think it's first step and can be improved later, `pawn_attacks_bb` could also be moved into the attacks.h but this was more of an oversight from my side.

closes https://github.com/official-stockfish/Stockfish/pull/6833

No functional change
2026-05-24 14:18:45 +02:00
anematodeandJoost VandeVondele be9df38f27 Add 1GB page support on x86 Linux
1GB pages for the TT give a small performance bump (`./stockfish speedtest 256 32000`):

master:
Nodes/second               : 230514479
patch
Nodes/second               : 237997728

With a large enough TT there is still dTLB thrashing with 2MB pages. 1GB pages reduces that and makes page table walks a bit faster (they're shorter, and there's fewer PTEs, which fits better in cache). `madvise` won't give you huge pages, only large pages, so we use mmap here.

We guard the huge page attempt to x86 + at least 8 huge pages per NUMA node, to avoid memory oversubscription when for example running with a 1.1 GB hash. Additionally, we change the TT clearing slightly so that the pages are better distributed across NUMA nodes.

Data from Torom:

master:
Nodes/second               : 43524292
patch:
Nodes/second               : 44080034

Data on an 8-thread Emerald Rapids VM:

./stockfish speedtest 8 65536

Baseline speedtest (2MB huge pages):

Nodes/second : 7366232
Nodes/second : 7262794
Nodes/second : 7355431
Nodes/second : 7318777

Baseline speedtest (1GB huge pages):

Nodes/second : 7541027
Nodes/second : 7572720
Nodes/second : 7550268
Nodes/second : 7538925

closes https://github.com/official-stockfish/Stockfish/pull/6681

No functional change
2026-05-24 14:17:54 +02:00
Joost VandeVondele 6a477d5610 Unblock CI
this assert, introduced at https://github.com/official-stockfish/Stockfish/commit/319d61effdad40ac633425d6504a98f6d2ad0cd2 , can fail in a multi-threading setup.

Fixing this is tracked at https://github.com/official-stockfish/Stockfish/issues/6847

closes https://github.com/official-stockfish/Stockfish/pull/6848

No functional change
2026-05-24 14:15:58 +02:00
tobi437aandJoost VandeVondele 133731f331 Simplify away unused CorrHistType variants
After the introduction of UnifiedCorrectionHistory, the Pawn, Minor and NonPawn entries in CorrHistType are dead - only PieceTo and Continuation are referenced via CorrectionHistory<T>. Drop them, along with the now unreachable default template and the NonPawn specialization. Reduce the primary CorrHistTypedef template to a forward declaration so that any future stray use of an unsupported tag fails to compile.

closes https://github.com/official-stockfish/Stockfish/pull/6828

No functional change
2026-05-19 18:42:17 +02:00
FauziAkramandJoost VandeVondele 94984fe5cb Simplify Mated Conditions in Iterative Deepening
Follow-up to #6785

After completing an iteration, the engine verifies if a "mate in x" condition is met to stop searching. Currently, it checks is_mate() and is_mated() independently using two separate bulky logic branches. However, VALUE_MATE equals 32000, so both a mate (e.g., 31998) and a mated score (e.g., -31998) satisfy VALUE_MATE - std::abs(score) == 2. By utilizing is_mate_or_mated() and applying std::abs(), we mathematically combine these checks into a single concise expression.

Can someone please help run a matetrack with a --mate 100 limit for this?

closes https://github.com/official-stockfish/Stockfish/pull/6829

No functional change
2026-05-19 18:41:36 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele 78d8f09bc8 Revert f8aa78e0a7
The two links given in the PR description for #6826 are not for the same code changes. The code that was eventually committed passed LTC non-reg, but not STC non-reg. However, it was a meaningful code change and so would have needed gainer bounds.

See https://tests.stockfishchess.org/tests/view/69fa69c83a3c3e525bb2b645 and https://tests.stockfishchess.org/tests/view/6a00a2549392f0c317214085.

closes https://github.com/official-stockfish/Stockfish/pull/6831

Bench: 2259945
2026-05-19 18:41:00 +02:00
anematodeandJoost VandeVondele 90890c5f1f Loongarch opts
optimize loongarch64 inference

passed STC:
 LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 14208 W: 3792 L: 3510 D: 6906
Ptnml(0-2): 54, 1485, 3765, 1725, 75
https://tests.stockfishchess.org/tests/view/6a0a2a956524d21ee79b85a8

closes https://github.com/official-stockfish/Stockfish/pull/6832

No functional change
2026-05-19 18:39:58 +02:00
anematodeandJoost VandeVondele 24abab9f6c Universal builds for Android armv8 and compatibility with older compilers
- We add a new path for supporting universal builds without #embed . `xxd` causes the compiler to run out of memory, so instead we embed it as a string literal created at compile time.
    - This file is created in `net.sh`, and only if `--embed-dir` isn't supported + we are doing a universal build.
    - This was necessary because Android NDK seems to not support embed, idk why
- A couple other Makefile tweaks were necessary for the Android path.
- While we're at it, remove the non-universal arm64 windows and android binaries.

closes https://github.com/official-stockfish/Stockfish/pull/6827

No functional change
2026-05-19 18:39:06 +02:00
DubslowandJoost VandeVondele 319d61effd Penalize TTEs whose inexact value mismatches the current window
Credit shared with @xu-shawn, who has done much of the work to instrument the search and investigate search explosions and endgame paralysis: https://github.com/official-stockfish/Stockfish/issues/6742#issuecomment-4386174472

This patch is a natural result of that commentary. It can also be seen as a refinement of more generalized secondary aging.

Passed STC: https://tests.stockfishchess.org/tests/view/69fb18e8c91f9625ce325427
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 434784 W: 111417 L: 110528 D: 212839
Ptnml(0-2): 1089, 50675, 112949, 51616, 1063

Passed LTC: https://tests.stockfishchess.org/tests/view/6a0379588d9bd4cd7cd68edf
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 81018 W: 20558 L: 20154 D: 40306
Ptnml(0-2): 35, 8554, 22939, 8934, 47

VVLTC *reversion*: https://tests.stockfishchess.org/tests/view/6a0622538d9bd4cd7cd69340
Ptnml(0-2): 2, 5159, 18107, 5050, 0
LLR: -2.94 <0.50,2.50>
LLR: -2.47 <0.00,2.00>
LLR: -2.24 <-0.25,1.75>

closes https://github.com/official-stockfish/Stockfish/pull/6819

Bench: 2676789
2026-05-19 18:38:07 +02:00
anematodeandJoost VandeVondele b82f5b59f7 Port sqr_clipped_relu.h to NEON
Passed STC (https://tests.stockfishchess.org/tests/view/69fd70c39392f0c317213b99)

LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 315360 W: 80995 L: 80315 D: 154050
Ptnml(0-2): 573, 33687, 88501, 34325, 594

Data from vondele:

```
==== master ====
1 Nodes/second : 278894157
2 Nodes/second : 279192601
3 Nodes/second : 278279517
Average (over 3):  278788758
==== neonnnn ====
1 Nodes/second : 280614075
2 Nodes/second : 279971502
3 Nodes/second : 280613919
Average (over 3):  280399832
```

closes https://github.com/official-stockfish/Stockfish/pull/6820

No functional change
2026-05-19 18:37:09 +02:00
anematodeandJoost VandeVondele fff35786bf Use bitset representation for nnz and move computation into feature transformer
Passed avx2/bmi2 STC:
https://tests.stockfishchess.org/tests/view/69ff99eb9392f0c317213eda
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 20992 W: 5469 L: 5192 D: 10331
Ptnml(0-2): 38, 2197, 5751, 2470, 40

Passed avxvnni STC:
https://tests.stockfishchess.org/tests/view/6a00022b9392f0c317213f7c
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 47328 W: 12328 L: 12009 D: 22991
Ptnml(0-2): 112, 5148, 12847, 5423, 134

Passed NEON STC:
https://tests.stockfishchess.org/tests/view/69ff99d69392f0c317213ed8
LLR: 2.96 (-2.94,2.94) <0.00,2.00>
Total: 29600 W: 7664 L: 7376 D: 14560
Ptnml(0-2): 48, 3074, 8277, 3344, 57

Passed avx512icl non-regression:
https://tests.stockfishchess.org/tests/view/6a002b699392f0c317213f91
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 90112 W: 23130 L: 22975 D: 44007
Ptnml(0-2): 192, 9939, 24633, 10106, 186

Measurements from vondele (Neoverse V2, armv8-dotprod):

==== master ====
==== Bench: 2344696 ====
1 Nodes/second : 280724660
2 Nodes/second : 280647282
3 Nodes/second : 282055192
Average (over 3):  281142378
==== 50a44640a3 ====
==== Bench: 2344696 ====
1 Nodes/second : 284271937
2 Nodes/second : 285638071
3 Nodes/second : 284349426
Average (over 3):  284753144

The patch's benefit is non-uniform and is ~0 for avx512icl unfortunately -- although I think we should be able to find something there....

## Background/explanation

The idea here is to move the non-zero block computation back to the previous layer, and overlap the work better. Then, to avoid trying to emulate compress instructions on targets not supporting them (i.e., everything except for AVX512), we use a `pop_lsb` loop on a bitset enumerating the non-zero blocks, rather than first writing them out as indices.

An early AVX2 implementation worked on fishtest (https://tests.stockfishchess.org/tests/view/69c3410534b6988b1e472db4) and linrock demonstrated that it also worked for NEON (https://tests.stockfishchess.org/tests/view/69ce0dc73ddc0eccd617188f). Thanks to him for encouraging me to push this idea over the finish line.

To abstract over the particular NNZ representation (bitset or index list), we use `NNZInfo` and `NNZCursor`. The cursor is used to write to one or the other perspective of the NNZ list. I'd appreciate ideas on making the code cleaner/more readable as imo it's still a bit ugly.

## Fixing GCC 15 regression

Separately, vondele noticed a regression in ARM performance from GCC 15 caused by suboptimal codegen. See https://discord.com/channels/435943710472011776/813919248455827515/1502837886381461605 for more info, but the easiest fix that I could come up with was inserting a couple `asm` optimization barriers.

Single threaded data:
```
average: stockfish.master.13.3  1041619
average: stockfish.master.15.2   999214
average: stockfish.patched.15.2  1031369
```

There's definitely regressions elsewhere as well, which we shld chase down, but at least this should unblock the arm64 universal binary work.

closes https://github.com/official-stockfish/Stockfish/pull/6814

No functional change
2026-05-19 18:36:11 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele c67c1ec603 [bugfix] Guard is_decisive() to avoid assert triggers
This PR fixes an oversight in #6818. At least in multithreaded searches the TT scores can be corrupted, and accessing them is racy.

Passed non-reg STC:
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 61792 W: 15609 L: 15428 D: 30755
Ptnml(0-2): 134, 6534, 17373, 6727, 128
https://tests.stockfishchess.org/tests/view/6a0b15396524d21ee79b868b

We also increase the number of the threads in our instrumented CI tests to make these assert failures for buggy patches more likely in future.

closes https://github.com/official-stockfish/Stockfish/pull/6830

No functional change
2026-05-19 18:35:23 +02:00
FauziAkramandJoost VandeVondele f8aa78e0a7 Simplify ttMove reduction formula
Passed STC non-reg:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 110720 W: 28313 L: 28180 D: 54227
Ptnml(0-2): 230, 12259, 30279, 12332, 260
https://tests.stockfishchess.org/tests/view/69fa69c83a3c3e525bb2b645

Passed LTC non-reg:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 307728 W: 77955 L: 78027 D: 151746
Ptnml(0-2): 138, 33481, 86698, 33409, 138
https://tests.stockfishchess.org/tests/view/6a00a2549392f0c317214085

closes https://github.com/official-stockfish/Stockfish/pull/6826

Bench: 2559757
2026-05-17 20:51:07 +02:00
MyselfandJoost VandeVondele 3fcd374ad4 Simplify and refactor clipped_relu.h
remove AVX2 special casing

based on earlier https://github.com/official-stockfish/Stockfish/pull/6824 that passed non-regression STC:
LLR: 3.56 (-2.94,2.94) <-1.75,0.25>
Total: 49888 W: 12683 L: 12439 D: 24766
Ptnml(0-2): 104, 5170, 14153, 5412, 105
https://tests.stockfishchess.org/tests/view/6a0702c58d9bd4cd7cd69462

No change on non-AVX2 architectures.

closes https://github.com/official-stockfish/Stockfish/pull/6825

No functional change
2026-05-17 20:50:22 +02:00
anematodeJoost VandeVondelecoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
d6469b3980 arm64 universal binaries redux
Introduce the armv8-universal target. 🥴

The entry point is essentially the same as x86's, except we no longer have access to `__builtin_cpu_supports` so instead we need an OS-specific query for whether we support dotprod. The Makefile is modified to support both universal builds.

If in the future we add more ARM targets, such as SVE, we'll need to add qemu to the RUN_PREFIX in CI, because currently we assume (for PGO purposes) that the CI host supports all the used ARM instructions.

### clang/Windows

The painful part here is clang on Windows, which, until arm64 mingw is stabilized, is required for targeting arm64. This PR also gets it to work for x86. In the Makefile this setup corresponds to `use_lto_emit_asm=yes`.

In particular, `--defsym` and `--save-temps` are not supported by `lld-link`, and objcopy `--rename-section` doesn't work on COFF binaries because of how section names work there.
- `--defsym` is needed to define `main` for PGO purposes and assigns it to the namespaced, per-arch main function. Instead, we define `main` in `main.cpp` so that the compilation is successful, then delete it before the final link.
- Instead of `--save-temps` to get the LTO intermediate object, we pass `--lto-emit-asm` to the linker, which outputs `stockfish.exe.lto.s`.
- Finally, we have a small AWK script to find the `.ctors` section, neuter it, and put start/stop symbols around it with the same naming scheme as ELF (`__start_*_init`/`__stop_*_init`).

I'm lowk a Windows programming noob so if there's simpler ways of going about this, I'd appreciate a pointer. @PikaCat-OuO + Codex used an approach that involved going in and modifying the LLVM bitcode, but that felt more complicated to me.

closes https://github.com/official-stockfish/Stockfish/pull/6823

No functional change

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-05-17 20:48:54 +02:00
anematodeandJoost VandeVondele 91736f7b4f Compute correction_value before TT lookup
Passed single-threaded STC with 128MB hash
https://tests.stockfishchess.org/tests/view/6a04a6348d9bd4cd7cd69124
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 103808 W: 26400 L: 26007 D: 51401
Ptnml(0-2): 205, 11212, 28711, 11537, 239

Passed standard SMP STC
https://tests.stockfishchess.org/tests/view/6a054f528d9bd4cd7cd69225
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 80392 W: 20580 L: 20221 D: 39591
Ptnml(0-2): 79, 8872, 21942, 9217, 86

Entering `do_move`, `correction_value` and the TT lookup are always done together, but the TT lookup happens first. Swapping the order allows the execution of `correction_value` to overlap with the in-flight TT prefetch.

closes https://github.com/official-stockfish/Stockfish/pull/6822

No functional change
2026-05-17 20:48:12 +02:00
Shawn XuandJoost VandeVondele 94beadffb3 Reintroduce Secondary TT Aging
Restricted secondary aging in TT to evict stale high-depth entries that could not cutoff.

Passed Non-regression STC:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 189792 W: 48752 L: 48702 D: 92338
Ptnml(0-2): 428, 21147, 51711, 21167, 443
https://tests.stockfishchess.org/tests/view/69fae7b6c91f9625ce3253c8

Passed Non-regression LTC:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 234792 W: 59743 L: 59741 D: 115308
Ptnml(0-2): 98, 24714, 67772, 24712, 100
https://tests.stockfishchess.org/tests/view/69ff3aa39392f0c317213e31

See #6742 for information and test results
```
Using ./stockfish_fix1 on KRvK_1000pv.epd with --nodes 1000000 --mate 0
Engine ID:     Stockfish dev-20260505-1429d98e
Total FENs:    1000
Found mates:   620
Best mates:    159

Using ./stockfish_fix1 on KRvK_1000pv.epd with --nodes 10000000 --mate 0
Engine ID:     Stockfish dev-20260505-1429d98e
Total FENs:    1000
Found mates:   1000
Best mates:    709
```

Closes #6576
Closes #6742

closes https://github.com/official-stockfish/Stockfish/pull/6818

Bench: 2821162
2026-05-17 20:46:31 +02:00
FauziAkramandJoost VandeVondele 57f3a2bfbe Replace directory == "<internal>" with a simple "else"
Replace directory == "<internal>" with a simple "else" , since the previous condition was the opposite.

closes https://github.com/official-stockfish/Stockfish/pull/6817

No functional change
2026-05-17 20:45:39 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele 1cebdafc14 Help aborted searches remember mate scores
This patch improves mate finding results by protecting mate scores from being forgotten in a later iteration. This applies especially to aborted searches, but also to completed iterations in e.g. `go depth` scenarios. The only visible change to the user is the final UCI info line.

The case of TB wins/losses was excluded from this patch on purpose. The reason is that in some corner cases related to rounding in some DTZ Syzygy EGTBs, later searches may turn a TB loss into a draw, for example. See #5175 and the example
```
info depth 5 seldepth 5 multipv 1 score cp -20000 nodes 122 nps 8714 hashfull 0 tbhits 26 time 14 pv h1b1 a7b6 f6e5 b6b1 e5f4 c8c3 f4g5 d7h7 g5f4 b1f5 f4f5 c3e5 f5g6 e5f4 g6h7 f4g5 h7h8 b8c7 h8h7 c7d6 h7h8 d6e5 h8h7 e5f6 h7h8 g5g7
info depth 6 seldepth 9 multipv 1 score cp 0 nodes 255 nps 18214 hashfull 0 tbhits 26 time 14 pv h1h2 d7d6 f6g5 a7e7
```
from #5414.

Matetrack for patch:
```
Using ./stockfish on matetrack.epd with --nodes 1000000
Engine ID:     Stockfish dev-20260510-dbbc3ed1
Total FENs:    6554
Found mates:   3054
Best mates:    2224
```
This is slightly more/equal compared to master (3040 and 2224), see https://github.com/vondele/matetrack/blob/faf7cf3be95ab7dfdcde6e035de28a30887cd8a5/matetrack1000000.csv#L4368.

Passed STC non-reg:
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 108224 W: 27565 L: 27429 D: 53230
Ptnml(0-2): 212, 11755, 30064, 11847, 234
https://tests.stockfishchess.org/tests/view/6a00899e9392f0c31721406c

closes https://github.com/official-stockfish/Stockfish/pull/6816

No functional change
2026-05-17 20:44:44 +02:00
Wencey WangandJoost VandeVondele ffffe1bdb5 Add intrinsics for LSX and LASX
This adds actual intrinsics of SIMD on loongarch64, with considerable performance improvements.

Benchmark on `Loongson-3A6000-HV Not Specified CPU @ 2.5GHz`

Baseline GCC 14 (debian trixie):
```
===========================
Total time (ms) : 17816
Nodes searched  : 2336177
Nodes/second    : 131128
```

With this PR:

GCC 16 (debian sid):
```
./stockfish compiler
Stockfish dev-20260516-52e8d9ef by the Stockfish developers (see AUTHORS file)

Compiled by                : g++ (GNUC) 16.1.0 on Linux
Compilation architecture   : loongarch64-lasx
Compilation settings       : 64bit LASX LSX
Compiler __VERSION__ macro : 16.1.0
===========================
Total time (ms) : 3518
Nodes searched  : 2336177
Nodes/second    : 664063
```
```
./stockfish compiler
Stockfish dev-20260516-52e8d9ef by the Stockfish developers (see AUTHORS file)

Compiled by                : g++ (GNUC) 16.1.0 on Linux
Compilation architecture   : loongarch64-lsx
Compilation settings       : 64bit LSX
Compiler __VERSION__ macro : 16.1.0
===========================
Total time (ms) : 4944
Nodes searched  : 2336177
Nodes/second    : 472527
```

clang 22 (debian sid):
```
./stockfish compiler
Stockfish dev-20260516-52e8d9ef by the Stockfish developers (see AUTHORS file)

Compiled by                : clang++ 22.1.5 on Linux
Compilation architecture   : loongarch64-lasx
Compilation settings       : 64bit LASX LSX
Compiler __VERSION__ macro : Debian Clang 22.1.5 (1)
===========================
Total time (ms) : 3401
Nodes searched  : 2336177
Nodes/second    : 686908
```
```
./stockfish compiler
Stockfish dev-20260516-52e8d9ef by the Stockfish developers (see AUTHORS file)

Compiled by                : clang++ 22.1.5 on Linux
Compilation architecture   : loongarch64-lsx
Compilation settings       : 64bit LSX
Compiler __VERSION__ macro : Debian Clang 22.1.5 (1)
===========================
Total time (ms) : 5501
Nodes searched  : 2336177
Nodes/second    : 424682
```

GCC 14 (debian trixie):
```
./stockfish compiler
Stockfish dev-20260516-52e8d9ef by the Stockfish developers (see AUTHORS file)

Compiled by                : g++ (GNUC) 14.2.0 on Linux
Compilation architecture   : loongarch64-lasx
Compilation settings       : 64bit LASX LSX
Compiler __VERSION__ macro : 14.2.0
===========================
Total time (ms) : 3559
Nodes searched  : 2336177
Nodes/second    : 656413
```
```
./stockfish compiler
Stockfish dev-20260516-52e8d9ef by the Stockfish developers (see AUTHORS file)

Compiled by                : g++ (GNUC) 14.2.0 on Linux
Compilation architecture   : loongarch64-lsx
Compilation settings       : 64bit LSX
Compiler __VERSION__ macro : 14.2.0
===========================
Total time (ms) : 5212
Nodes searched  : 2336177
Nodes/second    : 448230
```

clang 19 (debian trixie):
```
./stockfish compiler
Stockfish dev-20260516-52e8d9ef by the Stockfish developers (see AUTHORS file)

Compiled by                : clang++ 19.1.7 on Linux
Compilation architecture   : loongarch64-lasx
Compilation settings       : 64bit LASX LSX
Compiler __VERSION__ macro : Debian Clang 19.1.7 (3+b1)
===========================
Total time (ms) : 4830
Nodes searched  : 2336177
Nodes/second    : 483680
```
```
./stockfish compiler
Stockfish dev-20260516-52e8d9ef by the Stockfish developers (see AUTHORS file)

Compiled by                : clang++ 19.1.7 on Linux
Compilation architecture   : loongarch64-lsx
Compilation settings       : 64bit LSX
Compiler __VERSION__ macro : Debian Clang 19.1.7 (3+b1)
===========================
Total time (ms) : 5568
Nodes searched  : 2336177
Nodes/second    : 419572
```

closes https://github.com/official-stockfish/Stockfish/pull/6815

No functional change
2026-05-17 20:42:36 +02:00
DisservinandJoost VandeVondele 0366d092a8 Shorten Prerelease Binary Window
Only delete the prerelease once all assets have been built and only need upload.

Also fix a link in prerelease notes to the official releases.

closes https://github.com/official-stockfish/Stockfish/pull/6812

No functional change
2026-05-17 20:41:36 +02:00
dd321af5df Updated main network to nn-83a0d6daf7e5.nnue
Passed STC:
https://tests.stockfishchess.org/tests/view/69f902413a3c3e525bb2b465
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 27264 W: 7199 L: 6895 D: 13170
Ptnml(0-2): 80, 3139, 6903, 3417, 93

Passed LTC:
https://tests.stockfishchess.org/tests/view/69f98b103a3c3e525bb2b4f7
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 32352 W: 8509 L: 8200 D: 15643
Ptnml(0-2): 10, 3439, 8979, 3728, 20

Nettest Run
https://gitlab.com/cscs-ci/ci-testing/webhook-ci/mirrors/5137461961076608/2926829081096545/-/pipelines/2496280249

Nettest PR:
https://github.com/vondele/nettest/pull/329

Makes quite a few changes to the trainer. In particular recipe does not use a lambda (game score + outcome blending parameter) schedule for the most part.

Accidentally dropped loss weighing parameters. Unclear how big the effect of that is

closes https://github.com/official-stockfish/Stockfish/pull/6802

Bench: 2336177

Co-authored-by: Tony <Tony@secret.com>
2026-05-10 11:50:01 +02:00
FauziAkramandJoost VandeVondele a12dc6cc1f VVLTC parameter's tune
Passed 1st VVLTC test with STC-bounds:
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 78172 W: 20207 L: 19894 D: 38071
Ptnml(0-2): 2, 6871, 25032, 7174, 7
https://tests.stockfishchess.org/tests/view/69fa262f3a3c3e525bb2b5e7

Passed 2nd VVLTC test with LTC-bounds:
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 44360 W: 11624 L: 11322 D: 21414
Ptnml(0-2): 0, 3931, 14022, 4221, 6
https://tests.stockfishchess.org/tests/view/69f8b30d3a3c3e525bb2b3d9

STC estimate test:
Elo: -1.50 ± 1.5 (95%) LOS: 2.3%
Total: 55742 W: 14169 L: 14409 D: 27164
Ptnml(0-2): 143, 6697, 14452, 6415, 164
nElo: -2.95 ± 2.9 (95%) PairsRatio: 0.96
https://tests.stockfishchess.org/tests/view/69fef06c9392f0c317213dca

closes https://github.com/official-stockfish/Stockfish/pull/6807

Bench: 2619968
2026-05-10 11:49:00 +02:00
Jörg OsterandJoost VandeVondele 944bee7117 Fix calculation of searched nodes (effort)
by moving it back to its original place right before making the move. See the original patch https://github.com/official-stockfish/Stockfish/commit/bf2c7306ac7f83200ba4d894867e3c0c78c0802c

Only functional with active time-management.

closes https://github.com/official-stockfish/Stockfish/pull/6810

No functional change
2026-05-10 11:47:50 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele bcd5b6ccb3 [CI] Add a mutiPV run for matetrack
Thanks to recent commits, we can now also trust mate(d) scores from multiPV analysis for the best move, with complete PV etc. Locally I have also tested master extensively with the help of the matetrack script in multiPV analysis, with no issues reported. So I believe we can add this to our CI.

closes https://github.com/official-stockfish/Stockfish/pull/6809

No functional change
2026-05-10 11:47:09 +02:00
anematodeandJoost VandeVondele 6a50807e01 On Linux, allow shared-memory network weights to be backed by huge pages
This does require the user to have set `/sys/kernel/mm/transparent_hugepage/shmem_enabled` to something other than `never` (unfortunately the default, at least on Ubuntu).

```
Result of  50 runs
==================
base (...kfish.master) =    1814244  +/- 4396
test (./stockfish    ) =    1823756  +/- 3448
diff                   =      +9512  +/- 5496

speedup         = +0.0052
P(speedup > 0) =  0.9996
```

```
 Performance counter stats for './stockfish bench':

           237,561      dTLB-misses
     5,783,003,231      cycles

 Performance counter stats for './bins/stockfish.master bench':

         2,219,832      dTLB-misses
     5,932,779,255      cycles
```

closes https://github.com/official-stockfish/Stockfish/pull/6805

No functional change
2026-05-10 11:46:31 +02:00
disservinandJoost VandeVondele c2500b1165 Add Prerelease Notes
closes https://github.com/official-stockfish/Stockfish/pull/6803

No functional change
2026-05-10 11:45:15 +02:00
Robert NurnbergandJoost VandeVondele ea48d38283 remove simple eval
No longer needed after the removal of smallnet.

Also remove declaration of `use_smallnet()`.

closes https://github.com/official-stockfish/Stockfish/pull/6799

No functional change
2026-05-10 11:44:38 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele fcbd160d26 [refactor] Add helper functions and simplify thread selection
I found these changes useful and natural when working on a patch to improve mate reporting. I can revert some of the changes if maintainers would prefer that. E.g. I am not sure whether `unset_bound_flags()` is worth defining.

closes https://github.com/official-stockfish/Stockfish/pull/6785

No functional change
2026-05-10 11:43:53 +02:00
Joost VandeVondele 6127bf711e [CI] Upgrade SDE action
fix Node.js version dependency, update SDE.

This upgrades to 9.58, upgrade to 10.8 failed with:
Invalid file name and base dir combination: Pin root DLL: pincrt4.dll

Also adjusts Makefile to show PGO bench output on failure

closes https://github.com/official-stockfish/Stockfish/pull/6781

No functional change
2026-05-10 11:43:17 +02:00
Tony WangandJoost VandeVondele 5095cd16c9 Update main network to nn-fcf986aea78a.nnue
Passed STC:
https://tests.stockfishchess.org/tests/view/69ef80071e5788938e86a302
LLR: 2.96 (-2.94,2.94) <0.00,2.00>
Total: 32000 W: 8549 L: 8231 D: 15220
Ptnml(0-2): 124, 3717, 8024, 3987, 148

Passed LTC:
https://tests.stockfishchess.org/tests/view/69f046ba1e5788938e86a42f
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 149646 W: 38739 L: 38186 D: 72721
Ptnml(0-2): 119, 16353, 41326, 16906, 119

New trainer:
official-stockfish/nnue-pytorch#430

Recipe:
TonyCongqianWang/nettest@104ed33

closes https://github.com/official-stockfish/Stockfish/pull/6790

Bench: 2344696
2026-05-04 08:42:55 +02:00
Timothy HerchenandJoost VandeVondele 1554a2ca0b Precompute moves and use magics index as compress mask on AVX512ICL
Passed STC
https://tests.stockfishchess.org/tests/view/69f6df65b64b50e29dbed427
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 112224 W: 29195 L: 28782 D: 54247
Ptnml(0-2): 312, 12345, 30381, 12766, 308

Local speedup:
```
Result of 100 runs
base (...kfish.master) =    2050387  +/- 5497
test (./stockfish    ) =    2069598  +/- 4426
diff                   =     +19211  +/- 7468
speedup         = +0.0094
P(speedup > 0) =  1.0000
```

closes https://github.com/official-stockfish/Stockfish/pull/6797

No functional change
2026-05-04 08:33:01 +02:00
anematodeandJoost VandeVondele ab8f901d25 Remove small net
Failed VVLTC non-regression
https://tests.stockfishchess.org/tests/view/69d562b84088e069540a2288
LLR: -2.96 (-2.94,2.94) <-1.75,0.25>
Total: 386998 W: 99181 L: 99760 D: 188057
Ptnml(0-2): 35, 35792, 122429, 35203, 40

Failed STC non-regression
https://tests.stockfishchess.org/tests/view/69f3c6601e5788938e86a99e
LLR: -2.93 (-2.94,2.94) <-1.75,0.25>
Total: 33696 W: 8492 L: 8795 D: 16409
Ptnml(0-2): 124, 4209, 8504, 3868, 143

Many thanks to Dubslow, Torom, ces42, Shawn, vondele, Disservin and others for discussion.

## Summary

The venerable small net has been around for quite some time now, and while the big net architecture has substantially advanced with TI, the small net has stayed with plain HalfKA. It therefore presents a few burdens: multiple net architectures to maintain, multiple nets to train, and a whole lot of templates to deal with the variable L1 size.

Locally I measure a slowdown of -2.5% in NPS with this branch – and it's probably more on non-AVX512 architectures – but a pure slowdown of that magnitude would lead to more dramatic losses (even at VVLTC) than exhibited in the above tests, suggesting that the small net's lower eval quality is deleterious.

Bonus: Shawn found this interesting PGN among the VVLTC games: https://lichess.org/study/hvo8jflc/OeTOityv  `master` seems to misevaluate the fortress because all positions go to small net (the material difference is larger than the threshold).

closes https://github.com/official-stockfish/Stockfish/pull/6796

Bench: 2877007
2026-05-04 08:32:42 +02:00
anematodeandJoost VandeVondele dc1686345c Hyperbola quintessence for ARM
Passed STC

https://tests.stockfishchess.org/tests/view/69f44c1e1e5788938e86aa2a
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 27680 W: 7340 L: 7053 D: 13287
Ptnml(0-2): 72, 2862, 7683, 3153, 70

x86 is unaffected

Data from Joost's machine (Neoverse V2):

```
==== master ====
==== Bench: 2723949 ====
1 Nodes/second : 276819933
2 Nodes/second : 273971948
3 Nodes/second : 275567995
Average (over 3):  275453292
==== 5b54755db23a3de29407b805b85e7bbdef28df49 ====
==== Bench: 2723949 ====
1 Nodes/second : 282861542
2 Nodes/second : 279085277
3 Nodes/second : 279612223
Average (over 3):  280519680 (+1.8%)
```

Data from my M1 macbook

```
Result of 100 runs
==================
base (./stockfish    ) =    1474702  +/- 6169
test (...fish.rbit-pr) =    1500236  +/- 6690
diff                   =     +25533  +/- 4011

speedup        = +0.0173
P(speedup > 0) =  1.0000
```

Hyperbola quintessence works nicely on ARM because it has an efficient bit reversal instruction `rbit`. This lets us avoid the large magic bitboards lookup table. Codegen looks nice (this is for rook attacks I think):

```
0000000100001ecc <__ZN9Stockfish10attacks_bbILNS_9PieceTypeE3EEEyNS_6SquareEy>:
100001ecc: 2a0003e8     mov w8, w0
100001ed0: f002cb09     adrp x9, 0x105964000 <dyld_stub_binder+0x105964000>
100001ed4: 91200129     add x9, x9, #0x800
100001ed8: 8b081928     add x8, x9, x8, lsl #6
100001edc: a9402909     ldp x9, x10, [x8]
100001ee0: 8a01012b     and x11, x9, x1
100001ee4: dac0016c     rbit x12, x11
100001ee8: a941210d     ldp x13, x8, [x8, #0x10]
100001eec: cb08018c     sub x12, x12, x8
100001ef0: dac0018c     rbit x12, x12
100001ef4: cb0d016b     sub x11, x11, x13
100001ef8: ca0b018b     eor x11, x12, x11
100001efc: 8a090169     and x9, x11, x9
100001f00: 8a01014b     and x11, x10, x1
100001f04: dac0016c     rbit x12, x11
100001f08: cb080188     sub x8, x12, x8
100001f0c: dac00108     rbit x8, x8
100001f10: cb0d016b     sub x11, x11, x13
100001f14: ca0b0108     eor x8, x8, x11
100001f18: 8a0a0108     and x8, x8, x10
100001f1c: 8b090100     add x0, x8, x9
100001f20: d65f03c0     ret
```

closes https://github.com/official-stockfish/Stockfish/pull/6795

No functional change
2026-05-04 08:32:23 +02:00
fe1d2b456c Update CI Binaries
- split into multiple jobs
- remove binaries which are already covered by the universal binary

superseeds https://github.com/official-stockfish/Stockfish/pull/6779

closes https://github.com/official-stockfish/Stockfish/pull/6794

No functional change

Co-authored-by: Disservin <disservin.social@gmail.com>
2026-05-04 08:32:03 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele 7e754e17dd Simplify away completedDepth
Recent changes to this part of the code mean that the variable `completedDepth` is no longer neeeded. The only *functional* part of this patch is the effect on the thread voting formula. Basically, compared to master this patch raises the depth weight in the formula uniformly by 1. For very, very low depths this may have a tiny effect on thread selection compared to master. But in general we expect this patch to have no effect on game play at STC and LTC.

Passed STC SMP non-reg:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 284808 W: 72944 L: 72994 D: 138870
Ptnml(0-2): 351, 32192, 77370, 32138, 353
https://tests.stockfishchess.org/tests/view/69eddd011e5788938e86a075

closes https://github.com/official-stockfish/Stockfish/pull/6793

No functional change
2026-05-04 08:31:44 +02:00
FauziAkramandJoost VandeVondele 4055a9fb27 Remove the limit from the formula
Passed STC:
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 364768 W: 94115 L: 94250 D: 176403
Ptnml(0-2): 1101, 40158, 99989, 40047, 1089
https://tests.stockfishchess.org/tests/view/69e7ca530e9667dd5a7655a7

Passed LTC:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 146382 W: 37335 L: 37247 D: 71800
Ptnml(0-2): 64, 14682, 43630, 14732, 83
https://tests.stockfishchess.org/tests/view/69eeaf551e5788938e86a1e6

Passed VLTC:
LLR: 3.00 (-2.94,2.94) <-1.75,0.25>
Total: 48308 W: 12350 L: 12172 D: 23786
Ptnml(0-2): 4, 4303, 15362, 4481, 4
https://tests.stockfishchess.org/tests/view/69f25adb1e5788938e86a794

closes https://github.com/official-stockfish/Stockfish/pull/6787

No functional change
2026-05-04 08:31:25 +02:00
3e3e976fdf [bugfix] Respect bound flags for searched moves in multiPV analysis
Master in `pv()` incorrectly assumes that any root move with `i != pvIdx` has an exact score. This can lead to the suppression of bound flags for (a) root moves that have been partially searched in an iteration but do not end up to be best move, or (b) when an aborted search for `pvIdx > 0` leads to a new best move. This latter case is the cause for issue #6783.

Fixes #6783.

The bug in its incarnation (a) was independently discovered by @ciekce a couple of weeks ago, and we have jointly discussed a patch that fixes it. He is therefore a co-author of the bugfix part of this patch.

While at it, we also fix three minor oversights in the PV roll-back for aborted searches with a loss score:
* We only roll back the PV if the aborted search would report an exact score.
* Master inadvertently may have reported a bound for the rolled-back score+PV.
* We ensure a final UCI info line with the correct search depth and searched nodes statistics etc.

closes https://github.com/official-stockfish/Stockfish/pull/6784

No functional change

Co-Authored-By: Ciekce <44617491+Ciekce@users.noreply.github.com>
2026-05-04 08:31:06 +02:00
FauziAkramandJoost VandeVondele 2be0b2cdb4 Change ttmove reduction logic, and values
Passed STC:
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 38112 W: 10024 L: 9708 D: 18380
Ptnml(0-2): 104, 4365, 9830, 4625, 132
https://tests.stockfishchess.org/tests/view/69dd25a33ca80bdf151d4b33

Passed LTC:
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 256926 W: 65744 L: 64977 D: 126205
Ptnml(0-2): 126, 27919, 71612, 28674, 132
https://tests.stockfishchess.org/tests/view/69de48645c67bee7d241ff4e

Passed VLTC non-reg:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 174386 W: 44640 L: 44578 D: 85168
Ptnml(0-2): 26, 17847, 51384, 17911, 25
https://tests.stockfishchess.org/tests/view/69e767370e9667dd5a76551b

closes https://github.com/official-stockfish/Stockfish/pull/6782

Bench: 2788824
2026-05-04 08:30:47 +02:00
FauziAkramandJoost VandeVondele 7bd32a51f5 Replacing the futility margin static value with a continuous transition
Smooth the futility margin by interpolating the multiplier based on depth, replacing the previous static value with a continuous transition between depth 1 and 10.

Passed STC:
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 208448 W: 53891 L: 53324 D: 101233
Ptnml(0-2): 629, 24426, 53574, 24939, 656
https://tests.stockfishchess.org/tests/view/69e232206f344a19176002f3

Passed LTC:
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 208938 W: 53432 L: 52765 D: 102741
Ptnml(0-2): 105, 22531, 58550, 23158, 125
https://tests.stockfishchess.org/tests/view/69e8b5d00e9667dd5a7656c6

closes https://github.com/official-stockfish/Stockfish/pull/6780

Bench: 2661424
2026-05-04 08:30:28 +02:00
Timothy HerchenandJoost VandeVondele 04ce2c04d7 Faster movepick sorting on AVX512ICL
The core idea here is to greatly speed up the backwards insertion loop of the insertion sort. We can store up to 16 sorted moves and their values/scores in vectors. We obtain all moves with a smaller score (i.e., sorted later than the move-to-insert) as a mask, giving us something like `1111111111000000`. If we subtract one this gives us `1111111110111111` which can be can be used as an expand mask, to insert the new move.

Passed STC:
https://tests.stockfishchess.org/tests/view/69e9c9190e9667dd5a765853
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 45024 W: 11867 L: 11550 D: 21607
Ptnml(0-2): 101, 4938, 12142, 5205, 126

Local test:
```
Result of  50 runs
==================
base (...kfish.master) =    2026071  +/- 5124
test (./stockfish    ) =    2083770  +/- 5964
diff                   =     +57699  +/- 8535

speedup         = +0.0285
P(speedup > 0) =  1.0000
```

closes https://github.com/official-stockfish/Stockfish/pull/6768

No functional change
2026-05-04 08:30:09 +02:00
anematodeandJoost VandeVondele 1a882efc7f Simplify out double_inc_update
Passed STC
https://tests.stockfishchess.org/tests/view/69e82e200e9667dd5a765631
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 115424 W: 29950 L: 29822 D: 55652
Ptnml(0-2): 318, 12712, 31544, 12800, 338

Passed AVX2-only STC
https://tests.stockfishchess.org/tests/view/69e953080e9667dd5a7657c0
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 134720 W: 34773 L: 34665 D: 65282
Ptnml(0-2): 344, 14958, 36694, 14974, 390

Now that our L1 size is much smaller, the overhead of tracking diffs for
`double_inc_update` is a larger share of the cost.

Interestingly ces42 measured a -0.9% slowdown locally (avxvnni build).
Meanwhile Torom and I find this branch to be 2% faster. I'm not sure exactly
what's going on but more tests may be in order

closes https://github.com/official-stockfish/Stockfish/pull/6776

No functional change
2026-04-26 09:43:42 +02:00
Ryan Hirsch rhirsch123andJoost VandeVondele 0e78190b72 Speed up find_nnz on neon (Refactored)
Passed STC:
LLR: 2.97 (-2.94,2.94) <0.00,2.00>
Total: 349568 W: 90131 L: 89393 D: 170044
Ptnml(0-2): 725, 37712, 97280, 38234, 833
https://tests.stockfishchess.org/tests/view/69e470510e9667dd5a765046

This is a refactor of https://github.com/official-stockfish/Stockfish/pull/6766 so we don't have to include nnue_architecture.h
It also includes the fix in https://github.com/official-stockfish/Stockfish/pull/6769 which was needed to make this work.
mstembera made rhirsch123 the author.  credits also to anematode.

[Edit] Also fixes a bug where _mm512_packus_epi32 was being used instead of the correct _mm512_packs_epi32 for the IceLake version.

closes https://github.com/official-stockfish/Stockfish/pull/6774

No functional change
2026-04-26 09:41:09 +02:00
ppigazziniandJoost VandeVondele b35a064eae ci: cancel stale workflow runs per PR or ref
Add workflow-level concurrency to the direct GitHub Actions entrypoints so a
newer run for the same pull request or repository ref cancels the older
in-progress run.

Keep the reusable workflow graph, release guards, triggers, and permissions
unchanged while documenting the concurrency policy and the official GitHub
references used for the change.

closes https://github.com/official-stockfish/Stockfish/pull/6773

No functional change.
2026-04-26 09:39:38 +02:00
mstemberaandJoost VandeVondele 692d90bd74 Simplify FullThreats::append_active_indices()
Also add an else to FullThreats::append_changed_indices() because dp2removed
can't be equal to both from and to simultaneously.

passed STC
https://tests.stockfishchess.org/tests/view/69e71a490e9667dd5a76549f
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 210688 W: 54362 L: 54335 D: 101991
Ptnml(0-2): 613, 23246, 57593, 23285, 607

Thanks to ces42 for an improvement suggestion.

closes https://github.com/official-stockfish/Stockfish/pull/6770

No functional change
2026-04-26 09:37:18 +02:00
FauziAkramandJoost VandeVondele c787509663 Simplify time management reduction logic
Simplify the time management reduction logic by replacing the previous sigmoid
function with the linear interpolate function introduced in #6729 , resulting
in a cleaner and more efficient calculation of move stability that avoids
unnecessary exponential operations.

Passed STC non-reg:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 161856 W: 41933 L: 41852 D: 78071
Ptnml(0-2): 418, 18137, 43792, 18108, 473
https://tests.stockfishchess.org/tests/view/69e117a46f344a1917600148

Passed LTC non-reg:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 108726 W: 27819 L: 27692 D: 53215
Ptnml(0-2): 59, 11084, 31942, 11227, 51
https://tests.stockfishchess.org/tests/view/69e563c00e9667dd5a7652ac

This is a follow-up for #6091  #6729

closes https://github.com/official-stockfish/Stockfish/pull/6765

No functional change
2026-04-26 09:35:37 +02:00
mstemberaandJoost VandeVondele 767dcfe48d Fix Universal build on Windows
TRACKED_TOP previously used a shell pipeline (xargs, awk, sort) to extract
unique top-level directory/file names from SRCS and HEADERS. When invoking make
from cmd.exe on Windows, $(shell ...) uses cmd.exe rather than sh.exe, so the
pipeline fails because cmd.exe does not support Unix pipe syntax. Other MSYS2
tools (e.g. the compiler) work fine because they are invoked directly as
commands, not through a shell pipeline. This caused TRACKED_TOP to be empty, so
ln -s never ran and the per-arch temp_builds subdirectories were left without a
Makefile symlink, breaking the universal build with: "No rule to make target
'universal-object-nopgo'". Fixed by replacing the shell pipeline with pure GNU
make built-in functions (sort, foreach, firstword, subst) which have no shell
or PATH dependencies and work identically on Windows, Linux and macOS.

closes https://github.com/official-stockfish/Stockfish/pull/6762

No functional change
2026-04-26 09:33:03 +02:00
anematodeandJoost VandeVondele 8f181af8fb Get universal binary working for clang on Linux
A few changes were necessary, mainly because clang LTO works a bit differently than GCC

Doesn't yet work on Windows

small Makefile cleanups

closes https://github.com/official-stockfish/Stockfish/pull/6764

No functional change
2026-04-26 09:31:30 +02:00
Robert Nurnberg @ elitebookandJoost VandeVondele 86853a60ce [bugfix] Send correct search depth in final UCI info line
For quite some time SF has reported a too low final search depth in rare cases
(in multi-threaded search if a non-main thread was selected). Unfortunately,
the recent refactoring in #6704 means that this now happens for almost every
stopped search.

This PR fixes this issue, which was first spotted and reported by joergoster,
by ensuring that `rootDepth` always holds the last depth for which a search was
started.

Fixes #6756.

closes https://github.com/official-stockfish/Stockfish/pull/6761

No functional change.
2026-04-26 09:28:54 +02:00
Dieter DobbelaereandJoost VandeVondele e68ca3c187 Rename Linux universal release package/binary
Rename Linux universal release package from
`stockfish-ubuntu-x86-64-universal.tar.gz`  to
`stockfish-linux-x86-64-universal.tar.gz` (and similar for the binary), as the
bundled binary should run on any Linux distro with glibc >= 2.35 (which is the
glibc version of the Ubuntu 22.04 build server).

Note that the Linux universal binary is built with static libstdc++ linkage, so
this adds no extra dependency.

This change will hopefully make it less confusing for users, and in line with
how we show it on stockfishchess.org (as Linux).

closes https://github.com/official-stockfish/Stockfish/pull/6760

No functional change
2026-04-26 09:27:08 +02:00
FauziAkramandJoost VandeVondele 38d887ac64 Reuse Promotion Type Natively
Piece promotion is generated, and then type_of(promotion) is used to retrieve
the underlying type. The class Move provides .promotion_type() natively,
meaning we can cache the type directly and avoid extracting it indirectly
afterward.  By extracting pt = m.promotion_type(); up front, we don't need to
call type_of(promotion) iteratively inside the assertions or conditions below.

closes https://github.com/official-stockfish/Stockfish/pull/6759

No functional change
2026-04-26 09:25:45 +02:00
Robert NurnbergandJoost VandeVondele 50e8ff1e23 multiPV: protect mated-ins and allow thread selection
This PR is a replacement for #6652 and takes into account the changes made to this part of the code since then.

In multiPV analysis master may suppress genuine proven mated-in scores, if the iteration for a secondary PV line is aborted. In very rare cases, it may also evict a proven mated-in score from the best PV line with a score from an incomplete iteration of a secondary PV line. This PR avoids both.

In addition, we allow best thread selection in multithreaded multiPV searches. This was originally disabled in #524.

Single PV searches are not affected by this patch at all.

Fixes #6642.

closes https://github.com/official-stockfish/Stockfish/pull/6728

No functional change.
2026-04-26 09:24:34 +02:00
anematodeandJoost VandeVondele e17725f445 Constexpr attacks (pext only)
LLR: 2.97 (-2.94,2.94) <0.00,2.00>
Total: 38400 W: 10057 L: 9749 D: 18594
Ptnml(0-2): 93, 4110, 10498, 4394, 105
https://tests.stockfishchess.org/tests/view/69d2073261a12cebe17edc04

compile time generation leads to sharing tables through the binary, resulting
in gain on fishtest, as well as reduced memory usage. The downside is slightly
longer compilation time.

Using pdep reduces table size 4x, and gains a little outside of fishtest as well.

closes https://github.com/official-stockfish/Stockfish/pull/6706

No functional change
2026-04-26 09:18:06 +02:00
FauziAkramandJoost VandeVondele ed651aab54 Replacing std::clamp with std::min
No functional change

closes https://github.com/official-stockfish/Stockfish/pull/6751

No functional change
2026-04-20 17:45:03 +02:00
FauziAkramandJoost VandeVondele 7faecd75ac Combine duplicated - tb.plies subtraction logic
closes https://github.com/official-stockfish/Stockfish/pull/6752

No functional change
2026-04-20 17:44:01 +02:00
DisservinandJoost VandeVondele 583e0c1c75 cleanup feature detection code
makes the main dispatch logic more easy to follow and possibly update

closes https://github.com/official-stockfish/Stockfish/pull/6753

No functional change
2026-04-20 17:42:47 +02:00
Dieter DobbelaereandJoost VandeVondele 9dd58542c9 Compress Linux universal release package
`stockfish-ubuntu-x86-64-universal.tar` goes from 94.8MiB to 67.4MiB (tar.gz)

Note that the CI code is written such that it easy to select other compression
schemes in the future (e.g. tar.xz) by simply changing the `archive_ext`
parameter.

closes https://github.com/official-stockfish/Stockfish/pull/6746

No functional change
2026-04-20 17:41:06 +02:00
anematodeandJoost VandeVondele 20a8d8d3c0 Remove git dependency from Makefile
To build the universal binaries, use already defined variables with sources and
headers, as well as Makefile and incbin explicitly.

closes https://github.com/official-stockfish/Stockfish/pull/6758

No functional change
2026-04-20 17:39:02 +02:00
ppigazziniandJoost VandeVondele 463f5a6ee1 ci: pin root workflow actions to immutable SHAs
Pin every external action used by the root GitHub workflow set to a full-length
commit SHA and keep the selected release tag as a same-line comment.

Update the root workflows to the latest stable tagged action versions found
during the audit while preserving existing release guards, permissions, and
reusable workflow wiring.

closes https://github.com/official-stockfish/Stockfish/pull/6754

No functional change.
2026-04-20 17:37:39 +02:00
Joost VandeVondele 4a6c7e77a5 [CI] Cleanup artifact content (part 2)
removes temp_builds directory before packaging, even more.

closes https://github.com/official-stockfish/Stockfish/pull/6750

No functional change
2026-04-19 13:51:09 +02:00
Joost VandeVondele edfd021257 [CI] Cleanup artifact content
removes temp_builds directory before packaging

closes https://github.com/official-stockfish/Stockfish/pull/6745

No functional change
2026-04-19 12:26:47 +02:00
anematodeandJoost VandeVondele 1e333b341c Add universal binaries to prerelease page
closes https://github.com/official-stockfish/Stockfish/pull/6744

No functional change
2026-04-19 11:09:24 +02:00
FauziAkramandJoost VandeVondele ce7848f7cc Remove the Redundant ply Variables in Benchmark Calculations
In setup_benchmark, the variables evaluating the current ply simply mirror the
exact iterative progress of the string arrays loop (since ply just increments
by 1 with every pass). We can delete the separate integer assignment and
execute it securely within the loop declaration.

closes https://github.com/official-stockfish/Stockfish/pull/6732

No functional change
2026-04-19 07:23:25 +02:00
FauziAkramandJoost VandeVondele c66acdac99 Restructure Futility pruning
to a simpler and cleaner version

Passed non-reg test:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 175648 W: 45156 L: 45091 D: 85401
Ptnml(0-2): 459, 19228, 48383, 19297, 457
https://tests.stockfishchess.org/tests/view/69d8d9263ca80bdf151d454a

closes https://github.com/official-stockfish/Stockfish/pull/6733

No functional change
2026-04-19 07:21:27 +02:00
Maxim MasiutinandJoost VandeVondele 40ac93b8f8 Fix MultiArray::at(); use operator[] on hot paths
Fix MultiArray::at() which was marked noexcept despite delegating to
std::array::at() that throws on out-of-range. Per review feedback, at() now
keeps standard bounds-checking semantics (only noexcept removed), and
operator[] gains an assert for debug-mode safety.

All eight correction history lookups in search.cpp (correction_value and
update_correction_history) are switched from .at(us) to [us].

Passed STC:
https://tests.stockfishchess.org/tests/view/69de7a735c67bee7d241ff92
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 77536 W: 20031 L: 19860 D: 37645
Ptnml(0-2): 221, 8411, 21338, 8572, 226

closes https://github.com/official-stockfish/Stockfish/pull/6734

No functional change
2026-04-19 07:19:23 +02:00
FauziAkramandJoost VandeVondele cc4637b52b Remove Redundant Cast in MovePicker
closes https://github.com/official-stockfish/Stockfish/pull/6735

No functional change
2026-04-19 07:18:02 +02:00
FauziAkramandJoost VandeVondele 95f3680080 Replace constant LMR history divisor with depth-based array
Passed STC:
LLR: 2.97 (-2.94,2.94) <0.00,2.00>
Total: 677344 W: 175301 L: 174041 D: 328002
Ptnml(0-2): 2018, 79669, 174166, 80673, 2146
https://tests.stockfishchess.org/tests/view/69cfae021668971c9da23b80

Passed LTC:
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 46470 W: 11988 L: 11651 D: 22831
Ptnml(0-2): 37, 4877, 13055, 5244, 22
https://tests.stockfishchess.org/tests/view/69dcd13c3ca80bdf151d4ab5

Passed VLTC (non-reg.):
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 58986 W: 15120 L: 14947 D: 28919
Ptnml(0-2): 7, 5835, 17636, 6008, 7
https://tests.stockfishchess.org/tests/view/69debc6c5c67bee7d241ffe9

closes https://github.com/official-stockfish/Stockfish/pull/6741

Bench: 2723949
2026-04-19 07:15:43 +02:00
sscg13andJoost VandeVondele de7b0cd7a6 Update trace eval
Updates the information printed in the "eval" command. Removes piece value estimation, and adds both small and big net eval information.

closes https://github.com/official-stockfish/Stockfish/pull/6743

No functional change
2026-04-19 07:10:20 +02:00
anematodeandJoost VandeVondele c3bcf74925 Universal binary for x86-64 Linux and Windows
We maintain support for quite a few x86 ISA extensions but relied on the end
user to select the binary that is best for their system.  We can detect at
runtime which architecture to use, and ship a single binary (per OS/ISA
combination). This PR does so, maintaining performance.

This is what I've landed on after quite a few iterations. Basically, we build
each arch separately, use `cpuid` to select one, and jump to that arch's main
function. Some details:

- The preprocessor macro `UNIVERSAL_BINARY` is defined when building for the universal binary.

- The Makefile target `universal-object-[no]pgo` is added, which produces a `stockfish.o` in each arch's build directory.

- To prevent symbol collisions between the multiple builds, we `#define Stockfish Stockfish_[arch]`. Furthermore we wrap the `main` function in `namespace Stockfish { }`.

- We can still get PGO data by linking to a per-arch binary, with `Stockfish_x86_64_avx2::main` as `main`, and running `bench`.
    - For arches not supported by the host we can use Intel SDE – this is what we do in CI.

- When embedding the NNUE, we use C++26/C23 `#embed` instead of `INCBIN`. The issue with `INCBIN` is that it injects assembly directives that we have no control over.
    - To ensure there's only one copy of the networks in the final binary, we define the network data as weak symbols in each per-arch build. Then, in the final link, we add a special nnue_embed.cpp which provides strong symbols.
    - This does require GCC 15. Statically linking libstdc++ allows the binary to still run on older OSes though. And latest msys2 already does static linking + is based off GCC 15.2.0

building can be as simple as

make -j profile-build ARCH=x86-64-universal

or using the sde if the host doesn't support all architectures supported in the
universal binary, and the default host compiler is not recent enough.

make -j profile-build ARCH=x86-64-universal RUN_PREFIX="/path/to/sde -future --" CXX=g++-15

This change is also integrated in CI, so universal binaries are available as
downloadable artifacts.

Next we could also do ARM64, especially Android (all Apple silicon users use
the same binary). It also might be worth getting this to work with clang.

closes https://github.com/official-stockfish/Stockfish/pull/6740

No functional change
2026-04-19 07:07:11 +02:00
rn5f107s2andJoost VandeVondele b1fb50ae69 Simplify away the special case for en passant in the legality check
Since as of 94175524b1 the en passant square is
only set if there is a legal en passant capture, the special handling is
redundant

Passed Non-Regression STC:
https://tests.stockfishchess.org/tests/view/696ffb6512ee1f6231b96fe8
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 79072 W: 20380 L: 20210 D: 38482.

closes https://github.com/official-stockfish/Stockfish/pull/6560

No functional change
2026-04-15 20:35:32 +02:00
DisservinandJoost VandeVondele 45711fceb4 Add PVMoves Struct
Use a dedicated struct for keeping track of the pv moves.

anematode pointed out that we can use the ValueList for this as well, left that open for a future patch.

Passed Non Regression
https://tests.stockfishchess.org/tests/view/69d399cfec2dc6794cf18376
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 267264 W: 68895 L: 68927 D: 129442
Ptnml(0-2): 790, 29238, 73566, 29290, 748

closes https://github.com/official-stockfish/Stockfish/pull/6726

No functional change
2026-04-15 20:30:00 +02:00
87andJoost VandeVondele 4bdeb6352c Remove unnecessary use of _mm512_zextsi256_si512
[Some build environments](https://tests.stockfishchess.org/actions?max_count=400&sort=time&order=desc&action=failed_task&user=gaster319&text=executing) do not have this intrinsic.

closes https://github.com/official-stockfish/Stockfish/pull/6722

No functional change.
2026-04-15 20:26:21 +02:00
FauziAkramandJoost VandeVondele 0694742a31 Remove redundant std::skipws from fresh string streams
In C++, all standard input streams (including std::istringstream) are initialized with the skipws format flag enabled by default. Because these stream objects are scoped locally and freshly constructed on each loop iteration, we are guaranteed that skipws is already active.

closes https://github.com/official-stockfish/Stockfish/pull/6719

No functional change
2026-04-15 20:26:10 +02:00
J. OsterandJoost VandeVondele 5f3dcb92ee Simplify stalemate detection in qsearch.
Conditions for no pawn pushes, non-pawn material and last captured piece are already such restrictive that we don't need to artificially set checkers bitboard for legal move generation.
Also moving the check for available pawn pushes at first place, as it should already filter about 90% of cases.

Passed STC and LTC non-regression tests.
 LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 171232 W: 44202 L: 44131 D: 82899
Ptnml(0-2): 466, 18809, 47023, 18824, 494
https://tests.stockfishchess.org/tests/view/69d3623b61a12cebe17ededa

LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 55662 W: 14424 L: 14247 D: 26991
Ptnml(0-2): 38, 5479, 16609, 5678, 27
https://tests.stockfishchess.org/tests/view/69d64cada07a818b0a25f95e

And also neutral on 10k fixed games test with custom stalemate book.
 Elo: 0.24 ± 0.9 (95%) LOS: 69.9%
Total: 10000 W: 4681 L: 4674 D: 645
Ptnml(0-2): 0, 87, 4819, 94, 0
nElo: 1.81 ± 6.8 (95%) PairsRatio: 1.08
https://tests.stockfishchess.org/tests/view/69d7e4183ca80bdf151d43b8

closes https://github.com/official-stockfish/Stockfish/pull/6715

Bench: 2984258
2026-04-15 20:15:36 +02:00
DubslowandJoost VandeVondele 3cce652564 Simplify relative_age and its constants
STC: https://tests.stockfishchess.org/tests/view/69cf29ed1668971c9da23a5a
LLR: 3.47 (-2.94,2.94) <-1.75,0.25>
Total: 193760 W: 50142 L: 50051 D: 93567
Ptnml(0-2): 534, 21375, 52980, 21448, 543

* Simplify retval and arithmetic semantics
* Remove mixing of int and uint8_t

I find this overall much easier to reason about

(Also rename a DEPTH constant per mstembera's suggestion https://github.com/official-stockfish/Stockfish/pull/5766#issuecomment-2586449296)

closes https://github.com/official-stockfish/Stockfish/pull/6702

No functional change
2026-04-15 20:13:22 +02:00
DisservinandJoost VandeVondele 9bdf2f8d4c Update main network to nn-f68ec79f0fe3.nnue
Trained with the following yaml definition https://github.com/Disservin/nettest/blob/8da0a21503adb341bc89ca20b4e2f166c7c9720d/threats.yaml

and the following trainer change
https://github.com/official-stockfish/nnue-pytorch/commit/d87c248fbed584d08df59820ae0dd309e9b35c85

Initially I saw a lot of weird 0 scores in the binpack, since then @linrock has explained to me/us their meaning and mentioned that they are actually mostly skipped.. except a very small percentage, however it seems this change was still beneficial.

Passed STC:
https://tests.stockfishchess.org/tests/view/69db95f43ca80bdf151d4913
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 106272 W: 27916 L: 27489 D: 50867
Ptnml(0-2): 420, 12533, 26841, 12884, 458

Passed LTC:
https://tests.stockfishchess.org/tests/view/69ddd9003ca80bdf151d4c4d
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 29748 W: 7775 L: 7470 D: 14503
Ptnml(0-2): 24, 3136, 8247, 3445, 22

closes https://github.com/official-stockfish/Stockfish/pull/6730

Bench: 3108996
2026-04-15 20:06:17 +02:00
mstemberaandJoost VandeVondele 7959885747 Interpolate highBestMoveEffort
STC: https://tests.stockfishchess.org/tests/view/69d586a64088e069540a22aa
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 63520 W: 16579 L: 16233 D: 30708
Ptnml(0-2): 138, 7179, 16825, 7435, 183

LTC: https://tests.stockfishchess.org/tests/view/69d6ddd79ffee997bab13a98
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 227100 W: 58279 L: 57591 D: 111230
Ptnml(0-2): 95, 23435, 65828, 24071, 121

Transition highBestMoveEffort smoothly instead of abruptly. May also be more amenable to future tuning.

closes https://github.com/official-stockfish/Stockfish/pull/6729

Bench: 2923401
2026-04-15 20:03:49 +02:00
Ryan LefkowitzandJoost VandeVondele bb5f0100b0 fix syzygy crash on Apple Silicon by unrolling tbprobe adjustment loop
A bit tricky to identify, but in `apple-silicon` builds, specifying a value for
`SyzygyPath` could still lead to crashes at certain depths from a
SIGBUS/SIGSEGV.

This PR replaces the `std::count_if()` adjustment in `tbprobe.cpp`'s
`do_probe_table()` with an annotated handwritten loop, avoiding the clang/LTO
miscompilation behind the crash.

closes https://github.com/official-stockfish/Stockfish/pull/6721

No functional change.
2026-04-15 19:05:14 +02:00
Shawn XuandJoost VandeVondele b63bd7b78e Fix assert
the assert checks for `bestThreadDecisive && newThreadDecisive`. However `newThreadDecisive` is not guaranteed at this point, leading to assertion failures. This PR rearranges the conditions to ensure correctness.

closes https://github.com/official-stockfish/Stockfish/pull/6716

No functional change
2026-04-15 19:01:06 +02:00
Linmiao XuandDisservin bb4eb04a50 Don't store stand pat return value in TT
Passed non-regression STC:
https://tests.stockfishchess.org/tests/view/69d2d3de61a12cebe17ede38
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 68384 W: 17646 L: 17459 D: 33279
Ptnml(0-2): 200, 8058, 17496, 8231, 207

Passed non-regression LTC:
https://tests.stockfishchess.org/tests/view/69d4293433584dad27b3c8cb
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 37290 W: 9582 L: 9380 D: 18328
Ptnml(0-2): 18, 3915, 10579, 4113, 20

closes https://github.com/official-stockfish/Stockfish/pull/6711

Bench: 2923401
2026-04-09 21:56:05 +02:00
Robert Nurnberg @ elitebookandDisservin 9db77822bd Simplify best thread selection
For some time now we can trust the threads' exact win/loss scores. This
allows for some simplification in the best thread selection code.

closes https://github.com/official-stockfish/Stockfish/pull/6708

No functional change
2026-04-09 21:56:05 +02:00
FauziAkramandDisservin 5ae13d2bd4 Remove Redundant Branchless Execution
Passed STC non-reg:
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 102272 W: 26553 L: 26411 D: 49308
Ptnml(0-2): 269, 11212, 28036, 11346, 273
https://tests.stockfishchess.org/tests/view/69d0f1c5e2b443cb2670b710

closes https://github.com/official-stockfish/Stockfish/pull/6707

No functional change
2026-04-09 21:56:05 +02:00
Robert Nurnberg @ elitebookandDisservin d3a4587fe8 Simplify uci pv output logic
The uci pv suppression and PV roll-back logic in master is a bit
convoluted, which makes it hard to reason about the code. In fact,
subtle bugs that led to wrong mated-in scores in game play or a mismatch
between bestmove and first PV move were only recently fixed. Moreover,
in master the uci pv output through `pv()` is called in four different
places.

This PR proposes to simplify this logic. In this patch, the PV is sent
to the GUI from within `iterative_deepening()` only (a) for fail
highs/lows or (b) when an iteration for a root move is completed. All
other PV outputs are now handled by `start_searching()`, just before the
bestmove extraction. This easily ensures that bestmove (and ponder move)
will always be in sync with the final PV output.

The only noticeable change to master is for multi-threaded searches that
do not involve `limits.depth`. Here master would show both the PV from
the aborted search in main thread, as well as the new PV from the
selected best thread. This patch would only show the latter.

While at it, we also remove the requirement to finish at least a depth 1
search and simplify the logic around stalemates/checkmates at root and
in particular avoid any PVs that start with `Move::none()`.

Passed STC non-reg:
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 72416 W: 18784 L: 18609 D: 35023
Ptnml(0-2): 200, 7762, 20127, 7901, 218
https://tests.stockfishchess.org/tests/view/69d3ef0c33584dad27b3c85a

closes https://github.com/official-stockfish/Stockfish/pull/6704

No functional change
2026-04-09 21:56:05 +02:00
Carlos EsparzaandDisservin df01cb7ed3 Avoid branching before ValueList::push_back
Instead of checking whether a threat index is valid and then writing to
a vector if it isn't, we can instead always write to the buffer of the
vector, and only increase the size of the vector if the index is valid.

This saves some branch mispredictions.

passed STC:
https://tests.stockfishchess.org/tests/view/69ceb1689f7a7e3fdfc9a44b
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 205760 W: 53109 L: 52561 D: 100090
Ptnml(0-2): 603, 22552, 56076, 22992, 657

local speedtest shows
```
Result of 100 runs (cycles)
===========================
base (...ockfish.orig) =     296886  +/- 1645
test (...kfish.df0822) =     298862  +/- 1662
speedup %              =      +0.67  +/- 0.19
                         [95% CI; t-statistic]

p(speedup > 0)   =  1.0000
p(speedup > .5%) =  0.9562

CPU: 16 x Intel(R) Core(TM) Ultra 9 185H
Hyperthreading: on
```

closes https://github.com/official-stockfish/Stockfish/pull/6703

No functional change
2026-04-09 21:56:05 +02:00
anematodeandDisservin ead7e650da Fix weird indexing bug
[Passed
STC](https://tests.stockfishchess.org/tests/live_elo/69cf6a5d1668971c9da23ae1)
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 51104 W: 13376 L: 13180 D: 24548
Ptnml(0-2): 134, 5555, 13963, 5781, 119

[Passed
LTC](https://tests.stockfishchess.org/tests/live_elo/69d00a7ee2b443cb2670b5c6)
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 101256 W: 25867 L: 25732 D: 49657
Ptnml(0-2): 59, 10423, 29520, 10576, 50

When processing a promotion we add/remove nonsensical threats, namely,
piece threats of pawns on the 1st or 8th rank. This is not only
inefficient but also seems to lead to an obscure bug during a
promotion–capture + `double_inc_update` that causes a lingering invalid
feature. Currently – as an artifact of the training process – these
parts of the net are filled with random values in [-1,1], which is why
there is a bench change.

@ces42 did a helpful analysis:

> checking bench with depth 17 actually shows that the bug in has been
there all the time since 8e5392d7 (when threat inputs was merged into
master)
> i.e. double_inc_update for ThreatFeatureSet has always been slightly
bugged

After this patch is applied we'll be able to better compress the net by
filling the invalid features with 0, w/o changing bench. I expect a
15%-ish size savings.

closes https://github.com/official-stockfish/Stockfish/pull/6701

Bench: 2700393
2026-04-09 21:53:08 +02:00
FauziAkramandDisservin ef27b4db7b Using the already existing pawn_single_push_bb
Using the already existing pawn_single_push_bb helper which encapsulates
the exact same logic.

closes https://github.com/official-stockfish/Stockfish/pull/6700

No functional change
2026-04-09 21:53:08 +02:00
Niklas FiekasandDisservin 20b61d2fd4 Fix shell escaping in misc.o rule
After 415f2ef1. Now works again with CXX paths like `A:\path\to\g++`
under MSYS2. The cache files are no longer read in the rule itself and
only used as markers for change detection.

closes https://github.com/official-stockfish/Stockfish/pull/6699

No functional change
2026-04-09 21:53:08 +02:00
Linmiao XuandDisservin 93ba8fb35e CI: test for macOS g++ pretending to be clang++
Avoids compilation errors on macOS by ensuring flags specific to g++ are
applied to actual g++

closes https://github.com/official-stockfish/Stockfish/pull/6709

No functional change
2026-04-09 21:53:08 +02:00
Linmiao XuandDisservin a42d328f21 Fix compilation errors on macOS
```
clang++: error: unknown argument: '-fno-ipa-cp-clone'
clang++: error: unknown argument: '-fno-ipa-cp-clone'
make[1]: *** [bitboard.o] Error 1
```

The flag is unsupported by Clang, which pretends to be GCC on macOS, so
only passing -fno-ipa-cp-clone to real GCC fixes macOS builds.

closes https://github.com/official-stockfish/Stockfish/pull/6698

No functional change
2026-04-09 21:53:08 +02:00
anematodeandDisservin bb752eb2f6 Remove step in update_accumulator_refresh_cache
Passed simplification STC
https://tests.stockfishchess.org/tests/live_elo/69c9c050a54395a76133504c

LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 304544 W: 78502 L: 78574 D: 147468
Ptnml(0-2): 906, 33590, 83358, 33506, 912

closes https://github.com/official-stockfish/Stockfish/pull/6697

No functional change
2026-04-09 21:53:08 +02:00
mstemberaandDisservin 969542e4f0 Simplify HalfKAv2_hm::write_indices()
https://tests.stockfishchess.org/tests/view/69bb1fafd7d60419badf31a3
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 116544 W: 30024 L: 29895 D: 56625
Ptnml(0-2): 317, 12875, 31729, 13064, 287

closes https://github.com/official-stockfish/Stockfish/pull/6677

No functional change
2026-04-09 21:53:01 +02:00
FauziAkramandDisservin 82908e0fd6 Simplify mtg in time management
Passed Stc:
LLR: 3.05 (-2.94,2.94) <-1.75,0.25>
Total: 289088 W: 74727 L: 74773 D: 139588
Ptnml(0-2): 903, 30974, 80841, 30918, 908
https://tests.stockfishchess.org/tests/view/699d9093eaae015cd278eda3

Passed Ltc:
LLR: 3.02 (-2.94,2.94) <-1.75,0.25>
Total: 144438 W: 36859 L: 36766 D: 70813
Ptnml(0-2): 62, 13391, 45235, 13454, 77
https://tests.stockfishchess.org/tests/view/699f02913ece4c464328a264

Passed Sudden death:
LLR: 2.99 (-2.94,2.94) <-1.75,0.25>
Total: 53406 W: 13960 L: 13753 D: 25693
Ptnml(0-2): 265, 5848, 14253, 6089, 248
https://tests.stockfishchess.org/tests/view/69a47548a70d5f3736be6482

closes https://github.com/official-stockfish/Stockfish/pull/6644

No functional change
2026-04-09 21:48:51 +02:00
FauziAkramandDisservin 19dec05602 Update a few outdated wrong comments
closes https://github.com/official-stockfish/Stockfish/pull/6525

No functional change
2026-04-09 21:48:40 +02:00
anematodeandDisservin 5eeca7392e Update NNUE architecture to SFNNv14 and net nn-7bf13f9655c8.nnue
[STC](https://tests.stockfishchess.org/tests/live_elo/69cb991bc025c305b7daa219)

LLR: 2.97 (-2.94,2.94) <0.00,2.00>
Total: 47520 W: 12558 L: 12219 D: 22743
Ptnml(0-2): 190, 5474, 12102, 5795, 199

[LTC](https://tests.stockfishchess.org/tests/live_elo/69cc6b42731dfb72d2e92128)

LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 123756 W: 31996 L: 31497 D: 60263
Ptnml(0-2): 81, 13512, 34221, 13955, 109

Adds features for pawns pushing against other pawns. For indexing, we
use the existing threats feature set by adding the forward square to the
squares threatened by a pawn. This threat is only enabled when another
pawn occupies that square. We then add appropriate logic changes in
`update_piece_threats` and `features/full_threats.cpp`.

The wonderful Ciekce (of Stormphrax) and peregrine (of Reckless) also
had similar ideas with adding forward pawn movements to TI, and I
appreciate their giving me motivation to figure out `nnue-pytorch` and
test it.

closes https://github.com/official-stockfish/Stockfish/pull/6694

Bench: 2926703
2026-04-02 20:32:43 +02:00
Linmiao XuandDisservin 8a1b0f8b04 Reorder NNUE accumulator evals
Makes eval calls more cache-friendly on a hot path.

Hints of a speedup found locally:
```
Result of  30 runs
==================
base (...ockfish-base) =    1754124  +/- 2887
test (...h-eval-order) =    1757848  +/- 2412
diff                   =      +3724  +/- 1368

speedup        = +0.0021
P(speedup > 0) =  1.0000

CPU: 16 x arm
Hyperthreading: off
```

Passed STC:
https://tests.stockfishchess.org/tests/view/69c84f3cf40d95b975597c21
LLR: 3.36 (-2.94,2.94) <0.00,2.00>
Total: 335840 W: 87022 L: 86257 D: 162561
Ptnml(0-2): 932, 36760, 91816, 37435, 977

closes https://github.com/official-stockfish/Stockfish/pull/6693

No functional change
2026-04-02 20:32:43 +02:00
Linmiao XuandDisservin 171afd2b4d NEON: simplify updates in NNUE accumulator
Reduce # instructions using widening add/sub calls on AArch64.
Add shims for 32-bit ARM.

Passed non-regression STC:
https://tests.stockfishchess.org/tests/view/69c093d3f690a4b7f5fb0d6e
LLR: 5.15 (-2.94,2.94) <-1.75,0.25>
Total: 338624 W: 86931 L: 86853 D: 164840
Ptnml(0-2): 667, 35888, 96079, 36056, 622

closes https://github.com/official-stockfish/Stockfish/pull/6692

No functional change
2026-04-02 20:32:43 +02:00
anematodeandDisservin c41c9bb973 Add -fno-ipa-cp-clone on GCC
Passed non-regression STC at
tests.stockfishchess.org/tests/view/69c9b8c3a54395a761335044 (set to
gainer bounds, *mea culpa*)

There's a major performance penalty when compiling the fish with GCC 16.
@Torom 's measurements (similar to mine, but more precise):

```
GCC 15 (master) vs. 16 (master)
sf_base =  2549174 +/-   2984 (95%)
sf_test =  2435456 +/-   2239 (95%)
diff    =  -113718 +/-   3599 (95%)
speedup = -4.46100% +/- 0.141% (95%)

GCC 15 (master) vs. 16 (patch)
sf_base =  2544107 +/-   3091 (95%)
sf_test =  2568837 +/-   3164 (95%)
diff    =    24729 +/-   4359 (95%)
speedup = 0.97204% +/- 0.171% (95%)
```

Based on looking at the disassembly + profiling data of the builds, the
root cause appears to be overly aggressive code duplication from
constant propagation of various functions especially in `search.cpp`.
The worst offender is `Worker::search`, which I think is being cloned
and optimized based on the value of `cutNode`. `perf stat` indicates
that the instruction cache miss rate goes up by ~50% on the GCC 16
build.

closes https://github.com/official-stockfish/Stockfish/pull/6691

No functional change
2026-04-02 20:32:34 +02:00
anematodeandDisservin 347fcaa392 Stack allocate NNUE buffer
Passed simp STC

LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 29632 W: 7772 L: 7552 D: 14308
Ptnml(0-2): 76, 3155, 8147, 3349,

Id expect there to be a small speedup (bc the compiler can skip some
stores) but i haven’t tried to measure it

closes https://github.com/official-stockfish/Stockfish/pull/6688

No functional change
2026-04-02 20:30:44 +02:00
mstemberaandDisservin 7926f16ea7 Minor cleanups
closes https://github.com/official-stockfish/Stockfish/pull/6685

No functional change
2026-04-02 20:30:33 +02:00
anematodeandDisservin 8132dbcb5a split affine_transform on VNNI as well
```
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 184544 W: 47990 L: 47474 D: 89080
Ptnml(0-2): 551, 20238, 50206, 20698, 579
```

Torom measured

```
sf_base =  2538696 +/-   2915 (95%)
sf_test =  2546510 +/-   3011 (95%)
diff    =     7814 +/-   4146 (95%)
speedup = 0.30782% +/- 0.163% (95%)
```

I get something similar. The benefit would be larger if we ever decide
to further increase the L2 size I think

closes https://github.com/official-stockfish/Stockfish/pull/6683

No functional change
2026-04-02 20:30:33 +02:00
Linmiao XuandDisservin f614ff90e2 Neon dotprod speedup in sparse-input affine transform
Also apply VNNI accumulator splitting strategy to NEON dotprod.

Speedup measured locally with profile-build, apple-silicon M3 Pro:
```
Result of  20 runs
==================
base (...kfish-master) =    1582485  +/- 12985
test (...parse-affine) =    1605204  +/- 13801
diff                   =     +22720  +/- 1212

speedup        = +0.0144
P(speedup > 0) =  1.0000

CPU: 11 x arm
Hyperthreading: off
```

Passed STC:
https://tests.stockfishchess.org/tests/view/69c04c71f690a4b7f5fb0cde
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 80576 W: 20748 L: 20391 D: 39437
Ptnml(0-2): 161, 8472, 22658, 8843, 154

closes https://github.com/official-stockfish/Stockfish/pull/6682

No functional change
2026-04-02 20:30:26 +02:00
Robert Nurnberg @ elitebookandDisservin b9f3273af1 Align ponder move and final PV output
In master for various reasons the given ponder move may get out of sync
with (the second move of) the last PV sent to the GUI:
1. If a ponder move is extracted from the TT despite bestmove leading to
a game ending draw.
2. If a ponder move is extracted from the TT from a fail high/low, since
after the ponder move extraction no new PV is sent to the GUI.
3. If `syzygy_extend_pv()` from within the call to `pv()` changes the PV
after the ponder move has been selected.
4. If the PV roll-back to protect mated-in scores differs in the second
move from the final PV sent to the GUI.

This PR keeps ponder move and the last printed PV in sync.

Fixes #6676.

closes https://github.com/official-stockfish/Stockfish/pull/6679

No functional change
2026-04-02 20:30:12 +02:00
anematodeandDisservin d173a0655d Speedup splat_moves on avx512icl
STC
```
LLR: 5.45 (-2.94,2.94) <0.00,2.00>
Total: 612192 W: 158602 L: 157260 D: 296330
Ptnml(0-2): 1634, 67783, 166027, 68911, 1741
```

@Torom measured:
```
sf_base =  2380902 +/-   1016 (95%)
sf_test =  2391426 +/-   1065 (95%)
diff    =    10524 +/-   1336 (95%)
speedup = 0.44203% +/- 0.056% (95%)
```

I've verified that `_mm512_slli_epi16(v, 0)` gets optimized out in GCC
9+ and clang 4+. Added constants to types.h so that people messing
around with the layout of `Move` don't have to know about this part of
the code.

closes https://github.com/official-stockfish/Stockfish/pull/6670

No functional change
2026-03-18 20:49:20 +01:00
FauziAkramandDisservin add173263d VVLTC Tune
Passed VVLTC with stc bounds:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 69810 W: 18119 L: 17814 D: 33877
Ptnml(0-2): 6, 6222, 22144, 6527, 6
https://tests.stockfishchess.org/tests/view/69b43f666c456d3a77a50a5d

Passed VVLTC with ltc bounds:
LLR: 2.95 (-2.94,2.94) <0.50,2.50>
Total: 52458 W: 13574 L: 13258 D: 25626
Ptnml(0-2): 5, 4632, 16636, 4954, 2
https://tests.stockfishchess.org/tests/view/69b16dfbd0c9228834e88e99

closes https://github.com/official-stockfish/Stockfish/pull/6669

Bench: 3164843
2026-03-18 20:49:06 +01:00
Robert Nurnberg @ elitebookandDisservin 67a2c247d4 Improve multi-threaded go-mate searches
Ever since #5094 master only lets the main thread terminate `go mate`
searches, reverting the earlier improvement #1215.

This PR restores the old logic. So any thread that found a "mate in x"
can now stop the search.

To make this work robustly, we need to guard against inexact mate scores
in the best thread selection. In addition, in contrast to time limits,
the main thread may now not complete a d1 search for a mated-in
position.
In master an aborted d1 search may have a PV beginning with
`Move::none()`, in which case no thread selection is performed. See
#623. We fix this bug here by checking if `lastBestPV` is empty or not.
For interrupted d1 searches we now label mated-in scores as inexact.

While at it, we also simplify the logic for detecting if we can
terminate a go mate x search, using the fact that threads.stop can only
be false if we have a completed iteration with a valid score.

The PR has no effect on game play, but should slightly improve general
mate finding and speed up multi-threaded `go mate` searches.

We also add a corresponding matecheck run to the CI. This only involves
61 mates up to mate-in-2. Test runs with the first 50 or 100 mates from
`mates2000.epd` did at times not finish within 30 minutes on my fork or
in local tests, possibly due to search explosions for some mate-in-3
positions.

closes https://github.com/official-stockfish/Stockfish/pull/6668

No functional change
2026-03-18 20:49:06 +01:00
Pieter te BrakeandDisservin e093339c2f Use existing function for removing and placing a piece.
Passed non-regression STC:
```
LLR: 3.33 (-2.94,2.94) <-1.75,0.25>
Total: 359712 W: 93077 L: 93175 D: 173460
Ptnml(0-2): 1088, 39816, 98144, 39722, 1086
```
https://tests.stockfishchess.org/tests/view/69af5e9a6b61ccc788710e31

closes https://github.com/official-stockfish/Stockfish/pull/6667

No functional change
2026-03-18 20:49:06 +01:00
Tomasz SobczykandDisservin e38f5b2f53 Strict FEN parsing. Exit on setting invalid position via UCI.
fixes #6663, #6664, and a million others issues raised over the years.

This is similar to
https://github.com/official-stockfish/Stockfish/pull/4563/changes but
more conservative and errors are split between "Unsupported position"
and "Invalid FEN".

The FEN parser needs to be strict as a foundation for safety. It does
not specify much of the semantics, so this step is fairly simple. Parts
after the ep square are optional, however, since it's common, for
example in EPD notation.

Errors arising from positional semantics that were previously bucketed
under invalid FENs are now reported as "Unsupported position". Only
positions that are potentially problematic are designated as
unsupported. It is NOT guided by illegality of the position, but instead
by the ability of the engine to handle them correctly. This means that
some checks from the previous PR were removed.

std::exit is used instead of std::terminate so atexit handles will be
called.

Probably wise to run copilot on this or smth because I scribbled it
without much thought.

With these small changes and reduced, less controversial, scope I hope
this PR will finally make it and we will be done with these weekly
issues. I'll wait with putting it on fishtest until there's approval.

closes https://github.com/official-stockfish/Stockfish/pull/6665

No functional change
2026-03-18 20:49:06 +01:00
Disservin 415f2ef11c Don't recompile misc.cpp on every run
alternative to https://github.com/official-stockfish/Stockfish/pull/6660

closes https://github.com/official-stockfish/Stockfish/pull/6662

No functional change
2026-03-18 20:49:06 +01:00
MARLIN-ToolsandDisservin 3c04b5c429 Fix Depth 1 bug at very low time controls, some fishtesting workers, and delayed move senders like Arena.
Non-Regression STC:

LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 98208 W: 25423 L: 25276 D: 47509
Ptnml(0-2): 271, 10713, 26994, 10850, 276
https://tests.stockfishchess.org/tests/view/69ad43c21d849c824c6afe82

-

Hell VVSTC (0.2+0.02)
LLR: 2.70 (-2.94,2.94) <0.00,2.00>
Total: 688 W: 660 L: 11 D: 17
Ptnml(0-2): 1, 0, 10, 15, 318
https://tests.stockfishchess.org/tests/view/69ae4bea1d849c824c6b00b0

-

Sudden Death (1+0)

LLR: 2.19 (-2.94,2.94) <-1.75,0.25>
Total: 538 W: 536 L: 0 D: 2
Ptnml(0-2): 0, 0, 0, 2, 267
https://tests.stockfishchess.org/tests/live_elo/69ae6c031d849c824c6b00dc

-

Sudden Death (5+0)

LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 22248 W: 6058 L: 5802 D: 10388
Ptnml(0-2): 214, 2420, 5583, 2710, 197
https://tests.stockfishchess.org/tests/view/69ae6dd01d849c824c6b00ee

-

Fixes #6639

closes https://github.com/official-stockfish/Stockfish/pull/6659

No functional change
2026-03-18 20:49:06 +01:00
Ryan HirschandDisservin 8b49968386 Speed up update_accumulator_refresh_cache with AVX512ICL
Passed STC:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 212960 W: 55550 L: 54991 D: 102419
Ptnml(0-2): 583, 23688, 57393, 24219, 597
https://tests.stockfishchess.org/tests/view/69ab916ccb31ee884aed62ea

closes https://github.com/official-stockfish/Stockfish/pull/6657

No functional change
2026-03-18 20:49:06 +01:00
ZlomenyMesicandDisservin e20ef7ed12 Disable IIR and quiet shallow pruning in PV lines
Disable IIR and quiet shallow pruning in nodes that are part of the PV
line from the previous search iteration. This is done by storing the PV
in Worker and adding a followPV flag to Stack. The new flag may also be
used in other pruning techniques, allowing further improvement.

Passed STC:
```
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 63776 W: 16679 L: 16323 D: 30774
Ptnml(0-2): 169, 7441, 16377, 7667, 234
```
https://tests.stockfishchess.org/tests/view/69aad1a7d0f65de95886e289

Passed LTC:
```
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 87342 W: 22421 L: 22000 D: 42921
Ptnml(0-2): 40, 9443, 24299, 9834, 55
```
https://tests.stockfishchess.org/tests/view/69ab5993cb31ee884aed629d

closes https://github.com/official-stockfish/Stockfish/pull/6656

Bench: 2434614
2026-03-18 20:49:06 +01:00
anematodeandJoost VandeVondele b3a810a1c4 Fix avx512icl update_piece_threats bug
Thanks to MinetaS for discovering this and finding the culprit commit,
specific to this architecture. 9f42980 was subtly incorrect, leading to
spurious wrong benches on avx512icl. Because this piece of code has twice been
the source of wrong benches, I also added a brief comment explaining why it
needs to be this way, and added a sample position to bench.

closes https://github.com/official-stockfish/Stockfish/pull/6654

Bench: 2288704
2026-03-07 22:28:06 +01:00
Shawn XuandJoost VandeVondele 2907ee1a90 Requantize smallnet to nn-47fc8b7fff06.nnue
Passed Non-regression STC:
LLR: 3.01 (-2.94,2.94) <-1.75,0.25>
Total: 340384 W: 88165 L: 88274 D: 163945
Ptnml(0-2): 1145, 40248, 87571, 40027, 1201
https://tests.stockfishchess.org/tests/view/69a4c49ab4a714eaaa196bc0

Passed Non-regression LTC:
LLR: 2.97 (-2.94,2.94) <-1.75,0.25>
Total: 125922 W: 32222 L: 32106 D: 61594
Ptnml(0-2): 100, 13815, 35002, 13957, 87
https://tests.stockfishchess.org/tests/view/69a9d5f489704e42c5e3a35d

closes https://github.com/official-stockfish/Stockfish/pull/6655

Bench: 2171643
2026-03-07 22:20:46 +01:00
8b6d8def30 Counter-Move History Continuity (CMHC)
Currently, the engine updates each level of the continuation history stack (ply -1, -2, -3, etc.) with fixed weights that are independent of one another. The idea behind CMHC is to scale the magnitude of these updates based on the consistency of the move's performance across the stack.
By tracking how often a move has historically performed well (positive history) within the current continuation context, we can adjust the update magnitude dynamically. Moves that demonstrate "continuity" (appearing as strong candidates across multiple historical contexts) receive more significant updates, while unproven or inconsistent moves are dampened.

Passed STC:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 103264 W: 26954 L: 26541 D: 49769
Ptnml(0-2): 338, 11910, 26728, 12313, 343
https://tests.stockfishchess.org/tests/view/69a81702fac54339cf435a87

Passed LTC:
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 47406 W: 12259 L: 11920 D: 23227
Ptnml(0-2): 22, 5052, 13237, 5349, 43
https://tests.stockfishchess.org/tests/view/69aab480bd35724e4b290498

closes https://github.com/official-stockfish/Stockfish/pull/6653

Bench: 2356418

Co-Authored-By: Daniel Samek <samekdaniel@email.cz>
2026-03-07 22:16:22 +01:00
Shawn XuandJoost VandeVondele 868b63d421 Update main network to nn-9a0cc2a62c52.nnue
network with new hash encoding.

closes https://github.com/official-stockfish/Stockfish/pull/6651

no functional change
2026-03-07 22:12:51 +01:00
Robert Nurnberg @ elitebookandJoost VandeVondele 82320c0e38 [CI] add multithreaded matetrack runs that simulate gameplay
closes https://github.com/official-stockfish/Stockfish/pull/6641

No functional change
2026-03-07 22:10:14 +01:00
maximmasiutinandJoost VandeVondele 702d4b8c9d Simplify attackers_to_exist function
test based on #6586 by maximmasiutin

https://tests.stockfishchess.org/tests/view/69977d58e2562aa5bc1e09c4
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 225056 W: 58208 L: 58195 D: 108653
Ptnml(0-2): 677, 25001, 61196, 24940, 714

closes https://github.com/official-stockfish/Stockfish/pull/6633

No functional change
2026-02-28 12:10:50 +01:00
anematodeandJoost VandeVondele 4c0878cb0b skip setting threatByLesser[KING]
It doesn't make sense for a king to move to a square threatened by a lesser
piece, since that move would be illegal.

closes https://github.com/official-stockfish/Stockfish/pull/6640

No functional change
2026-02-28 12:07:38 +01:00
mstemberaandJoost VandeVondele bc28ff15bd Simplify threat by lesser
STC https://tests.stockfishchess.org/tests/view/699da988eaae015cd278ede4
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 141184 W: 36647 L: 36541 D: 67996
Ptnml(0-2): 511, 16620, 36237, 16700, 524

LTC https://tests.stockfishchess.org/tests/view/699f4bee2be03365d5073c71
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 99852 W: 25463 L: 25322 D: 49067
Ptnml(0-2): 47, 10997, 27721, 11090, 71

Removes a constant and a branch(for the price of a subtract) and treats the
from and to squares symmetrically.

closes https://github.com/official-stockfish/Stockfish/pull/6637

bench: 2460547
2026-02-28 12:06:39 +01:00
ByteBufandJoost VandeVondele b6769f101c CI: Fix typo in error message
closes https://github.com/official-stockfish/Stockfish/pull/6624

No functional change
2026-02-28 12:00:08 +01:00
Ryan HirschandJoost VandeVondele 0571e4e390 More NMP when improving
Passed STC:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 89472 W: 23291 L: 22895 D: 43286
Ptnml(0-2): 317, 10389, 22942, 10757, 331
https://tests.stockfishchess.org/tests/view/699f070c3ece4c464328a26f

Passed LTC:
LLR: 2.96 (-2.94,2.94) <0.50,2.50>
Total: 95880 W: 24537 L: 24098 D: 47245
Ptnml(0-2): 55, 10259, 26880, 10684, 62
https://tests.stockfishchess.org/tests/view/699fb9be44b9136df1165ded

closes https://github.com/official-stockfish/Stockfish/pull/6638

bench: 2534375
2026-02-28 11:58:49 +01:00
Robert NurnbergandJoost VandeVondele 46ac9a7e6a prevent unproven mated-in scores in game play
This PR removes the variable threads.abortedSearch introduced in #4990 and
relies on threads.stop and completedDepth instead.

The logic in master is only guaranteed to work in single threaded search. For
multiple threads, as soon as threads.stop is set, some of the threads may abort
their search, and so their mated-in scores are not reliable.

Compared to master, this patch prevents unproven mated-in scores in these
multi-threaded scenarios:

    in game play (when time management is used)
    when the uci commands quit or stop are received
    when maximum depth is reached
    for go mate commands

The patch will change the bestmove in those very rare situations where the bug
occurs in master. This is because bestmove is taken from rootMoves[0].pv[0],
which will now no longer contain unproven mated-in PVs. In that sense the patch
is "functional". But of course, it does not change bench.

While at it, we also fix a bug in master that suppressed PV output for inexact
mated-in scores on receiving threads.stop. In cases where uciScore < score with
score not being a proven loss, and where the new bestmove was different from
the last completed iteration, this led to a bestmove output that did not match
the final PV (which was for an exact score).

Fixes #6293.

Fixes #6626.

closes https://github.com/official-stockfish/Stockfish/pull/6636

No functional change
2026-02-28 11:55:03 +01:00
sscg13andJoost VandeVondele a6d055d7e2 Update NNUE architecture to SFNNv13 and net nn-5227780996d3.nnue
Since the introduction of Threat Inputs in SFNNv10 and its refinements, the
accumulator/L1 size has significantly decreased. This has made it more
computationally favorable to double the L2 size from 16 to 32.

Failed STC:
LLR: -2.98 (-2.94,2.94) <0.00,2.00>
Total: 189184 W: 49649 L: 49635 D: 89900
Ptnml(0-2): 881, 22479, 47812, 22585, 835
https://tests.stockfishchess.org/tests/view/69928ef7bfe62dc95a624d5c

Passed LTC:
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 37902 W: 9833 L: 9509 D: 18560
Ptnml(0-2): 35, 4074, 10408, 4400, 34
https://tests.stockfishchess.org/tests/view/69937936ff782f8eeef611e3

Passed VLTC SMP:
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 20576 W: 5455 L: 5191 D: 9930
Ptnml(0-2): 6, 1837, 6338, 2101, 6
https://tests.stockfishchess.org/tests/view/6994df0586e4a32314116399

closes https://github.com/official-stockfish/Stockfish/pull/6619

bench: 2396996
2026-02-18 21:46:57 +01:00
mstemberaandJoost VandeVondele ba852999ff Minor cleanups in bitboard.h
closes https://github.com/official-stockfish/Stockfish/pull/6618

No functional change
2026-02-18 21:44:20 +01:00
mstemberaandJoost VandeVondele 84bdf5e2a6 Don't process kings after #6609
https://github.com/official-stockfish/Stockfish/pull/6617

No functional change
2026-02-18 21:40:01 +01:00
rn5f107s2andJoost VandeVondele 5b5986b095 Add en passant santization tests to the CI and pos is ok
closes https://github.com/official-stockfish/Stockfish/pull/6615

No functional change
2026-02-18 21:39:06 +01:00
cf559b2c17 speed up update_piece_threats after SFNNv12
with the removal of all king-related threats in SFNNv12 we can avoid some work in update_piece_threats

STC for anematode's swan:
https://tests.stockfishchess.org/tests/view/6986b920b0f3ca5200aaf80a
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 262880 W: 68379 L: 67747 D: 126754
Ptnml(0-2): 808, 29001, 71223, 29567, 841

STC of further speedups on top of swan:
https://tests.stockfishchess.org/tests/view/698d7f8362a8f472da3a8a35
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 65920 W: 17126 L: 16781 D: 32013
Ptnml(0-2): 186, 7084, 18105, 7369, 216

closes https://github.com/official-stockfish/Stockfish/pull/6614

No functional change

Co-Authored-By: Timothy Herchen <timothy.herchen@gmail.com>
2026-02-18 05:58:44 +01:00
FauziAkramandJoost VandeVondele e6d04b4ec5 VVLTC tune
Passed VVLTC with stc bounds:
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 21424 W: 5638 L: 5387 D: 10399
Ptnml(0-2): 2, 1925, 6607, 2176, 2
https://tests.stockfishchess.org/tests/view/69938fbcff782f8eeef612a9

Passed VVLTC with ltc bounds:
LLR: 2.96 (-2.94,2.94) <0.50,2.50>
Total: 33410 W: 8784 L: 8499 D: 16127
Ptnml(0-2): 5, 2968, 10474, 3253, 5
https://tests.stockfishchess.org/tests/view/69923ea872254723ef22c6fa

Verify Elo loss at STC:
LLR: -2.93 (-2.94,2.94) <-1.75,0.25>
Total: 17504 W: 4420 L: 4704 D: 8380
Ptnml(0-2): 56, 2259, 4420, 1947, 70
https://tests.stockfishchess.org/tests/view/69946e4186e4a3231411625b

closes https://github.com/official-stockfish/Stockfish/pull/6616

bench: 3074962
2026-02-18 05:54:55 +01:00
Daniel MonroeandJoost VandeVondele 77d46ff61c Simplify probCutDepth term
Passed STC
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 139392 W: 36003 L: 35897 D: 67492
Ptnml(0-2): 451, 16383, 35911, 16511, 440
https://tests.stockfishchess.org/tests/view/697ebb4d5f56030af97b5aa5

Passed LTC
LLR: 2.97 (-2.94,2.94) <-1.75,0.25>
Total: 266934 W: 67822 L: 67851 D: 131261
Ptnml(0-2): 175, 29579, 73955, 29616, 142
https://tests.stockfishchess.org/tests/view/6980e5a26362aee5c8a55398

closes https://github.com/official-stockfish/Stockfish/pull/6607

Bench: 2515956
2026-02-13 07:22:36 +01:00
Daniel MonroeandJoost VandeVondele 91c6ea82aa Simplify malus formula
Passed non-regression STC
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 51936 W: 13490 L: 13283 D: 25163
Ptnml(0-2): 161, 6128, 13184, 6333, 162
https://tests.stockfishchess.org/tests/view/698120e06362aee5c8a55412

Passed non-regression LTC
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 233772 W: 59502 L: 59499 D: 114771
Ptnml(0-2): 144, 25753, 65086, 25762, 141
https://tests.stockfishchess.org/tests/view/69827426889c3a4c31c8db52

closes https://github.com/official-stockfish/Stockfish/pull/6606

bench 2739355
2026-02-13 07:15:35 +01:00
Daniel MonroeandJoost VandeVondele 89b4f457e0 Simplify malus decay formula
Passed non-regression STC
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 93600 W: 24165 L: 24007 D: 45428
Ptnml(0-2): 325, 11021, 23958, 11163, 333
https://tests.stockfishchess.org/tests/view/698121d46362aee5c8a55414

Passed non-regression LTC
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 83232 W: 21236 L: 21077 D: 40919
Ptnml(0-2): 46, 9147, 23083, 9282, 58
https://tests.stockfishchess.org/tests/view/698336dc473df9d1d24a90e8

closes https://github.com/official-stockfish/Stockfish/pull/6600

Bench: 2666284
2026-02-13 07:12:04 +01:00
anematodeandJoost VandeVondele b808fd3d55 avoid modify_and_replicate for first load
We call modify_and_replicate in load_networks, but this is quite inefficient.

closes https://github.com/official-stockfish/Stockfish/pull/6574

No functional change
2026-02-13 07:06:33 +01:00
lemteayandJoost VandeVondele b248d689a8 Add constexpr specifier back to conthist_bonuses
Passed STC
LLR: 2.94 (-2.94,2.94) <0.00,2.00>
Total: 60288 W: 15668 L: 15331 D: 29289
Ptnml(0-2): 159, 6490, 16532, 6781, 182
https://tests.stockfishchess.org/tests/view/698c835762a8f472da3a88e1

closes https://github.com/official-stockfish/Stockfish/pull/6611

No functional change
2026-02-13 07:03:17 +01:00
Maxim MasiutinandJoost VandeVondele 238ef05bb0 Prefetch threat weight rows during append_changed_indices
Passed STC (first run):
https://tests.stockfishchess.org/tests/view/698030ee6362aee5c8a552c7
LLR: 2.96 (-2.94,2.94) <0.00,2.00>
Total: 120800 W: 31336 L: 30912 D: 58552
Ptnml(0-2): 330, 13084, 33172, 13460, 354

Passed LTC:
https://tests.stockfishchess.org/tests/view/6983946d473df9d1d24a916d
LLR: 2.94 (-2.94,2.94) <0.50,2.50>
Total: 86850 W: 22352 L: 21951 D: 42547
Ptnml(0-2): 45, 8506, 25934, 8883, 57

Passed STC SMP (after changes made after opening the PR):
https://tests.stockfishchess.org/tests/view/6987b4c1b0f3ca5200aaf9da
LLR: 2.95 (-2.94,2.94) <0.00,2.00>
Total: 84480 W: 21994 L: 21627 D: 40859
Ptnml(0-2): 98, 9378, 22932, 9723, 109

Passed STC (second run, as a sanity check made after changes made after opening the PR -- these changed, however, didn't change assembly code produced by the compiler):
https://tests.stockfishchess.org/tests/view/6987ad21b0f3ca5200aaf9c9
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 132192 W: 34526 L: 34083 D: 63583
Ptnml(0-2): 421, 14511, 35759, 15014, 391

GCC maps __builtin_prefetch locality=1 to PREFETCHT2 instruction, targeting L2
cache. To make things clear, added template-based approach as suggested by
Disservin.

PREFETCHT2 should be the appropriate choice for the 78 MB threat weight table:
avoids L1d pollution, parks data in L2 where it is promoted to L1d on actual
load.

With 200-500 cycle lead time from make_index, data has time to arrive in L2.

suggestions by Disservin and anematode included

closes https://github.com/official-stockfish/Stockfish/pull/6602

No functional change
2026-02-13 07:01:15 +01:00
Joost VandeVondele 99221e5ee6 Introduce timeout for wget/curl in net download
ensures the download terminates after 5min, even if the server stalled.

closes https://github.com/official-stockfish/Stockfish/pull/6608

No functional change
2026-02-13 06:57:08 +01:00
sscg13andJoost VandeVondele 83e42045a6 Update NNUE architecture to SFNNv12 and net nn-7e1657811c6d.nnue
This net is trained with https://github.com/sscg13/nettest/blob/no-king-attacker/threats.yaml.

Passed non-regression STC:
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 92640 W: 24371 L: 24209 D: 44060
Ptnml(0-2): 389, 11047, 23322, 11137, 425
https://tests.stockfishchess.org/tests/view/6986345fa259e26d2c32e9c5

Passed non-regression LTC:
LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 198636 W: 50960 L: 50919 D: 96757
Ptnml(0-2): 176, 22218, 54511, 22215, 198
https://tests.stockfishchess.org/tests/view/6987ee29b0f3ca5200aafa51

closes https://github.com/official-stockfish/Stockfish/pull/6609

bench: 2465828
2026-02-13 06:56:10 +01:00
Daniel MonroeandDisservin 21b0974f8d Simplify away main history default
Passed non-regression STC
LLR: 2.97 (-2.94,2.94) <-1.75,0.25>
Total: 130752 W: 33725 L: 33605 D: 63422
Ptnml(0-2): 421, 15472, 33460, 15612, 411
https://tests.stockfishchess.org/tests/view/697eb9e35f56030af97b5a9e

Passed non-regression LTC
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 53352 W: 13697 L: 13509 D: 26146
Ptnml(0-2): 36, 5804, 14811, 5986, 39
https://tests.stockfishchess.org/tests/view/6980e5846362aee5c8a55396

closes https://github.com/official-stockfish/Stockfish/pull/6590

Bench: 2375860
2026-02-08 15:22:29 +01:00
Dieter DobbelaereandDisservin 6b6334ce8e Replace FNV-1a hash by faster and better MurmurHash64A.
Replace FNV-1a hash algorithm by public domain MurmurHash64A, by Austin
Appleby, which is faster and has better hash properties. This is also
the default hash algorithm of GNU C++ Standard Library.

Startup time on a given system (with x86-64-avx2 binaries) reduces from
378ms to 332ms (46ms faster). Hashing of the largest data input (46.1MB)
goes from 44.2ms to 6.2ms, so about 7x faster.

No functional change

closes https://github.com/official-stockfish/Stockfish/pull/6579

Bench: 2570943
2026-02-08 15:22:21 +01:00
lemteayandDisservin 005f0f9b2a Raise inline threshold for clang/llvm
Passed STC:
https://tests.stockfishchess.org/tests/view/696884a56d118e46e1731473
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 29952 W: 7843 L: 7549 D: 14560
Ptnml(0-2): 72, 3215, 8118, 3489, 82

.text area size:
```
master(eb5a65a): 455490
pr:              453250
```

Bench (clang 21.1.8):
```
Result of 100 runs
==================
base (...kfish-master) =    1719708  +/- 3473
test (./stockfish    ) =    1770555  +/- 3284
diff                   =     +50846  +/- 3949

speedup        = +0.0296
P(speedup > 0) =  1.0000

CPU: 16 x AMD Ryzen 9 7945HX with Radeon Graphics
Hyperthreading: on
```

closes https://github.com/official-stockfish/Stockfish/pull/6550

No functional change
2026-02-08 15:22:06 +01:00
FauziAkramandDisservin f0d0c3ddd2 Simplify Triple Margin Formula
Passed STC:
LLR: 3.01 (-2.94,2.94) <-1.75,0.25>
Total: 517472 W: 133271 L: 133575 D: 250626
Ptnml(0-2): 1628, 61366, 133032, 61102, 1608
https://tests.stockfishchess.org/tests/view/69590175d844c1ce7cc7e9a2

Passed LTC:
LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 95430 W: 24242 L: 24098 D: 47090
Ptnml(0-2): 42, 10289, 26926, 10399, 59
https://tests.stockfishchess.org/tests/view/695f7f03ca95f52e4b85253c

Passed VVLTC:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 58828 W: 15299 L: 15135 D: 28394
Ptnml(0-2): 4, 5277, 18687, 5443, 3
https://tests.stockfishchess.org/tests/view/6961686c8709a8d27c0383f6

closes https://github.com/official-stockfish/Stockfish/pull/6546

Bench: 2570943
2026-02-08 15:22:06 +01:00
Jakub CiolekandDisservin 73475b198d Remove prefetching in Position::do_null_move
Passed non-regression STC, Hash=16:

LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 60704 W: 15709 L: 15522 D: 29473
Ptnml(0-2): 169, 6589, 16649, 6776, 169
https://tests.stockfishchess.org/tests/view/6963d381f6d2e8deff76918f

Passed non-regression STC SMP, Hash=512:

LLR: 2.95 (-2.94,2.94) <-1.75,0.25>
Total: 313512 W: 80151 L: 80231 D: 153130
Ptnml(0-2): 376, 35492, 85111, 35390, 387
https://tests.stockfishchess.org/tests/view/696e1684e5a70879bd3fe38f

closes https://github.com/official-stockfish/Stockfish/pull/6545

No functional change
2026-02-08 15:22:06 +01:00
ppigazziniandDisservin e3a5c40f05 feat(build): use lld for clang if available
Also simplify the CI.

closes https://github.com/official-stockfish/Stockfish/pull/6592

No functional change
2026-02-08 15:22:06 +01:00
mstemberaandDisservin 3cde1560f6 Minor fixes for MSVC
closes https://github.com/official-stockfish/Stockfish/pull/6596

No functional change
2026-02-08 15:22:06 +01:00
DisservinandTimothy Herchen bfeb36eb3d Fix Strict Aliasing Violation
Even though this failed with non regression bounds on fishtest, merging
it as is to improve the current situation.

Failed Non Regression:
https://tests.stockfishchess.org/tests/view/6988814db0f3ca5200aafb50
LLR: -2.94 (-2.94,2.94) <-1.75,0.25>
Total: 185792 W: 48109 L: 48565 D: 89118
Ptnml(0-2): 553, 21228, 49811, 20730, 574

Previously we had code similar to this
```
alignas(64) std::uint8_t foo[1024];
const auto* int_ptr = reinterpret_cast<const std::int32_t*>(foo);
```

Which is a strict aliasing violation once we dereference int_ptr, which
invokes UB and causes newer gcc version to emit incorrect code.

fixes https://github.com/official-stockfish/Stockfish/issues/6598
fixes https://github.com/official-stockfish/Stockfish/issues/4932

This violation was introduced in PR #3287 and merged in commit 23c385e.
Due to aggressive compiler optimizations, users may need to include
CXXFLAGS=-fno-strict-aliasing as a build argument to ensure a correctly
functioning binary.

Also thanks to @anematode for writing parts of this and realizing this
strict aliasing violation.

closes https://github.com/official-stockfish/Stockfish/pull/6599

Bench: 2668754

Co-Authored-By: Timothy Herchen <timothy.herchen@gmail.com>
2026-02-08 15:20:42 +01:00
sscg13andJoost VandeVondele fac506bdf3 Update NNUE architecture to SFNNv11 and net nn-3dd094f3dfcf.nnue
Removes threat features of the form piece -> king, thus saving 13MB of net
space and approximately 0.8 threat feature updates per incremental accumulator
update.

Passed non-regression STC:
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 33664 W: 8864 L: 8636 D: 16164
Ptnml(0-2): 136, 3926, 8501, 4112, 157
https://tests.stockfishchess.org/tests/view/6981dcda4776a4e6e7fef2ac

Passed non-regression LTC:
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 46896 W: 12077 L: 11881 D: 22938
Ptnml(0-2): 41, 5118, 12924, 5334, 31
https://tests.stockfishchess.org/tests/view/69827c6beb87369ff4d0c7d5

closes https://github.com/official-stockfish/Stockfish/pull/6593

bench: 2668754
2026-02-04 18:26:28 +01:00
Pieter te BrakeandJoost VandeVondele 24af6a6bc4 Update castling rights unconditionally.
passed STC:
LLR: 2.93 (-2.94,2.94) <-1.75,0.25>
Total: 163680 W: 42214 L: 42137 D: 79329
Ptnml(0-2): 454, 18054, 44734, 18157, 441
https://tests.stockfishchess.org/tests/view/697e6f4e5f56030af97b5a3c

closes https://github.com/official-stockfish/Stockfish/pull/6588

No functional change
2026-02-04 18:22:07 +01:00
Pieter te BrakeandJoost VandeVondele 2321cf2f77 Simplify en passant square update in Position::do_move().
Passed non-regression STC:

LLR: 2.96 (-2.94,2.94) <-1.75,0.25>
Total: 119680 W: 31011 L: 30884 D: 57785
Ptnml(0-2): 354, 13231, 32575, 13294, 386
https://tests.stockfishchess.org/tests/view/6973b06c6cd60a8e97ca62e5

closes https://github.com/official-stockfish/Stockfish/pull/6565

No functional change
2026-02-04 18:17:23 +01:00
KazAppsandJoost VandeVondele 9f42980dd2 refactor update_piece_threats to reduce branching
Passed STC Non-Regression:
https://tests.stockfishchess.org/tests/view/696f1a398b64097dacd231c3
LLR: 2.94 (-2.94,2.94) <-1.75,0.25>
Total: 122272 W: 31587 L: 31466 D: 59219
Ptnml(0-2): 301, 13358, 33750, 13373, 354

slight speedup as well:

1 thread bench:
sf_base =  2238429 +/-   1221 (95%)
sf_test =  2248298 +/-   1371 (95%)
diff    =     9869 +/-   1571 (95%)
speedup = 0.44090% +/- 0.070% (95%)

32 thread speedtest:
sf_base = 41016996 +/-  83654 (95%)
sf_test = 41185801 +/-  84269 (95%)
diff    =   168805 +/-  79986 (95%)
speedup = 0.41155% +/- 0.195% (95%)

closes https://github.com/official-stockfish/Stockfish/pull/6559

No functional change
2026-02-04 18:09:00 +01:00
disservinandJoost VandeVondele 9cc2985f51 Don't push prerelease for release commits
Looks for the text "Official release version of Stockfish" in the commit
message to determine if something is a release.

closes https://github.com/official-stockfish/Stockfish/pull/6580

No functional change
2026-02-04 18:00:54 +01:00
ppigazziniandJoost VandeVondele 3ee16a1d8e refactor(scripts): improve get_native_properties.sh
Improve `get_native_properties.sh` with a refreshed implementation.
The new version covers all Makefile ARCH variants and keeps the
script interface stable while improving portability and CPU feature
detection across supported platforms. Drop the asset file name to
avoid coupling outputs to a specific artifact naming scheme.

Refs: https://github.com/ppigazzini/get-native-properties (includes a
testing framework)

closes https://github.com/official-stockfish/Stockfish/pull/6552

No functional change.
2026-02-04 17:58:55 +01:00
rn5f107s2andJoost VandeVondele 7f0b5d10ee Only record the ep square from a given FEN if ep is legal
When given a FEN with an ep square set, only actually set the ep square if there is a legal ep capture that can be played.

Fixes #6563

closes https://github.com/official-stockfish/Stockfish/pull/6564

No functional change
2026-02-04 17:56:00 +01:00
disservinandJoost VandeVondele 9f968446cb Improve usage of sf_assume
Make the sf_assume also have an effect with clang and help the compiler
proof it's side effect free by comparing against a variable.

https://tests.stockfishchess.org/tests/view/6979d14fa2c75f923be1e681
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 63488 W: 16530 L: 16188 D: 30770
Ptnml(0-2): 167, 6904, 17277, 7212, 184

closes https://github.com/official-stockfish/Stockfish/pull/6573

No functional change
2026-02-04 17:46:47 +01:00
Maxim MasiutinandJoost VandeVondele 542c30c292 Branchless correction history with to_sq_unchecked
Add Move::to_sq_unchecked() that bypasses the is_ok() assertion,
for use in branchless code paths where invalid moves are masked out.

passed STC:
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 92384 W: 24052 L: 23665 D: 44667
Ptnml(0-2): 265, 10229, 24831, 10588, 279
https://tests.stockfishchess.org/tests/view/6974dfc798dc5fff1dba5b74

closes https://github.com/official-stockfish/Stockfish/pull/6569

No functional change
2026-02-04 17:44:25 +01:00
Dieter DobbelaereandJoost VandeVondele 7f85cfbfa2 Include threat weights in network hash calculation
closes https://github.com/official-stockfish/Stockfish/pull/6583

No functional change
2026-02-04 17:38:31 +01:00
DisservinandJoost VandeVondele 1018879fe1 Fix clang-format complaints
Somehow we forgot to add shm.h & shm_linux.h to the list of headers to be
formatted.

closes https://github.com/official-stockfish/Stockfish/pull/6591

No functional change
2026-02-04 17:34:11 +01:00
anematodeandJoost VandeVondele 0d26c61ff2 fix return type of vmovl_high_s8
also add -flax-vector-conversions=none to the build

closes https://github.com/official-stockfish/Stockfish/pull/6587

No functional change
2026-02-04 17:32:48 +01:00
DisservinandJoost VandeVondele 823b04e58a Restore development
closes https://github.com/official-stockfish/Stockfish/pull/6581

No functional change
2026-02-04 17:29:42 +01:00
99 changed files with 8437 additions and 5031 deletions
+5 -1
View File
@@ -4,4 +4,8 @@
# Post commit formatting fixes
0fca5605fa2e5e7240fde5e1aae50952b2612231
08ed4c90db31959521b7ef3186c026edd1e90307
08ed4c90db31959521b7ef3186c026edd1e90307
# Ignore commits related to integer type renamings
dd3e1c4a50fd358c8bf8051c9ef73219669ff6c0
6e4e03fd282d5e1cba514197bf1ef326c990f0d2
+6
View File
@@ -1,6 +1,12 @@
name: Report issue
description: Create a report to help us fix issues with the engine
body:
- type: markdown
attributes:
value: |
> [!IMPORTANT]
> Please do not open bug reports regarding the engine's choice of moves, such as the engine failing to find a particular "best" move. If you have any questions or wish to discuss engine analysis, please use our community channels, such as Discord.
- type: textarea
attributes:
label: Describe the issue
+7 -16
View File
@@ -8,7 +8,7 @@
"emu": "qemu-aarch64",
"comp": "ndk",
"shell": "bash",
"archive_ext": "tar"
"archive_ext": "tar.gz"
},
{
"name": "Android NDK arm",
@@ -18,29 +18,20 @@
"emu": "qemu-arm",
"comp": "ndk",
"shell": "bash",
"archive_ext": "tar"
"archive_ext": "tar.gz"
}
],
"binaries": ["armv8-dotprod", "armv8", "armv7", "armv7-neon"],
"binaries": [
"arm64-universal",
"armv7-neon"
],
"exclude": [
{
"binaries": "armv8-dotprod",
"binaries": "arm64-universal",
"config": {
"compiler": "armv7a-linux-androideabi29-clang++"
}
},
{
"binaries": "armv8",
"config": {
"compiler": "armv7a-linux-androideabi29-clang++"
}
},
{
"binaries": "armv7",
"config": {
"compiler": "aarch64-linux-android29-clang++"
}
},
{
"binaries": "armv7-neon",
"config": {
-282
View File
@@ -1,282 +0,0 @@
{
"config": [
{
"name": "Ubuntu 22.04 GCC",
"os": "ubuntu-22.04",
"simple_name": "ubuntu",
"compiler": "g++",
"comp": "gcc",
"shell": "bash",
"archive_ext": "tar",
"sde": "/home/runner/work/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-lin/sde -future --"
},
{
"name": "macOS 15 Apple Clang",
"os": "macos-15-intel",
"simple_name": "macos",
"compiler": "clang++",
"comp": "clang",
"shell": "bash",
"archive_ext": "tar"
},
{
"name": "macOS 15 Apple Clang M1",
"os": "macos-15",
"simple_name": "macos-m1",
"compiler": "clang++",
"comp": "clang",
"shell": "bash",
"archive_ext": "tar"
},
{
"name": "Windows 2022 Mingw-w64 GCC x86_64",
"os": "windows-2022",
"simple_name": "windows",
"compiler": "g++",
"comp": "mingw",
"msys_sys": "mingw64",
"msys_env": "x86_64-gcc",
"shell": "msys2 {0}",
"ext": ".exe",
"sde": "/d/a/Stockfish/Stockfish/.output/sde-temp-files/sde-external-9.33.0-2024-01-07-win/sde.exe -future --",
"archive_ext": "zip"
},
{
"name": "Windows 11 Mingw-w64 Clang arm64",
"os": "windows-11-arm",
"simple_name": "windows",
"compiler": "clang++",
"comp": "clang",
"msys_sys": "clangarm64",
"msys_env": "clang-aarch64-clang",
"shell": "msys2 {0}",
"ext": ".exe",
"archive_ext": "zip"
}
],
"binaries": [
"x86-64",
"x86-64-sse41-popcnt",
"x86-64-avx2",
"x86-64-bmi2",
"x86-64-avxvnni",
"x86-64-avx512",
"x86-64-vnni512",
"x86-64-avx512icl",
"apple-silicon",
"armv8",
"armv8-dotprod"
],
"exclude": [
{
"binaries": "x86-64",
"config": {
"os": "macos-15"
}
},
{
"binaries": "x86-64-sse41-popcnt",
"config": {
"os": "macos-15"
}
},
{
"binaries": "x86-64-avx2",
"config": {
"os": "macos-15"
}
},
{
"binaries": "x86-64-bmi2",
"config": {
"os": "macos-15"
}
},
{
"binaries": "x86-64-avxvnni",
"config": {
"os": "macos-15"
}
},
{
"binaries": "x86-64-avx512",
"config": {
"os": "macos-15"
}
},
{
"binaries": "x86-64-vnni512",
"config": {
"os": "macos-15"
}
},
{
"binaries": "x86-64-avx512icl",
"config": {
"os": "macos-15"
}
},
{
"binaries": "x86-64-avxvnni",
"config": {
"os": "macos-15-intel"
}
},
{
"binaries": "x86-64-avx512",
"config": {
"os": "macos-15-intel"
}
},
{
"binaries": "x86-64-vnni512",
"config": {
"os": "macos-15-intel"
}
},
{
"binaries": "x86-64-avx512icl",
"config": {
"os": "macos-15-intel"
}
},
{
"binaries": "x86-64",
"config": {
"os": "windows-11-arm"
}
},
{
"binaries": "x86-64-sse41-popcnt",
"config": {
"os": "windows-11-arm"
}
},
{
"binaries": "x86-64-avx2",
"config": {
"os": "windows-11-arm"
}
},
{
"binaries": "x86-64-bmi2",
"config": {
"os": "windows-11-arm"
}
},
{
"binaries": "x86-64-avxvnni",
"config": {
"os": "windows-11-arm"
}
},
{
"binaries": "x86-64-avx512",
"config": {
"os": "windows-11-arm"
}
},
{
"binaries": "x86-64-vnni512",
"config": {
"os": "windows-11-arm"
}
},
{
"binaries": "x86-64-avx512icl",
"config": {
"os": "windows-11-arm"
}
},
{
"binaries": "apple-silicon",
"config": {
"os": "windows-2022"
}
},
{
"binaries": "apple-silicon",
"config": {
"os": "windows-11-arm"
}
},
{
"binaries": "apple-silicon",
"config": {
"os": "ubuntu-20.04"
}
},
{
"binaries": "apple-silicon",
"config": {
"os": "ubuntu-22.04"
}
},
{
"binaries": "apple-silicon",
"config": {
"os": "macos-15-intel"
}
},
{
"binaries": "armv8",
"config": {
"os": "windows-2022"
}
},
{
"binaries": "armv8",
"config": {
"os": "ubuntu-20.04"
}
},
{
"binaries": "armv8",
"config": {
"os": "ubuntu-22.04"
}
},
{
"binaries": "armv8",
"config": {
"os": "macos-15-intel"
}
},
{
"binaries": "armv8",
"config": {
"os": "macos-15"
}
},
{
"binaries": "armv8-dotprod",
"config": {
"os": "windows-2022"
}
},
{
"binaries": "armv8-dotprod",
"config": {
"os": "ubuntu-20.04"
}
},
{
"binaries": "armv8-dotprod",
"config": {
"os": "ubuntu-22.04"
}
},
{
"binaries": "armv8-dotprod",
"config": {
"os": "macos-15-intel"
}
},
{
"binaries": "armv8-dotprod",
"config": {
"os": "macos-15"
}
}
]
}
+60
View File
@@ -0,0 +1,60 @@
{
"include": [
{
"config": {
"name": "Ubuntu 22.04 GCC Universal",
"os": "ubuntu-22.04",
"simple_name": "linux",
"archive_ext": "tar.gz"
},
"binaries": "x86-64-universal"
},
{
"config": {
"name": "Windows 2022 Mingw-w64 GCC Universal",
"os": "windows-2022",
"simple_name": "windows",
"ext": ".exe",
"archive_ext": "zip"
},
"binaries": "x86-64-universal"
},
{
"config": {
"name": "Ubuntu 22.04 GCC Universal arm64",
"os": "ubuntu-22.04-arm",
"simple_name": "linux",
"archive_ext": "tar.gz"
},
"binaries": "arm64-universal"
},
{
"config": {
"name": "Ubuntu 22.04 GCC Universal riscv64",
"os": "ubuntu-22.04",
"simple_name": "linux",
"archive_ext": "tar.gz"
},
"binaries": "riscv64-universal"
},
{
"config": {
"name": "Windows 11 Clang Universal arm64",
"os": "windows-11-arm",
"simple_name": "windows",
"ext": ".exe",
"archive_ext": "zip"
},
"binaries": "arm64-universal"
},
{
"config": {
"name": "macOS 15 Apple Clang Universal",
"os": "macos-15",
"simple_name": "macos",
"archive_ext": "tar.gz"
},
"binaries": "universal"
}
]
}
+5 -5
View File
@@ -1,4 +1,4 @@
name: Compilation
name: Android Compilation
on:
workflow_call:
inputs:
@@ -7,7 +7,7 @@ on:
required: true
jobs:
Compilation:
name: ${{ matrix.config.name }} ${{ matrix.binaries }}
name: "Android build: ${{ matrix.config.name }} ${{ matrix.binaries }}"
runs-on: ${{ matrix.config.os }}
env:
COMPCXX: ${{ matrix.config.compiler }}
@@ -23,7 +23,7 @@ jobs:
working-directory: src
shell: ${{ matrix.config.shell }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
@@ -86,10 +86,10 @@ jobs:
mv ./stockfish$EXT ../stockfish-android-$BINARY$EXT
- name: Remove non src files
run: git clean -fx
run: git clean -fxd
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
path: |
+1 -3
View File
@@ -22,7 +22,6 @@ jobs:
- { name: gcc-15, comp: gcc, cxx: g++, image: "gcc:15" }
# Using silkeh/clang for older versions
- { name: clang-10, comp: clang, cxx: clang++, image: "silkeh/clang:10", is_clang: true, ver: "10" }
- { name: clang-11, comp: clang, cxx: clang++, image: "silkeh/clang:11", is_clang: true, ver: "11" }
- { name: clang-12, comp: clang, cxx: clang++, image: "silkeh/clang:12", is_clang: true, ver: "12" }
- { name: clang-13, comp: clang, cxx: clang++, image: "silkeh/clang:13", is_clang: true, ver: "13" }
@@ -38,7 +37,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install dependencies
run: |
@@ -74,7 +73,6 @@ jobs:
run: |
export CXXFLAGS="-Werror"
if [ "${{ matrix.ver }}" -ge 20 ]; then
export LDFLAGS="-fuse-ld=lld"
apt install -y lld
fi
make clean
+15 -10
View File
@@ -12,6 +12,10 @@ on:
- "**.cpp"
- "**.h"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
pull-requests: write
@@ -20,12 +24,12 @@ jobs:
name: Clang-Format
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Run clang-format style check
uses: jidicula/clang-format-action@4726374d1aa3c6aecf132e5197e498979588ebc8 # @v4.15.0
uses: jidicula/clang-format-action@654a770daa28443dd111d133e4083e21c1075674 # v4.18.0
id: clang-format
continue-on-error: true
with:
@@ -34,24 +38,25 @@ jobs:
- name: Comment on PR
if: steps.clang-format.outcome == 'failure'
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
with:
message: |
clang-format 20 needs to be run on this PR.
If you do not have clang-format installed, the maintainer will run it when merging.
For the exact version please see https://packages.ubuntu.com/plucky/clang-format-20.
For the exact version please see https://packages.ubuntu.com/questing/clang-format-20.
An easier way to install it might be through https://apt.llvm.org/#llvmsh.
_(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
comment_tag: execution
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
comment-tag: execution
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Comment on PR
if: steps.clang-format.outcome != 'failure'
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # @v2.5.0
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
with:
message: |
_(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
create_if_not_exists: false
comment_tag: execution
create-if-not-exists: false
comment-tag: execution
mode: delete
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
+7 -3
View File
@@ -9,6 +9,10 @@ on:
schedule:
- cron: "17 18 * * 1"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
analyze:
name: Analyze
@@ -29,13 +33,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -50,6 +54,6 @@ jobs:
run: make -j build
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
with:
category: "/language:${{matrix.language}}"
-94
View File
@@ -1,94 +0,0 @@
name: Compilation
on:
workflow_call:
inputs:
matrix:
type: string
required: true
jobs:
Compilation:
name: ${{ matrix.config.name }} ${{ matrix.binaries }}
runs-on: ${{ matrix.config.os }}
env:
COMPCXX: ${{ matrix.config.compiler }}
COMP: ${{ matrix.config.comp }}
EXT: ${{ matrix.config.ext }}
NAME: ${{ matrix.config.simple_name }}
BINARY: ${{ matrix.binaries }}
SDE: ${{ matrix.config.sde }}
strategy:
fail-fast: false
matrix: ${{ fromJson(inputs.matrix) }}
defaults:
run:
working-directory: src
shell: ${{ matrix.config.shell }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Install fixed GCC on Linux
if: runner.os == 'Linux'
uses: egor-tensin/setup-gcc@eaa888eb19115a521fa72b65cd94fe1f25bbcaac # @v1.3
with:
version: 11
- name: Setup msys and install required packages
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.config.msys_sys }}
install: mingw-w64-${{ matrix.config.msys_env }} make git zip
- name: Download SDE package
if: runner.os == 'Linux' || runner.os == 'Windows'
uses: petarpetrovt/setup-sde@f0fa5971dc275704531e94264dd23250c442aa41 # @v2.4
with:
environmentVariableName: SDE_DIR
sdeVersion: 9.33.0
- name: Download the used network from the fishtest framework
run: make net
- name: Check compiler
run: $COMPCXX -v
- name: Test help target
run: make help
- name: Check git
run: git --version
- name: Check compiler
run: $COMPCXX -v
- name: Show compiler cpu info
run: |
if [[ "$COMPCXX" == clang* ]]; then
$COMPCXX -E - -march=native -###
else
$COMPCXX -Q -march=native --help=target
fi
# x86-64 with newer extensions tests
- name: Compile ${{ matrix.config.binaries }} build
run: |
make clean
make -j4 profile-build ARCH=$BINARY COMP=$COMP RUN_PREFIX="$SDE"
make strip ARCH=$BINARY COMP=$COMP
RUN_PREFIX="$SDE" ../tests/signature.sh $benchref
mv ./stockfish$EXT ../stockfish-$NAME-$BINARY$EXT
- name: Remove non src files
run: git clean -fx
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
path: |
.
!.git
!.output
+2 -2
View File
@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Checkout SF repo
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
path: Stockfish
@@ -20,7 +20,7 @@ jobs:
run: make -j build debug=yes
- name: Checkout fastchess repo
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: Disservin/fastchess
path: fastchess
+2 -2
View File
@@ -11,13 +11,13 @@ jobs:
shell: bash
steps:
- name: Checkout Stockfish
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: Stockfish
persist-credentials: false
- name: Checkout include-what-you-use
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: include-what-you-use/include-what-you-use
ref: f25caa280dc3277c4086ec345ad279a2463fea0f
+48 -8
View File
@@ -7,9 +7,12 @@ jobs:
Matetrack:
name: Matetrack
runs-on: ubuntu-22.04
defaults:
run:
shell: bash
steps:
- name: Checkout SF repo
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
path: Stockfish
@@ -20,11 +23,11 @@ jobs:
run: make -j profile-build
- name: Checkout matetrack repo
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: vondele/matetrack
path: matetrack
ref: 2d96fa3373f90edb032b7ea7468473fb9e6f0343
ref: 1e7f6c6fff8e2f23923880a94fae596281346395
persist-credentials: false
- name: matetrack install deps
@@ -33,7 +36,7 @@ jobs:
- name: cache syzygy
id: cache-syzygy
uses: actions/cache@v4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
matetrack/3-4-5-wdl/
@@ -47,17 +50,54 @@ jobs:
wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-wdl/
wget --no-verbose -r -nH --cut-dirs=2 --no-parent --reject="index.html*" -e robots=off https://tablebase.lichess.ovh/tables/standard/3-4-5-dtz/
# matecheck/python chess is not good at handling crashing engines. Do a basic check first.
- name: Test engine functionality
working-directory: Stockfish/src
run: |
python3 ../tests/instrumented.py --none ./stockfish
- name: Run matetrack th1
working-directory: matetrack
run: |
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheckout1.out
! grep "issues were detected" matecheckout1.out > /dev/null
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 | tee matecheck1.out
! grep "issues were detected" matecheck1.out > /dev/null
- name: Run matetrack th4
working-directory: matetrack
run: |
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 --threads 4 | tee matecheckout4.out
! grep "issues were detected" matecheckout4.out > /dev/null
python matecheck.py --syzygyPath 3-4-5-wdl/:3-4-5-dtz/ --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --nodes 100000 --threads 4 | tee matecheck4.out
! grep "issues were detected" matecheck4.out > /dev/null
- name: Run matetrack th4 gameplay
working-directory: matetrack
run: |
python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile mates2000.epd --time 3 --timeinc 0.01 --threads 4 | tee matecheck4g.out
! grep "issues were detected" matecheck4g.out > /dev/null
- name: Run matetrack th4 go-mate
working-directory: matetrack
run: |
python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile matetrack.epd matedtrack.epd --bmMax 2 --mate 0 --threads 4 | tee matecheck4gm.out
! grep "issues were detected" matecheck4gm.out > /dev/null
total=$(grep "Total FENs:" matecheck4gm.out | awk '{print $3}')
bmates=$(grep "Best mates:" matecheck4gm.out | awk '{print $3}')
if [ $bmates -ne $total ]; then
echo "At least one go-mate search did not yield expected mate, see matecheck4gm.out" >&2
exit 1
fi
- name: Run matetrack th1 multiPV
working-directory: matetrack
run: |
python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile matetrack.epd matedtrack.epd --bmMax 10 --nodes 5000 --multiPV 20 --checkMultiPVs | tee matecheck1mpv.out
! grep "issues were detected" matecheck1mpv.out > /dev/null
- name: Run matetrack th4 multiPV
working-directory: matetrack
run: |
python matecheck.py --engine /home/runner/work/Stockfish/Stockfish/Stockfish/src/stockfish --epdFile matetrack.epd matedtrack.epd --bmMax 10 --nodes 5000 --threads 4 --multiPV 20 --checkMultiPVs | tee matecheck4mpv.out
! grep "issues were detected" matecheck4mpv.out > /dev/null
- name: Run matetrack th1 with --syzygy50MoveRule false
working-directory: matetrack
+1 -1
View File
@@ -48,7 +48,7 @@ jobs:
working-directory: src
shell: ${{ matrix.config.shell }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+106 -41
View File
@@ -11,16 +11,24 @@ on:
branches:
- master
- tools
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
Prerelease:
name: Prerelease
if: github.repository == 'official-stockfish/Stockfish' && (github.ref == 'refs/heads/master' || (startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'))
runs-on: ubuntu-latest
needs: [Matrix, ARMCompilation, UniversalCompilation]
permissions:
contents: write # For deleting/creating a prerelease
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0
# returns null if no pre-release exists
- name: Get Commit SHA of Latest Pre-release
@@ -53,66 +61,114 @@ jobs:
id: commit_date
run: echo "COMMIT_DATE=$(git show -s --date=format:'%Y%m%d' --format=%cd HEAD)" >> $GITHUB_ENV
- name: Official Release?
id: official_release
# Check for "Official release version of Stockfish" in the commit message
run: |
if git log -1 --pretty=%B | grep -q "Official release version of Stockfish"; then
echo "OFFICIAL_RELEASE=true" >> $GITHUB_ENV
else
echo "OFFICIAL_RELEASE=false" >> $GITHUB_ENV
fi
# Get recent commits for release body
- name: Get commits in this push
id: recent_commits
run: |
COMMITS=$(git log \
--format="- [\`%h\`](https://github.com/${{ github.repository }}/commit/%H) *%s*" \
${{ github.event.before }}..HEAD)
echo "COMMITS<<EOF" >> $GITHUB_OUTPUT
echo "$COMMITS" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Create a new pre-release, the other upload_binaries.yml will upload the binaries
# to this pre-release.
- name: Create Prerelease
if: github.ref_name == 'master' && env.CHANGES == '0'
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
if: github.ref_name == 'master' && env.CHANGES == '0' && env.OFFICIAL_RELEASE == 'false'
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
prerelease: true
body: |
Precompiled binaries of the latest Stockfish development version, built automatically on every commit.
> [!NOTE]
> For stable, thoroughly tested builds use the [official releases](https://github.com/official-stockfish/Stockfish/releases).
> Pre-releases may contain bugs.
*Some platforms ship a **universal binary** that automatically selects the best
instruction set for your CPU - no manual selection needed.*
---
## Commits
${{ steps.recent_commits.outputs.COMMITS }}
[View all commits →](https://github.com/${{ github.repository }}/commits/master)
Matrix:
name: Prepare matrices
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
arm_matrix: ${{ steps.set-arm-matrix.outputs.arm_matrix }}
universal_matrix: ${{ steps.set-universal-matrix.outputs.universal_matrix }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- id: set-matrix
run: |
TASKS=$(echo $(cat .github/ci/matrix.json) )
echo "MATRIX=$TASKS" >> $GITHUB_OUTPUT
- id: set-arm-matrix
run: |
TASKS_ARM=$(echo $(cat .github/ci/arm_matrix.json) )
echo "ARM_MATRIX=$TASKS_ARM" >> $GITHUB_OUTPUT
Compilation:
needs: [Matrix]
uses: ./.github/workflows/compilation.yml
with:
matrix: ${{ needs.Matrix.outputs.matrix }}
ARM_MATRIX=$(jq -c '.' .github/ci/arm_matrix.json)
echo "ARM_MATRIX=$ARM_MATRIX" >> $GITHUB_OUTPUT
- id: set-universal-matrix
run: |
UNIVERSAL_MATRIX=$(jq -c '.' .github/ci/universal_matrix.json)
echo "UNIVERSAL_MATRIX=$UNIVERSAL_MATRIX" >> $GITHUB_OUTPUT
# Testing Jobs
IWYU:
name: IWYU
uses: ./.github/workflows/iwyu.yml
Sanitizers:
name: Sanitizers
if: ${{ always() }}
uses: ./.github/workflows/sanitizers.yml
Tests:
name: Tests
if: ${{ always() }}
uses: ./.github/workflows/tests.yml
Matetrack:
name: Matetrack
if: ${{ always() }}
uses: ./.github/workflows/matetrack.yml
Games:
name: Games
if: ${{ always() }}
uses: ./.github/workflows/games.yml
CompilerCheck:
name: Compiler check
if: ${{ always() }}
uses: ./.github/workflows/avx2_compilers.yml
WASMCompilation:
name: WASM compilation
if: ${{ always() }}
uses: ./.github/workflows/wasm_compilation.yml
# Release Jobs
ARMCompilation:
needs: [Matrix]
name: Android builds
needs: [Matrix, Sanitizers, Tests, Matetrack, Games, CompilerCheck]
uses: ./.github/workflows/arm_compilation.yml
with:
matrix: ${{ needs.Matrix.outputs.arm_matrix }}
IWYU:
uses: ./.github/workflows/iwyu.yml
Sanitizers:
uses: ./.github/workflows/sanitizers.yml
Tests:
uses: ./.github/workflows/tests.yml
Matetrack:
uses: ./.github/workflows/matetrack.yml
Games:
uses: ./.github/workflows/games.yml
Binaries:
if: github.repository == 'official-stockfish/Stockfish'
needs: [Matrix, Prerelease, Compilation]
uses: ./.github/workflows/upload_binaries.yml
with:
matrix: ${{ needs.Matrix.outputs.matrix }}
permissions:
contents: write # For deleting/creating a (pre)release
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
UniversalCompilation:
name: "Universal builds"
needs: [Matrix, Sanitizers, Tests, Matetrack, Games, CompilerCheck]
uses: ./.github/workflows/universal_compilation.yml
ARM_Binaries:
if: github.repository == 'official-stockfish/Stockfish'
needs: [Matrix, Prerelease, ARMCompilation]
name: Android uploads
needs: [Prerelease, Matrix]
uses: ./.github/workflows/upload_binaries.yml
with:
matrix: ${{ needs.Matrix.outputs.arm_matrix }}
@@ -120,5 +176,14 @@ jobs:
contents: write # For deleting/creating a (pre)release
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
CompilerCheck:
uses: ./.github/workflows/avx2_compilers.yml
Universal_Binaries:
if: github.repository == 'official-stockfish/Stockfish'
name: Universal binaries
needs: [Prerelease, Matrix]
uses: ./.github/workflows/upload_binaries.yml
with:
matrix: ${{ needs.Matrix.outputs.universal_matrix }}
permissions:
contents: write # For deleting/creating a (pre)release
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
+49 -23
View File
@@ -39,15 +39,6 @@ jobs:
comp: ndk
run_armv7_tests: true
shell: bash
# Currently segfaults in the CI unrelated to a Stockfish change.
# - name: Linux GCC riscv64
# os: ubuntu-22.04
# compiler: g++
# comp: gcc
# run_riscv64_tests: true
# base_image: "riscv64/alpine:edge"
# platform: linux/riscv64
# shell: bash
- name: Linux GCC ppc64
os: ubuntu-22.04
compiler: g++
@@ -56,6 +47,14 @@ jobs:
base_image: "ppc64le/alpine:latest"
platform: linux/ppc64le
shell: bash
- name: Linux GCC loongarch64
os: ubuntu-22.04
compiler: g++
comp: gcc
run_loongarch64_tests: true
base_image: "loongarch64/alpine:3.21"
platform: linux/loong64
shell: bash
- name: macOS 15 Apple Clang
os: macos-15-intel
compiler: clang++
@@ -112,7 +111,7 @@ jobs:
working-directory: src
shell: ${{ matrix.config.shell }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
@@ -139,11 +138,11 @@ jobs:
- name: Set up QEMU
if: matrix.config.base_image
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
- name: Set up Docker Buildx
if: matrix.config.base_image
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build Docker container
if: matrix.config.base_image
@@ -157,11 +156,17 @@ jobs:
- name: Download required macOS packages
if: runner.os == 'macOS'
run: brew install coreutils gcc@11
run: brew install coreutils
- name: Install GCC on macOS
if: runner.os == 'macOS' && matrix.config.comp == 'gcc'
run: |
brew install gcc@11 || true
g++-11 --version
- name: Setup msys and install required packages
if: runner.os == 'Windows'
uses: msys2/setup-msys2@v2
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1
with:
msystem: ${{ matrix.config.msys_sys }}
install: mingw-w64-${{ matrix.config.msys_env }} make git expect
@@ -306,6 +311,13 @@ jobs:
make -j4 ARCH=apple-silicon build
../tests/signature.sh $benchref
- name: Test apple-silicon build with system g++ (macOS g++ == clang++)
if: matrix.config.run_m1_tests
run: |
make clean
env -u COMPCXX make -j4 ARCH=apple-silicon build COMP=gcc
../tests/signature.sh $benchref
# armv8 tests
- name: Test armv8 build
@@ -350,15 +362,6 @@ jobs:
make -j4 ARCH=armv7-neon build
../tests/signature.sh $benchref
# riscv64 tests
- name: Test riscv64 build
if: matrix.config.run_riscv64_tests
run: |
echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=riscv64 build" > script.sh
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
../tests/signature.sh $benchref
# ppc64 tests
- name: Test ppc64 build
@@ -368,6 +371,29 @@ jobs:
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
../tests/signature.sh $benchref
# loongarch64 tests
- name: Test loongarch64 build
if: matrix.config.run_loongarch64_tests
run: |
echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=loongarch64 build" > script.sh
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
../tests/signature.sh $benchref
- name: Test loongarch64-lsx build
if: matrix.config.run_loongarch64_tests
run: |
echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=loongarch64-lsx build" > script.sh
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
../tests/signature.sh $benchref
- name: Test loongarch64-lasx build
if: matrix.config.run_loongarch64_tests
run: |
echo "cd src && export LDFLAGS='-static' && make clean && make -j4 ARCH=loongarch64-lasx build" > script.sh
docker run --rm --platform ${{ matrix.config.platform }} -v ${{ github.workspace }}:/app sf_builder
../tests/signature.sh $benchref
# Other tests
- name: Check perft and search reproducibility
+318
View File
@@ -0,0 +1,318 @@
name: Universal Compilation
on:
workflow_call:
jobs:
UniversalLinux:
name: Linux
runs-on: ubuntu-22.04
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0
- name: Install GCC 15
uses: egor-tensin/setup-gcc@a2861a8b8538f49cf2850980acccf6b05a1b2ae4 # v2.0
with:
version: 15
- name: Download SDE package
uses: petarpetrovt/setup-sde@31aa4a8e85e109bef00f1d838613fcc6ec421271 # v5.0
with:
environmentVariableName: SDE_DIR
sdeVersion: 9.58.0
- name: Build universal binary
run: make -j4 -C src ARCH=x86-64-universal profile-build RUN_PREFIX="$SDE_DIR/sde -future --"
- name: Strip binary
run: strip src/stockfish
- name: Rename binary
run: mv src/stockfish stockfish-linux-x86-64-universal
- name: Extract the bench number from the commit history
run: |
for hash in $(git rev-list -100 HEAD); do
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
done
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
- name: Check arch selection under SDE
run: ./scripts/check_universal.sh ./stockfish-linux-x86-64-universal "$SDE_DIR/sde" "$benchref"
- name: Remove non-src files
run: git clean -fxd -- src
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux x86-64-universal
path: |
.
!.git
!.output
UniversalWindows:
name: Windows
runs-on: windows-2022
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0
- name: Setup msys and install required packages
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1
with:
msystem: mingw64
install: mingw-w64-x86_64-gcc make git zip
- name: Download SDE package
uses: petarpetrovt/setup-sde@31aa4a8e85e109bef00f1d838613fcc6ec421271 # v5.0
with:
environmentVariableName: SDE_DIR
sdeVersion: 9.58.0
- name: Build universal binary
run: |
SDE_PATH=$(cygpath "$SDE_DIR")
make -j4 -C src ARCH=x86-64-universal profile-build RUN_PREFIX="$SDE_PATH/sde.exe -future --"
- name: Strip binary
run: strip src/stockfish.exe
- name: Rename binary
run: mv src/stockfish.exe stockfish-windows-x86-64-universal.exe
- name: Extract the bench number from the commit history
run: |
for hash in $(git rev-list -100 HEAD); do
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
done
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
- name: Check arch selection under SDE
run: ./scripts/check_universal.sh ./stockfish-windows-x86-64-universal.exe "$SDE_DIR/sde" "$benchref"
- name: Remove non-src files
run: git clean -fxd -- src
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows x86-64-universal
path: |
.
!.git
!.output
UniversalLinuxARM64:
name: Linux arm64
runs-on: ubuntu-22.04-arm
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0
- name: Install GCC 15
uses: egor-tensin/setup-gcc@a2861a8b8538f49cf2850980acccf6b05a1b2ae4 # v2.0
with:
version: 15
- name: Install qemu-user
run: |
sudo apt-get update
sudo apt-get install -y qemu-user
- name: Build universal binary
run: make -j4 -C src ARCH=arm64-universal profile-build
- name: Strip binary
run: strip src/stockfish
- name: Rename binary
run: mv src/stockfish stockfish-linux-arm64-universal
- name: Extract the bench number from the commit history
run: |
for hash in $(git rev-list -100 HEAD); do
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
done
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
- name: Check arch selection under qemu
run: ./scripts/check_universal_arm.sh ./stockfish-linux-arm64-universal "$benchref"
- name: Remove non-src files
run: git clean -fxd -- src
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux arm64-universal
path: |
.
!.git
!.output
UniversalWindowsARM64:
name: Windows arm64
runs-on: windows-11-arm
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0
- name: Setup msys and install required packages
uses: msys2/setup-msys2@e9898307ac31d1a803454791be09ab9973336e1c # v2.31.1
with:
msystem: clangarm64
install: mingw-w64-clang-aarch64-clang make git zip
- name: Build universal binary
run: make -j4 -C src ARCH=arm64-universal profile-build COMP=clang COMPCXX=clang++
- name: Strip binary
run: strip src/stockfish.exe
- name: Extract the bench number from the commit history
run: |
for hash in $(git rev-list -100 HEAD); do
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
done
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
- name: Verify bench and compiler
working-directory: src
run: |
../tests/signature.sh $benchref
./stockfish compiler 2>&1 | grep -q DOTPROD || { echo "compiler output missing DOTPROD" >&2; exit 1; }
- name: Rename binary
run: mv src/stockfish.exe stockfish-windows-arm64-universal.exe
- name: Remove non-src files
run: git clean -fxd -- src
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows arm64-universal
path: |
.
!.git
!.output
UniversalMacOS:
name: macOS
runs-on: macos-15
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0
# macos-15 is Apple silicon; Rosetta 2 is needed to exercise the x86-64 slice
- name: Install Rosetta 2
run: softwareupdate --install-rosetta --agree-to-license
- name: Build universal binary
run: make -j4 -C src macos-lipo COMP=clang
- name: Rename binary
run: mv src/stockfish stockfish-macos-universal
- name: Verify binary runs natively and under Rosetta
run: ./scripts/check_universal_macos.sh ./stockfish-macos-universal
- name: Remove non-src files
run: git clean -fxd -- src
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: macos universal
path: |
.
!.git
!.output
UniversalLinuxRISCV64:
name: Linux riscv64
runs-on: ubuntu-22.04
defaults:
run:
shell: bash
env:
QEMU_DEB: https://launchpad.net/ubuntu/+archive/primary/+files/qemu-user_9.2.1+ds-1ubuntu5_amd64.deb
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
fetch-depth: 0
- name: Install clang and the riscv cross runtime
run: |
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc >/dev/null
echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-20 main" | sudo tee /etc/apt/sources.list.d/llvm.list >/dev/null
sudo apt-get update
sudo apt-get install -y clang-20 lld-20 llvm-20 g++-riscv64-linux-gnu
echo "/usr/lib/llvm-20/bin" >> "$GITHUB_PATH"
- name: Install qemu
run: |
curl -sL -o /tmp/qemu.deb "$QEMU_DEB"
dpkg -x /tmp/qemu.deb /tmp/qemu
/tmp/qemu/usr/bin/qemu-riscv64 --version
echo "/tmp/qemu/usr/bin" >> "$GITHUB_PATH"
- name: Build universal binary
run: |
make -j4 -C src ARCH=riscv64-universal build \
COMP=clang COMPCXX=clang++ \
EXTRACXXFLAGS="--target=riscv64-linux-gnu" \
EXTRALDFLAGS="--target=riscv64-linux-gnu -fuse-ld=lld" \
OBJCOPY=llvm-objcopy
- name: Strip binary
run: llvm-strip src/stockfish
- name: Rename binary
run: mv src/stockfish stockfish-linux-riscv64-universal
- name: Extract the bench number from the commit history
run: |
for hash in $(git rev-list -100 HEAD); do
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
done
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
- name: Check arch selection under qemu
run: |
QEMU=qemu-riscv64 QEMU_LD_PREFIX=/usr/riscv64-linux-gnu \
./scripts/check_universal_riscv.sh ./stockfish-linux-riscv64-universal "$benchref"
- name: Remove non-src files
run: git clean -fxd -- src
- name: Upload artifact for (pre)-release
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux riscv64-universal
path: |
.
!.git
!.output
+18 -8
View File
@@ -24,12 +24,12 @@ jobs:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download artifact from compilation
uses: actions/download-artifact@v4
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
path: ${{ matrix.config.simple_name }} ${{ matrix.binaries }}
@@ -59,19 +59,19 @@ jobs:
cp CONTRIBUTING.md ../stockfish/
- name: Create tar
if: ${{ !startsWith(matrix.config.os, 'windows') }}
if: ${{ startsWith(matrix.config.archive_ext, 'tar') }}
run: |
chmod +x ./stockfish/stockfish-$NAME-$BINARY$EXT
tar -cvf stockfish-$NAME-$BINARY.tar stockfish
tar -cavf stockfish-$NAME-$BINARY."${{ matrix.config.archive_ext }}" stockfish
- name: Create zip
if: ${{ startsWith(matrix.config.os, 'windows') }}
if: ${{ matrix.config.archive_ext == 'zip' }}
run: |
zip -r stockfish-$NAME-$BINARY.zip stockfish
- name: Release
if: startsWith(github.ref_name, 'sf_') && github.ref_type == 'tag'
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
files: stockfish-${{ matrix.config.simple_name }}-${{ matrix.binaries }}.${{ matrix.config.archive_ext }}
token: ${{ secrets.token }}
@@ -92,10 +92,20 @@ jobs:
CHANGES=$(git rev-list HEAD..origin/master --count)
echo "CHANGES=$CHANGES" >> $GITHUB_ENV
- name: Official Release?
id: official_release
# Check for "Official release version of Stockfish" in the commit message
run: |
if git log -1 --pretty=%B | grep -q "Official release version of Stockfish"; then
echo "OFFICIAL_RELEASE=true" >> $GITHUB_ENV
else
echo "OFFICIAL_RELEASE=false" >> $GITHUB_ENV
fi
- name: Prerelease
if: github.ref_name == 'master' && env.CHANGES == '0'
if: github.ref_name == 'master' && env.CHANGES == '0' && env.OFFICIAL_RELEASE == 'false'
continue-on-error: true
uses: softprops/action-gh-release@4634c16e79c963813287e889244c50009e7f0981
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
name: Stockfish dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
tag_name: stockfish-dev-${{ env.COMMIT_DATE }}-${{ env.COMMIT_SHA }}
+51
View File
@@ -0,0 +1,51 @@
name: WASM Compilation
on:
workflow_call:
jobs:
wasm:
name: wasm (${{ matrix.arch }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [wasm32, wasm32-relaxed-simd]
defaults:
run:
working-directory: src
shell: bash
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Install emsdk
working-directory: .
run: |
git clone --depth 1 https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
- name: Extract the bench number from the commit history
run: |
for hash in $(git rev-list -100 HEAD); do
benchref=$(git show -s $hash | tac | grep -m 1 -o -x '[[:space:]]*\b[Bb]ench[ :]\+[1-9][0-9]\{5,7\}\b[[:space:]]*' | sed 's/[^0-9]//g') && break || true
done
[[ -n "$benchref" ]] && echo "benchref=$benchref" >> $GITHUB_ENV && echo "From commit: $hash" && echo "Reference bench: $benchref" || echo "No bench found"
- name: Download the used network from the fishtest framework
run: make net
- name: Build ${{ matrix.arch }} build
run: |
source $GITHUB_WORKSPACE/emsdk/emsdk_env.sh
em++ --version
net=$(grep -oE 'nn-[0-9a-f]+\.nnue' evaluate.h | head -1)
make clean
COMPCXX=em++ make -j build ARCH=${{ matrix.arch }} EXTRALDFLAGS="--preload-file $net"
- name: Verify bench under node
run: EXE=./stockfish.js RUN_PREFIX=node ../tests/signature.sh $benchref
+6 -1
View File
@@ -2,6 +2,11 @@
**/*.o
**/*.s
src/.depend
src/temp_builds
src/universal/network_dump.inc
.build_sha.txt
.build_date.txt
.build_diffindex.txt
# Built binary
src/stockfish*
@@ -14,4 +19,4 @@ src/-lstdc++.res
tsan.supp
__pycache__/
tests/syzygy
tests/bench_tmp.epd
tests/bench_tmp.epd
+27 -11
View File
@@ -12,9 +12,11 @@ Hisayori Noda (nodchip)
# All other authors of Stockfish code (in alphabetical order)
87flowers
Aditya (absimaldata)
Adrian Ladoni (AdrianGHUB15)
Adrian Petrescu (apetresc)
Ahmed Kerimov (wcdbmv)
Ajith Chandy Jose (ajithcj)
AK-Khan02
Alain Savard (Rocky640)
Alayan Feh (Alayan-stk-2)
Alexander Kure
@@ -58,13 +60,15 @@ Ciekce
clefrks
Clemens L. (rn5f107s2)
Cody Ho (aesrentai)
Con Kirby (ConKirby)
CSTENTOR
Dale Weiler (graphitemaster)
Dalton Hanaway (dhanaway)
Dan Schmidt (dfannius)
Daniel Axtens (daxtens)
Daniel Dugovic (ddugovic)
Daniel Monroe (daniel-monroe)
Daniel Samek (DanSamek)
Dan Schmidt (dfannius)
Dariusz Orzechowski (dorzechowski)
David (dav1312)
David Zar
@@ -89,9 +93,9 @@ Fanael Linithien (Fanael)
fanon
Fauzi Akram Dabat (fauzi2)
Felix Wittmann
gamander
Gabriele Lombardo (gabe)
Gahtan Nahdi
gamander
Gary Heckman (gheckman)
George Sobala (gsobala)
gguliash
@@ -102,8 +106,8 @@ Gontran Lemaire (gonlem)
Goodkov Vasiliy Aleksandrovich (goodkov)
Gregor Cramer
GuardianRM
Guy Vreuls (gvreuls)
Günther Demetz (pb00067, pb00068)
Guy Vreuls (gvreuls)
Henri Wiechers
Hiraoka Takuya (HiraokaTakuya)
homoSapiensSapiens
@@ -116,8 +120,8 @@ Jan Ondruš (hxim)
Jared Kish (Kurtbusch, kurt22i)
Jarrod Torriero (DU-jdto)
Jasper Shovelton (Beanie496)
Jean-Francois Romang (jromang)
Jean Gauthier (OuaisBla)
Jean-Francois Romang (jromang)
Jekaa
Jerry Donald Watson (jerrydonaldwatson)
jjoshua2
@@ -125,10 +129,10 @@ Jonathan Buladas Dumale (SFisGOD)
Jonathan Calovski (Mysseno)
Jonathan McDermid (jonathanmcdermid)
Joost VandeVondele (vondele)
Jörg Oster (joergoster)
Joseph Ellis (jhellis3)
Joseph R. Prostko
Jost Triller (tsoj)
Jörg Oster (joergoster)
Julian Willemer (NightlyKing)
jundery
Justin Blanchard (UncombedCoconut)
@@ -177,15 +181,18 @@ Michel Van den Bergh (vdbergh)
Miguel Lahoz (miguel-l)
Mikael Bäckman (mbootsector)
Mike Babigian (Farseer)
Miloslav Macůrek (maelic13)
Mira
Miroslav Fontán (Hexik)
Moez Jellouli (MJZ1977)
Mohammed Li (tthsqe12)
Mukhammedali Beriktassuly (Berektassuly)
Muzhen J (XInTheDark)
Mafanwei
Nathan Rugg (nmrugg)
Nguyen Pham (nguyenpham)
Nicklas Persson (NicklasPersson)
Nick Pelling (nickpelling)
Nicklas Persson (NicklasPersson)
Nicolas Duhamel (nikloskoda)
Niklas Fiekas (niklasf)
Nikolay Kostov (NikolayIT)
@@ -193,10 +200,11 @@ Norman Schmidt (FireFather)
notruck
Nour Berakdar (Nonlinear)
Ofek Shochat (OfekShochat, ghostway)
Ondrej Mosnáček (WOnder93)
Ondřej Mišina (AndrovT)
Oskar Werkelin Ahlin
Ömer Faruk Tutkun (OmerFarukTutkun)
Ondřej Mišina (AndrovT)
Ondrej Mosnáček (WOnder93)
Onur Zungur (zungur)
Oskar Werkelin Ahlin
Pablo Vazquez
Panthee
Pascal Romaret
@@ -205,6 +213,7 @@ Patrick Jansen (mibere)
Patrick Leonhardt (Yoshie2000)
Peter Schneider (pschneider1968)
Peter Zsifkovits (CoffeeOne)
Pieter te Brake (pieterteb)
PikaCat
Praveen Kumar Tummala (praveentml)
Prokop Randáček (ProkopRandacek)
@@ -217,11 +226,13 @@ Richard Lloyd (Richard-Lloyd)
Robert Nürnberg (robertnurnberg)
Rodrigo Exterckötter Tjäder
Rodrigo Roim (roim)
Ronald de Man (syzygy1, syzygy)
Ron Britvich (Britvich)
Ronald de Man (syzygy1, syzygy)
rqs
Rui Coelho (ruicoelhopedro)
rustam-cpp
Ryan Hirsch
Ryan Lefkowitz (rlefko)
Ryan Schmitt
Ryan Takker
Sami Kiminki (skiminki)
@@ -251,10 +262,12 @@ theo77186
TierynnB
Timothy Herchen (anematode)
Ting-Hsuan Huang (fffelix-huang)
tobi437a
Tobias Steinmann
Tomasz Sobczyk (Sopel97)
Tom Truscott
Tom Vijlbrief (tomtor)
Tomasz Sobczyk (Sopel97)
Tony Wang (TonyCongqianWang)
Torsten Franz (torfranz, tfranzer)
Torsten Hellwig (Torom)
Tracey Emery (basepr1me)
@@ -264,9 +277,12 @@ Uri Blass (uriblass)
Vince Negri (cuddlestmonkey)
Viren
Wencey Wang
Will Miles (willm)
windfishballad
xefoci7612
Xiang Wang (KatyushaScarlet)
Yen-Chao Shen (lemteay)
ZlomenyMesic
zz4032
# Additionally, we acknowledge the authors and maintainers of fishtest,
+91
View File
@@ -0,0 +1,91 @@
#!/bin/sh
# Verify that the universal binary selects the correct per-arch build under
# Intel SDE emulation for a range of target CPUs
#
# Usage: check_universal.sh STOCKFISH_EXE SDE_EXE EXPECTED_BENCH
set -eu
if [ $# -ne 3 ]; then
echo "Usage: $0 STOCKFISH_EXE SDE_EXE EXPECTED_BENCH" >&2
exit 2
fi
STOCKFISH_EXE=$1
SDE_EXE=$2
EXPECTED_BENCH=$3
if [ "$(uname)" = 'Linux' ]; then
# Windows 11 doesn't support these old arches
PAIRS="
p4p:x86-64
nhm:x86-64-sse41-popcnt
"
else
PAIRS=''
fi
PAIRS="$PAIRS
snb:x86-64-sse41-popcnt
ivb:x86-64-sse41-popcnt
hsw:x86-64-bmi2
skl:x86-64-bmi2
skx:x86-64-avx512
clx:x86-64-vnni512
icl:x86-64-avx512icl
adl:x86-64-avxvnni
"
BINARY_SIZE=$(wc -c < "$STOCKFISH_EXE")
MAX_SIZE=$((150 * 1024 * 1024))
if [ "$BINARY_SIZE" -gt "$MAX_SIZE" ]; then
printf 'check_universal.sh: binary size %d bytes exceeds 150 MB limit\n' "$BINARY_SIZE" >&2
exit 1
fi
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
i=0
for pair in $PAIRS; do
i=$((i + 1))
cpu=${pair%%:*}
expected_compiler=${pair##*:}
(
compiler_out=$("$SDE_EXE" "-$cpu" -- "$STOCKFISH_EXE" compiler 2>&1 || true)
bench_out=$("$SDE_EXE" "-$cpu" -- "$STOCKFISH_EXE" bench 2>&1 >/dev/null || true)
actual_compiler=$(printf '%s\n' "$compiler_out" | awk -F: '/Compilation architecture/ {
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
}')
actual_bench=$(printf '%s\n' "$bench_out" | awk -F: '/Nodes searched/ {
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
}')
if [ "$actual_compiler" != "$expected_compiler" ] || [ "$actual_bench" != "$EXPECTED_BENCH" ]; then
printf '===== CPU %s output (expected %s/%s, got %s/%s) =====\n' \
"$cpu" "$expected_compiler" "$EXPECTED_BENCH" "${actual_compiler:--}" "$actual_bench" > "$WORK/$i.fail"
else
printf 'CPU %s ok\n' "$cpu" > "$WORK/$i.log"
fi
) &
done
wait
FAIL=0
i=0
for pair in $PAIRS; do
i=$((i + 1))
if [ -f "$WORK/$i.fail" ]; then
cat "$WORK/$i.fail" >&2
FAIL=1
else
cat "$WORK/$i.log" >&2
fi
done
if [ "$FAIL" != 0 ]; then
echo "check_universal.sh: failed"
exit 1
fi
echo "check_universal.sh: Good! Universal binary has correct hardware detection."
+58
View File
@@ -0,0 +1,58 @@
#!/bin/sh
# Verify that the arm64 universal binary selects the correct per-arch build
# under qemu-aarch64 emulation for a range of target CPUs.
#
# Usage: check_universal_arm.sh STOCKFISH_EXE EXPECTED_BENCH
set -eu
if [ $# -ne 2 ]; then
echo "Usage: $0 STOCKFISH_EXE EXPECTED_BENCH" >&2
exit 2
fi
STOCKFISH_EXE=$1
EXPECTED_BENCH=$2
PAIRS="
cortex-a53:armv8
max:armv8-dotprod
"
BINARY_SIZE=$(wc -c < "$STOCKFISH_EXE")
MAX_SIZE=$((150 * 1024 * 1024))
if [ "$BINARY_SIZE" -gt "$MAX_SIZE" ]; then
printf 'check_universal_arm.sh: binary size %d bytes exceeds 150 MB limit\n' "$BINARY_SIZE" >&2
exit 1
fi
FAIL=0
for pair in $PAIRS; do
cpu=${pair%%:*}
expected_compiler=${pair##*:}
compiler_out=$(qemu-aarch64 -cpu "$cpu" -- "$STOCKFISH_EXE" compiler 2>&1 || true)
bench_out=$(qemu-aarch64 -cpu "$cpu" -- "$STOCKFISH_EXE" bench 2>&1 || true)
actual_compiler=$(printf '%s\n' "$compiler_out" | awk -F: '/Compilation architecture/ {
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
}')
actual_bench=$(printf '%s\n' "$bench_out" | awk -F: '/Nodes searched/ {
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
}')
if [ "$actual_compiler" != "$expected_compiler" ] || [ "$actual_bench" != "$EXPECTED_BENCH" ]; then
printf '===== CPU %s output (expected %s/%s, got %s/%s) =====\n' \
"$cpu" "$expected_compiler" "$EXPECTED_BENCH" "${actual_compiler:--}" "$actual_bench" >&2
printf 'Full "compiler" output: %s\n' "$compiler_out"
printf 'Full "bench" output: %s\n' "$bench_out"
FAIL=1
else
printf 'CPU %s ok\n' "$cpu" >&2
fi
done
if [ "$FAIL" != 0 ]; then
echo "check_universal_arm.sh: failed"
exit 1
fi
echo "check_universal_arm.sh: Good! Universal binary has correct hardware detection."
+62
View File
@@ -0,0 +1,62 @@
#!/bin/sh
# Usage: check_universal_macos.sh STOCKFISH_EXE [EXPECTED_BENCH]
set -eu
STOCKFISH_EXE=$1
extract() {
awk -F: -v lbl="$1" '$0 ~ lbl {
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit
}'
}
FAIL=0
BINARY_SIZE=$(wc -c < "$STOCKFISH_EXE")
MAX_SIZE=$((150 * 1024 * 1024))
if [ "$BINARY_SIZE" -gt "$MAX_SIZE" ]; then
printf 'check_universal_macos.sh: binary size %d bytes exceeds 150 MB limit\n' "$BINARY_SIZE" >&2
exit 1
fi
native_bench=$(arch -arm64 "$STOCKFISH_EXE" bench 2>&1 | extract "Nodes searched" || true)
if [ -z "$native_bench" ]; then
echo "check_universal_macos.sh: arm64 run produced no bench" >&2
FAIL=1
else
printf 'native (arm64) bench %s\n' "$native_bench" >&2
fi
x86_bench=$(arch -x86_64 "$STOCKFISH_EXE" bench 2>&1 | extract "Nodes searched" || true)
if [ -z "$x86_bench" ]; then
echo "check_universal_macos.sh: x86_64 (Rosetta) run produced no bench (crashed?)" >&2
FAIL=1
else
printf 'x86_64 (Rosetta) bench %s\n' "$x86_bench" >&2
fi
# Should match
if [ "$native_bench" != "$x86_bench" ]; then
printf 'check_universal_macos.sh: slice bench mismatch: arm64=%s x86_64=%s\n' \
"$native_bench" "$x86_bench" >&2
FAIL=1
fi
# Check that under Rosetta, SSE41 is detected
x86_compiler=$(arch -x86_64 "$STOCKFISH_EXE" compiler 2>&1 || true)
if printf '%s\n' "$x86_compiler" | grep -q 'SSE41'; then
printf 'x86_64 (Rosetta) compiler reports SSE41 ok\n' >&2
else
echo "check_universal_macos.sh: x86_64 compiler output missing SSE41" >&2
printf '%s\n' "$x86_compiler" >&2
FAIL=1
fi
if [ "$FAIL" != 0 ]; then
echo "check_universal_macos.sh: failed"
exit 1
fi
echo "check_universal_macos.sh: Good! Universal macOS binary works."
+73
View File
@@ -0,0 +1,73 @@
#!/bin/sh
# Verify that the riscv64 universal binary selects correct build
# and benches correctly under qemu-riscv64 with various vlens
#
# Usage: check_universal_riscv.sh STOCKFISH_EXE EXPECTED_BENCH
set -eu
if [ $# -ne 2 ]; then
echo "Usage: $0 STOCKFISH_EXE EXPECTED_BENCH" >&2
exit 2
fi
STOCKFISH_EXE=$1
EXPECTED_BENCH=$2
QEMU=${QEMU:-qemu-riscv64}
export QEMU_LD_PREFIX=${QEMU_LD_PREFIX:-/usr/riscv64-linux-gnu}
EXTS=zba=true,zbb=true,zbs=true,zicond=true
PAIRS="
rv64:riscv64
rv64,v=true,$EXTS,vlen=128:riscv64-rva23
rv64,v=true,$EXTS,vlen=256:riscv64-rva23
rv64,v=true,$EXTS,vlen=512:riscv64-rva23
rv64,v=true,$EXTS,vlen=1024:riscv64-rva23
"
BINARY_SIZE=$(wc -c < "$STOCKFISH_EXE")
MAX_SIZE=$((150 * 1024 * 1024))
if [ "$BINARY_SIZE" -gt "$MAX_SIZE" ]; then
printf 'check_universal_riscv.sh: binary size %d bytes exceeds 150 MB limit\n' "$BINARY_SIZE" >&2
exit 1
fi
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT
idx=0
for pair in $PAIRS; do
idx=$((idx + 1))
cpu=${pair%%:*}
(
comp=$($QEMU -cpu "$cpu" "$STOCKFISH_EXE" compiler 2>&1 | awk -F: '/Compilation architecture/ {
sub(/^[[:space:]]+/, "", $2); sub(/[[:space:]]+$/, "", $2); print $2; exit }')
bench=$($QEMU -cpu "$cpu" "$STOCKFISH_EXE" bench 2>&1 | awk -F: '/Nodes searched/ {
gsub(/[^0-9]/, "", $2); print $2; exit }')
printf '%s|%s|%s\n' "$cpu" "$comp" "$bench" > "$tmp/$idx"
) &
done
wait
FAIL=0
idx=0
for pair in $PAIRS; do
idx=$((idx + 1))
expected=${pair##*:}
IFS='|' read -r cpu comp bench < "$tmp/$idx"
if [ "$comp" = "$expected" ] && [ "$bench" = "$EXPECTED_BENCH" ]; then
printf 'CPU %-26s ok (%s, bench %s)\n' "$cpu" "$comp" "$bench" >&2
else
printf 'CPU %-26s FAIL: expected %s/%s, got %s/%s\n' \
"$cpu" "$expected" "$EXPECTED_BENCH" "${comp:--}" "${bench:--}" >&2
FAIL=1
fi
done
if [ "$FAIL" != 0 ]; then
echo "check_universal_riscv.sh: failed"
exit 1
fi
echo "check_universal_riscv.sh: Good! Universal binary has correct hardware detection."
+370 -139
View File
@@ -1,163 +1,394 @@
#!/bin/sh
#
# Returns properties of the native system.
# best architecture as supported by the CPU
# filename of the best binary uploaded as an artifact during CI
# Returns the best architecture supported by the CPU (as expected by src/Makefile ARCH=).
#
# Output format:
# "<true_arch>\n"
#
# Check if all the given flags are present in the CPU flags list
check_flags() {
for flag; do
printf '%s\n' "$flags" | grep -q -w "$flag" || return 1
done
# ---------------------------
# Helpers (POSIX)
# ---------------------------
# Test hooks (optional env overrides)
# GP_UNAME_S: override `uname -s`
# GP_UNAME_M: override `uname -m`
# GP_CPUINFO: path to a cpuinfo-like fixture file (defaults to /proc/cpuinfo)
# GP_BITS: override getconf LONG_BIT result (32/64)
# GP_SYSCTL_FEATURES: override sysctl feature strings on Darwin x86_64
cpuinfo_path=${GP_CPUINFO:-/proc/cpuinfo}
# Normalize to a single-line, space-separated string.
normalize_ws() {
printf '%s\n' "$*" | tr '\n\t' ' ' | tr -s ' '
}
# Set the CPU flags list
# remove underscores and points from flags, e.g. gcc uses avx512vnni, while some cpuinfo can have avx512_vnni, some systems use sse4_1 others sse4.1
die() {
printf '%s\n' "$*" >&2
exit 1
}
# Populate $flags from /proc/cpuinfo when available,
# removing underscores and dots to reduce naming variations.
get_flags() {
flags=$(awk '/^flags[ \t]*:|^Features[ \t]*:/{gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, ""); line=$0} END{print line}' /proc/cpuinfo)
if [ -r "$cpuinfo_path" ]; then
flags=$(
awk '
/^flags[ \t]*:|^Features[ \t]*:/ {
if (!found) {
gsub(/^flags[ \t]*:[ \t]*|^Features[ \t]*:[ \t]*|[_.]/, "");
line=$0
found=1
}
}
END { print line }
' "$cpuinfo_path" 2>/dev/null
)
else
flags=''
fi
flags=$(printf '%s\n' "$flags" | tr '[:upper:]' '[:lower:]')
flags=$(normalize_ws "$flags")
}
# Check for gcc march "znver1" or "znver2" https://en.wikichip.org/wiki/amd/cpuid
check_znver_1_2() {
vendor_id=$(awk '/^vendor_id/{print $3; exit}' /proc/cpuinfo)
cpu_family=$(awk '/^cpu family/{print $4; exit}' /proc/cpuinfo)
[ "$vendor_id" = "AuthenticAMD" ] && [ "$cpu_family" = "23" ] && znver_1_2=true
# Populate $flags from the RISC-V isa string; split the single-letter
# base (e.g., rv64imafdcv), and keep multi-letter extensions.
get_riscv_flags() {
if [ -r "$cpuinfo_path" ]; then
isa=$(awk -F: '/^isa[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null)
else
isa=''
fi
isa=$(printf '%s\n' "$isa" | tr '[:upper:]' '[:lower:]')
flags=$(printf '%s\n' "$isa" | awk '{
gsub(/^[ \t]*rv(32|64)/, "");
n = split($0, ext, "_");
out = "";
for (i = 1; i <= length(ext[1]); i++) out = out " " substr(ext[1], i, 1);
for (i = 2; i <= n; i++) out = out " " ext[i];
print out;
}')
flags=$(normalize_ws "$flags")
}
# Set the file CPU loongarch64 architecture
# Populate $flags from sysctl on Darwin x86_64.
get_sysctl_flags() {
if [ -n "${GP_SYSCTL_FEATURES:-}" ]; then
flags=$(printf '%s\n' "$GP_SYSCTL_FEATURES")
else
flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features 2>/dev/null)
fi
flags=$(printf '%s\n' "$flags" | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '._')
flags=$(normalize_ws "$flags")
}
# Best-effort bitness for fallback arch selection.
get_bits() {
if [ -n "${GP_BITS:-}" ]; then
bits=$GP_BITS
else
bits=$(getconf LONG_BIT 2>/dev/null)
fi
case $bits in
32|64) : ;;
*) bits=64 ;;
esac
}
# Extract ARM architecture level (5/6/7/8/...) from /proc/cpuinfo when present.
get_arm_arch_level() {
[ -r "$cpuinfo_path" ] || return 1
awk '
/^CPU architecture[ \t]*:/{
s=$0
sub(/^[^:]*:[ \t]*/, "", s)
if (match(s, /[0-9]+/)) { print substr(s, RSTART, RLENGTH); exit }
}
/^Processor[ \t]*:/{
s=$0
sub(/^[^:]*:[ \t]*/, "", s)
if (match(s, /ARMv[0-9]+/)) { print substr(s, RSTART+4, RLENGTH-4); exit }
}
' "$cpuinfo_path" 2>/dev/null
}
# Best-effort ARM architecture level (5/6/7/8/...) with a minimal fallback.
# Prefer /proc/cpuinfo when available; fall back to uname -m only when it encodes it.
get_arm_level() {
arm_level=$(get_arm_arch_level || :)
if [ -n "$arm_level" ]; then
printf '%s\n' "$arm_level"
return 0
fi
case ${1:-} in
armv5*) printf '5\n' ;;
armv6*) printf '6\n' ;;
armv7*) printf '7\n' ;;
armv8l) printf '8\n' ;;
*) return 1 ;;
esac
}
# Whole-token membership check.
has_flag() {
case " $flags " in
*" $1 "*) return 0 ;;
*) return 1 ;;
esac
}
match_flags() {
for f; do
has_flag "$f" || return 1
done
return 0
}
match_any_flags() {
for f; do
has_flag "$f" && return 0
done
return 1
}
# SSE3 is often exposed as "pni" in /proc/cpuinfo.
match_sse3() {
match_any_flags sse3 pni
}
# AMD Excavator/Zen1/2 exclusion logic (used for bmi2 tier).
# https://web.archive.org/web/20250821132355/https://en.wikichip.org/wiki/amd/cpuid
# All of Bulldozer through Excavator are matched here, but pre-Excavator doesn't support bmi2 anyway
has_slow_bmi2() (
[ -r "$cpuinfo_path" ] || exit 1
vendor_id=$(awk '/^vendor_id/{print $3; exit}' "$cpuinfo_path" 2>/dev/null)
cpu_family=$(awk '/^cpu family/{print $4; exit}' "$cpuinfo_path" 2>/dev/null)
[ "$vendor_id" = "AuthenticAMD" ] \
&& { [ "$cpu_family" = "21" ] || [ "$cpu_family" = "23" ]; }
)
match_not_slow_bmi2_and_flags() {
has_slow_bmi2 && return 1
match_flags "$@"
}
match_sse3_popcnt() {
has_flag popcnt || return 1
match_sse3
}
match_true() { return 0; }
# Generic selector: reads lines like "arch|predicate|arg1 arg2 ..."
# First match wins; blank lines and lines starting with '#' are ignored.
select_arch_from_table() {
while IFS='|' read -r arch pred args; do
[ -z "$arch" ] && continue
case $arch in \#*) continue ;; esac
if [ -n "$args" ]; then
# Intentional splitting of args into words for the predicate.
# shellcheck disable=SC2086
$pred $args && { printf '%s\n' "$arch"; return 0; }
else
$pred && { printf '%s\n' "$arch"; return 0; }
fi
done
return 1
}
# ---------------------------
# Arch selection (table-driven)
# ---------------------------
set_arch_loongarch64() {
if check_flags 'lasx'; then
true_arch='loongarch64-lasx'
elif check_flags 'lsx'; then
true_arch='loongarch64-lsx'
else
true_arch='loongarch64'
fi
true_arch=$(
select_arch_from_table <<'EOF'
loongarch64-lasx|match_flags|lasx
loongarch64-lsx|match_flags|lsx
loongarch64|match_true|
EOF
)
}
set_arch_riscv64() {
get_riscv_flags
true_arch=$(
select_arch_from_table <<'EOF'
riscv64-rva23|match_flags|v zba zbb zbs zicond
riscv64|match_true|
EOF
)
}
# Set the file CPU x86_64 architecture
set_arch_x86_64() {
if check_flags 'avx512f' 'avx512cd' 'avx512vl' 'avx512dq' 'avx512bw' 'avx512ifma' 'avx512vbmi' 'avx512vbmi2' 'avx512vpopcntdq' 'avx512bitalg' 'avx512vnni' 'vpclmulqdq' 'gfni' 'vaes'; then
true_arch='x86-64-avx512icl'
elif check_flags 'avx512vnni' 'avx512dq' 'avx512f' 'avx512bw' 'avx512vl'; then
true_arch='x86-64-vnni512'
elif check_flags 'avx512f' 'avx512bw'; then
true_arch='x86-64-avx512'
elif check_flags 'avxvnni'; then
true_arch='x86-64-avxvnni'
elif [ -z "${znver_1_2+1}" ] && check_flags 'bmi2'; then
true_arch='x86-64-bmi2'
elif check_flags 'avx2'; then
true_arch='x86-64-avx2'
elif check_flags 'sse41' && check_flags 'popcnt'; then
true_arch='x86-64-sse41-popcnt'
else
true_arch='x86-64'
fi
true_arch=$(
select_arch_from_table <<'EOF'
# Strongest -> weakest (first match wins)
x86-64-avx512icl|match_flags|avx512f avx512cd avx512vl avx512dq avx512bw avx512ifma avx512vbmi avx512vbmi2 avx512vpopcntdq avx512bitalg avx512vnni vpclmulqdq gfni vaes
x86-64-vnni512|match_flags|avx512vnni avx512dq avx512f avx512bw avx512vl
x86-64-avx512|match_flags|avx512f avx512bw
x86-64-avxvnni|match_flags|avxvnni
x86-64-bmi2|match_not_slow_bmi2_and_flags|bmi2
x86-64-avx2|match_flags|avx2
x86-64-sse41-popcnt|match_flags|sse41 popcnt
x86-64-ssse3|match_flags|ssse3
x86-64-sse3-popcnt|match_sse3_popcnt|
x86-64|match_true|
EOF
)
}
set_arch_x86_32() {
true_arch=$(
select_arch_from_table <<'EOF'
x86-32-sse41-popcnt|match_flags|sse41 popcnt
x86-32-sse2|match_flags|sse2
x86-32|match_true|
EOF
)
}
# PPC64 needs a little parsing to distinguish vsx vs altivec.
set_arch_ppc_64() {
if grep -q -w "altivec" /proc/cpuinfo; then
power=$(grep -oP -m 1 'cpu\t+: POWER\K\d+' /proc/cpuinfo)
if [ "0$power" -gt 7 ]; then
# VSX started with POWER8
true_arch='ppc-64-vsx'
else
true_arch='ppc-64-altivec'
fi
else
true_arch='ppc-64'
fi
if [ -r "$cpuinfo_path" ] && grep -q "altivec" "$cpuinfo_path" 2>/dev/null; then
# Typical: "cpu : POWER8E" (extract the number after POWER)
power=$(
awk -F: '/^cpu[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null \
| sed -n 's/.*[Pp][Oo][Ww][Ee][Rr][^0-9]*\([0-9][0-9]*\).*/\1/p'
)
if [ -z "$power" ]; then
power=$(
awk -F: '/^cpu[ \t]*:/{print $2; exit}' "$cpuinfo_path" 2>/dev/null \
| sed -n 's/.*\([0-9][0-9]*\).*/\1/p'
)
fi
case $power in
''|*[!0-9]*)
true_arch='ppc-64-altivec'
;;
*)
if [ "$power" -gt 7 ] 2>/dev/null; then
true_arch='ppc-64-vsx'
else
true_arch='ppc-64-altivec'
fi
;;
esac
else
true_arch='ppc-64'
fi
}
# Check the system type
uname_s=$(uname -s)
uname_m=$(uname -m)
# ---------------------------
# OS / machine dispatch
# ---------------------------
uname_s=$(uname -s 2>/dev/null)
uname_m=$(uname -m 2>/dev/null)
uname_s=${GP_UNAME_S:-$uname_s}
uname_m=${GP_UNAME_M:-$uname_m}
case $uname_s in
'Darwin') # Mac OSX system
case $uname_m in
'arm64')
true_arch='apple-silicon'
file_arch='m1-apple-silicon'
;;
'x86_64')
flags=$(sysctl -n machdep.cpu.features machdep.cpu.leaf7_features | tr '\n' ' ' | tr '[:upper:]' '[:lower:]' | tr -d '_.')
set_arch_x86_64
if [ "$true_arch" = 'x86-64-avx512' ]; then
file_arch='x86-64-bmi2'
fi
;;
esac
file_os='macos'
file_ext='tar'
;;
'Linux') # Linux system
get_flags
case $uname_m in
'x86_64')
file_os='ubuntu'
check_znver_1_2
set_arch_x86_64
;;
'i686')
file_os='ubuntu'
true_arch='x86-32'
;;
'ppc64'*)
file_os='ubuntu'
set_arch_ppc_64
;;
'aarch64')
file_os='android'
true_arch='armv8'
if check_flags 'asimddp'; then
true_arch="$true_arch-dotprod"
fi
;;
'armv7'*)
file_os='android'
true_arch='armv7'
if check_flags 'neon'; then
true_arch="$true_arch-neon"
fi
;;
'loongarch64'*)
file_os='linux'
set_arch_loongarch64
;;
*) # Unsupported machine type, exit with error
printf 'Unsupported machine type: %s\n' "$uname_m"
exit 1
;;
esac
file_ext='tar'
;;
'MINGW'*'ARM64'*) # Windows ARM64 system with POSIX compatibility layer
# TODO: older chips might be armv8, but we have no good way to detect, /proc/cpuinfo shows x86 info
file_os='windows'
true_arch='armv8-dotprod'
file_ext='zip'
;;
'CYGWIN'*|'MINGW'*|'MSYS'*) # Windows x86_64system with POSIX compatibility layer
get_flags
check_znver_1_2
set_arch_x86_64
file_os='windows'
file_ext='zip'
;;
*)
# Unknown system type, exit with error
printf 'Unsupported system type: %s\n' "$uname_s"
exit 1
;;
Darwin)
case $uname_m in
arm64)
true_arch='apple-silicon'
;;
x86_64)
get_sysctl_flags
set_arch_x86_64
;;
*)
get_bits
if [ "$bits" = "32" ]; then
true_arch='general-32'
else
true_arch='general-64'
fi
;;
esac
;;
Linux)
get_flags
case $uname_m in
x86_64)
set_arch_x86_64
;;
i?86)
set_arch_x86_32
;;
ppc64*)
set_arch_ppc_64
;;
aarch64|arm64)
true_arch='armv8'
if match_flags asimddp; then
true_arch='armv8-dotprod'
fi
;;
armv5*|armv6*|armv7*|armv8l|arm*)
arm_level=$(get_arm_level "$uname_m" || :)
case $arm_level in
5|6)
true_arch='general-32'
;;
7|8)
true_arch='armv7'
if match_flags neon; then
true_arch='armv7-neon'
fi
;;
*)
true_arch='general-32'
if match_flags neon; then
true_arch='armv7-neon'
fi
;;
esac
;;
loongarch64*)
set_arch_loongarch64
;;
riscv64)
set_arch_riscv64
;;
e2k*)
true_arch='e2k'
;;
ppc|ppc32|powerpc)
true_arch='ppc-32'
;;
*)
# Don't hard-fail: fall back to general-* so ARCH=native still builds
get_bits
if [ "$bits" = "32" ]; then
true_arch='general-32'
else
true_arch='general-64'
fi
;;
esac
;;
MINGW*ARM64*)
# Windows ARM64 (MSYS2/MinGW)
# Can't reliably detect ARM CPU features here
true_arch='armv8-dotprod'
;;
CYGWIN*|MINGW*|MSYS*)
# Windows x86_64 (MSYS2/Cygwin/MinGW)
get_flags
set_arch_x86_64
;;
*)
die "Unsupported system type: $uname_s"
;;
esac
if [ -z "$file_arch" ]; then
file_arch=$true_arch
fi
file_name="stockfish-$file_os-$file_arch.$file_ext"
printf '%s %s\n' "$true_arch" "$file_name"
printf '%s\n' "$true_arch"
+18 -10
View File
@@ -1,11 +1,11 @@
#!/bin/sh
wget_or_curl=$( (command -v wget > /dev/null 2>&1 && echo "wget -qO-") || \
(command -v curl > /dev/null 2>&1 && echo "curl -skL"))
# download commands with a 5min time-out to ensure things fail if the server stalls
wget_or_curl=$( (command -v wget >/dev/null 2>&1 && echo "wget -qO- --timeout=300 --tries=1") ||
(command -v curl >/dev/null 2>&1 && echo "curl -skL --max-time 300"))
sha256sum=$( (command -v shasum > /dev/null 2>&1 && echo "shasum -a 256") || \
(command -v sha256sum > /dev/null 2>&1 && echo "sha256sum"))
sha256sum=$( (command -v shasum >/dev/null 2>&1 && echo "shasum -a 256") ||
(command -v sha256sum >/dev/null 2>&1 && echo "sha256sum"))
if [ -z "$sha256sum" ]; then
>&2 echo "sha256sum not found, NNUE files will be assumed valid."
@@ -44,7 +44,7 @@ fetch_network() {
if [ -z "$wget_or_curl" ]; then
>&2 printf "%s\n" "Neither wget or curl is installed." \
"Install one of these tools to download NNUE files automatically."
"Install one of these tools to download NNUE files automatically."
exit 1
fi
@@ -52,14 +52,16 @@ fetch_network() {
"https://tests.stockfishchess.org/api/nn/$_filename" \
"https://github.com/official-stockfish/networks/raw/master/$_filename"; do
echo "Downloading from $url ..."
if $wget_or_curl "$url" > "$_filename"; then
if $wget_or_curl "$url" >"$_filename"; then
if validate_network "$_filename"; then
echo "Successfully validated $_filename"
else
echo "Downloaded $_filename is invalid"
rm -f $_filename
echo "Downloaded $_filename is invalid, and has been removed."
continue
fi
else
rm -f $_filename
echo "Failed to download from $url"
fi
if [ -f "$_filename" ]; then
@@ -72,5 +74,11 @@ fetch_network() {
return 1
}
fetch_network EvalFileDefaultNameBig && \
fetch_network EvalFileDefaultNameSmall
fetch_network EvalFileDefaultName
if [ "$1" = "0" ]; then
DUMP_FILE=universal/network_dump.inc
echo -n '"' > $DUMP_FILE
hexdump -v -e '"\\" "x" 1/1 "%02X"' "$(get_nnue_filename EvalFileDefaultName)" >> $DUMP_FILE
echo -n '"' >> $DUMP_FILE
fi
+455 -86
View File
@@ -25,6 +25,13 @@ ifeq ($(KERNEL),Linux)
OS := $(shell uname -o)
endif
### Target operating system (may differ from KERNEL when cross-compiling)
TARGET_KERNEL := $(KERNEL)
ifeq ($(COMP),ndk)
TARGET_KERNEL := Linux
OS := Android
endif
### Command prefix to run the built executable (e.g. wine, sde, qemu)
### Backward compatible alias: WINE_PATH (deprecated)
ifneq ($(strip $(WINE_PATH)),)
@@ -55,6 +62,8 @@ endif
### Executable name
ifeq ($(target_windows),yes)
EXE = stockfish.exe
else ifneq (,$(findstring wasm,$(ARCH)))
EXE = stockfish.js
else
EXE = stockfish
endif
@@ -67,20 +76,22 @@ BINDIR = $(PREFIX)/bin
PGOBENCH = $(RUN_PREFIX) ./$(EXE) bench
### Source and object files
SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
SRCS = attacks.cpp benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
misc.cpp movegen.cpp movepick.cpp position.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/network.cpp \
nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp \
nnue/features/half_ka_v2_hm.cpp nnue/features/full_threats.cpp nnue/features/pp_3wide.cpp \
engine.cpp score.cpp memory.cpp
HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \
OTHER_SRCS = universal/entry_x86.cpp universal/entry_arm64.cpp universal/entry_riscv64.cpp universal/nnue_embed.cpp
HEADERS = attacks.h benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \
nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/features/full_threats.h \
nnue/layers/affine_transform.h nnue/layers/affine_transform_sparse_input.h \
nnue/features/pp_3wide.h nnue/layers/affine_transform.h nnue/layers/affine_transform_sparse_input.h \
nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h \
nnue/nnue_architecture.h nnue/nnue_common.h nnue/nnue_feature_transformer.h nnue/simd.h \
position.h search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h
nnue/nnz_helper.h position.h search.h syzygy/tbprobe.h thread.h thread_native.h timeman.h \
tt.h tune.h types.h uci.h ucioption.h perft.h nnue/network.h engine.h score.h numa.h memory.h shm.h shm_linux.h
OBJS = $(notdir $(SRCS:.cpp=.o))
@@ -120,7 +131,9 @@ VPATH = syzygy:nnue:nnue/features
# neon = yes/no --- -DUSE_NEON --- Use ARM SIMD architecture
# dotprod = yes/no --- -DUSE_NEON_DOTPROD --- Use ARM advanced SIMD Int8 dot product instructions
# lsx = yes/no --- -mlsx --- Use Loongson SIMD eXtension
# lasx = yes/no --- -mlasx --- use Loongson Advanced SIMD eXtension
# lasx = yes/no --- -mlasx --- Use Loongson Advanced SIMD eXtension
# relaxedsimd = y/n --- -mrelaxed-simd --- Use WebAssembly relaxed SIMD extension
# syzygy = yes/no --- -DNO_TABLEBASES --- Support Syzygy tablebase probing
#
# Note that Makefile is space sensitive, so when adding new architectures
# or modifying existing flags, you have to make sure there are no extra spaces
@@ -143,16 +156,23 @@ endif
# explicitly check for the list of supported architectures (as listed with make help),
# the user can override with `make ARCH=x86-64-avx512icl SUPPORTED_ARCH=true`
ifeq ($(ARCH), $(filter $(ARCH), \
x86-64-avx512icl x86-64-vnni512 x86-64-avx512 x86-64-avxvnni \
x86-64-universal x86-64-avx512icl x86-64-vnni512 x86-64-avx512 x86-64-avxvnni \
x86-64-bmi2 x86-64-avx2 x86-64-sse41-popcnt x86-64-modern x86-64-ssse3 x86-64-sse3-popcnt \
x86-64 x86-32-sse41-popcnt x86-32-sse2 x86-32 ppc-64 ppc-64-altivec ppc-64-vsx ppc-32 e2k \
armv7 armv7-neon armv8 armv8-dotprod apple-silicon general-64 general-32 riscv64 \
loongarch64 loongarch64-lsx loongarch64-lasx))
armv7 armv7-neon armv8 armv8-dotprod arm64-universal apple-silicon general-64 general-32 riscv64 \
riscv64-rva23 riscv64-universal loongarch64 loongarch64-lsx loongarch64-lasx wasm32 wasm32-relaxed-simd))
SUPPORTED_ARCH=true
else
SUPPORTED_ARCH=false
endif
ifneq (,$(filter $(ARCH),x86-64-universal arm64-universal riscv64-universal))
UNIVERSAL_BUILD=true
else
UNIVERSAL_BUILD=false
endif
optimize = yes
debug = no
sanitize = none
@@ -175,8 +195,11 @@ vsx = no
neon = no
dotprod = no
arm_version = 0
rva23 = no
lsx = no
lasx = no
relaxedsimd = no
syzygy = yes
STRIP = strip
ifneq ($(shell which clang-format-20 2> /dev/null),)
@@ -363,6 +386,14 @@ ifeq ($(ARCH),armv8-dotprod)
arm_version = 8
endif
ifeq ($(ARCH),arm64-universal)
arch = armv8
prefetch = yes
popcnt = yes
neon = yes
arm_version = 8
endif
ifeq ($(ARCH),apple-silicon)
arch = arm64
prefetch = yes
@@ -372,6 +403,27 @@ ifeq ($(ARCH),apple-silicon)
arm_version = 8
endif
# WASM is largely compiled using emcc's x86 compatibility layer, with occasional
# WASM-specific intrinsics
ifeq ($(ARCH),wasm32)
arch = wasm32
sse = yes
sse2 = yes
ssse3 = yes
sse41 = yes
syzygy = no
endif
ifeq ($(ARCH),wasm32-relaxed-simd)
arch = wasm32
sse = yes
sse2 = yes
ssse3 = yes
sse41 = yes
relaxedsimd = yes
syzygy = no
endif
ifeq ($(ARCH),ppc-32)
arch = ppc
bits = 32
@@ -408,10 +460,16 @@ ifeq ($(findstring e2k,$(ARCH)),e2k)
popcnt = yes
endif
ifeq ($(ARCH),riscv64)
ifeq ($(findstring riscv64,$(ARCH)),riscv64)
arch = riscv64
endif
ifeq ($(ARCH),riscv64-rva23)
rva23 = yes
popcnt = yes
prefetch = yes
endif
ifeq ($(findstring loongarch64,$(ARCH)),loongarch64)
arch = loongarch64
prefetch = yes
@@ -458,12 +516,9 @@ ifeq ($(COMP),gcc)
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
endif
ifeq ($(ARCH),riscv64)
CXXFLAGS += -latomic
endif
else ifeq ($(arch),loongarch64)
CXXFLAGS += -latomic
else
else ifneq ($(arch),wasm32)
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
endif
@@ -472,9 +527,11 @@ ifeq ($(COMP),gcc)
LDFLAGS += -latomic
endif
ifneq ($(KERNEL),Darwin)
ifneq ($(TARGET_KERNEL),Darwin)
ifneq ($(arch),wasm32)
LDFLAGS += -Wl,--no-as-needed
endif
endif
endif
ifeq ($(target_windows),yes)
@@ -497,14 +554,14 @@ ifeq ($(COMP),mingw)
CXX=i686-w64-mingw32-c++-posix
endif
endif
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations -fconstexpr-ops-limit=500000000
endif
ifeq ($(COMP),icx)
comp=icx
CXX=icpx
CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \
-Wconditional-uninitialized -Wabi -Wdeprecated
-Wconditional-uninitialized -Wabi -Wdeprecated -fconstexpr-steps=500000000
endif
ifeq ($(COMP),clang)
@@ -515,9 +572,9 @@ ifeq ($(COMP),clang)
endif
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \
-Wconditional-uninitialized
-Wconditional-uninitialized -flax-vector-conversions=none -fconstexpr-steps=500000000
ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),)
ifeq ($(filter $(TARGET_KERNEL),Darwin OpenBSD FreeBSD),)
ifeq ($(target_windows),)
ifneq ($(RTLIB),compiler-rt)
LDFLAGS += -latomic
@@ -530,9 +587,6 @@ ifeq ($(COMP),clang)
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
endif
ifeq ($(ARCH),riscv64)
CXXFLAGS += -latomic
endif
else ifeq ($(arch),loongarch64)
CXXFLAGS += -latomic
else
@@ -541,13 +595,13 @@ ifeq ($(COMP),clang)
endif
endif
ifeq ($(KERNEL),Darwin)
CXXFLAGS += -mmacosx-version-min=10.15
LDFLAGS += -mmacosx-version-min=10.15
ifeq ($(TARGET_KERNEL),Darwin)
mac_target_flags := -mmacosx-version-min=10.15
ifneq ($(arch),any)
CXXFLAGS += -arch $(arch)
LDFLAGS += -arch $(arch)
mac_target_flags += -arch $(arch)
endif
CXXFLAGS += $(mac_target_flags)
LDFLAGS += $(mac_target_flags)
XCRUN = xcrun
endif
@@ -580,6 +634,7 @@ ifeq ($(COMP),ndk)
STRIP=llvm-strip
endif
endif
OBJCOPY=llvm-objcopy
LDFLAGS += -static-libstdc++
endif
@@ -610,7 +665,7 @@ else ifeq ($(comp),clang)
else
profile_make = gcc-profile-make
profile_use = gcc-profile-use
ifeq ($(KERNEL),Darwin)
ifeq ($(TARGET_KERNEL),Darwin)
EXTRAPROFILEFLAGS = -fvisibility=hidden
endif
endif
@@ -622,34 +677,35 @@ ifeq ($(COMP),gcc)
ifneq ($(gccisclang),)
profile_make = clang-profile-make
profile_use = clang-profile-use
CXXFLAGS += -fconstexpr-steps=500000000
else
CXXFLAGS += -Wstack-usage=128000
CXXFLAGS += -Wstack-usage=128000 -fno-ipa-cp-clone -fconstexpr-ops-limit=500000000
endif
else ifeq ($(COMP),mingw)
CXXFLAGS += -fno-ipa-cp-clone
endif
### On mingw use Windows threads, otherwise POSIX
ifneq ($(comp),mingw)
CXXFLAGS += -DUSE_PTHREADS
# On Android Bionic's C library comes with its own pthread implementation bundled in
ifneq ($(OS),Android)
# Haiku has pthreads in its libroot, so only link it in on other platforms
ifneq ($(KERNEL),Haiku)
ifneq ($(COMP),ndk)
LDFLAGS += -lpthread
# On Android Bionic's C library comes with its own pthread implementation bundled in
ifneq ($(OS),Android)
# Haiku has pthreads in its libroot, so only link it in on other platforms
ifneq ($(KERNEL),Haiku)
ifneq ($(COMP),ndk)
ifneq ($(arch),wasm32)
LDFLAGS += -lpthread
add_lrt = yes
ifeq ($(target_windows),yes)
add_lrt = no
endif
ifeq ($(KERNEL),Darwin)
add_lrt = no
endif
ifeq ($(add_lrt),yes)
LDFLAGS += -lrt
endif
add_lrt = yes
ifeq ($(target_windows),yes)
add_lrt = no
endif
ifeq ($(TARGET_KERNEL),Darwin)
add_lrt = no
endif
ifeq ($(add_lrt),yes)
LDFLAGS += -lrt
endif
endif
endif
endif
endif
@@ -679,7 +735,7 @@ ifeq ($(optimize),yes)
endif
endif
ifeq ($(KERNEL),Darwin)
ifeq ($(TARGET_KERNEL),Darwin)
ifeq ($(comp),$(filter $(comp),clang icx))
CXXFLAGS += -mdynamic-no-pic
endif
@@ -714,7 +770,7 @@ else
endif
ifeq ($(popcnt),yes)
ifeq ($(arch),$(filter $(arch),ppc64 ppc64-altivec ppc64-vsx armv7 armv8 arm64))
ifeq ($(arch),$(filter $(arch),ppc64 ppc64-altivec ppc64-vsx armv7 armv8 arm64 riscv64))
CXXFLAGS += -DUSE_POPCNT
else
CXXFLAGS += -msse3 -mpopcnt -DUSE_POPCNT
@@ -800,7 +856,7 @@ endif
ifeq ($(neon),yes)
CXXFLAGS += -DUSE_NEON=$(arm_version)
ifeq ($(KERNEL),Linux)
ifeq ($(TARGET_KERNEL),Linux)
ifneq ($(COMP),ndk)
ifneq ($(arch),armv8)
CXXFLAGS += -mfpu=neon
@@ -813,18 +869,32 @@ ifeq ($(dotprod),yes)
CXXFLAGS += -march=armv8.2-a+dotprod -DUSE_NEON_DOTPROD
endif
ifeq ($(rva23),yes)
CXXFLAGS += -march=rv64gcv1p0_zba_zbb_zbs_zicbop_zicond -DUSE_RVV
endif
ifeq ($(lasx),yes)
CXXFLAGS += -DUSE_LASX
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mlasx
endif
endif
ifeq ($(lsx),yes)
CXXFLAGS += -DUSE_LSX
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mlsx
endif
endif
ifeq ($(arch),wasm32)
CXXFLAGS += -pthread -msimd128 -DUSE_POPCNT -DNNUE_EMBEDDING_OFF -DUSE_SLOPPY_ATOMICS
LDFLAGS += -pthread -sINITIAL_MEMORY=64MB -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=3MB
ifeq ($(relaxedsimd),yes)
CXXFLAGS += -mrelaxed-simd
endif
endif
### 3.7 pext
ifeq ($(pext),yes)
CXXFLAGS += -DUSE_PEXT
@@ -833,19 +903,36 @@ ifeq ($(pext),yes)
endif
endif
### 3.8.1 Try to include git commit sha for versioning
GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8)
ifneq ($(GIT_SHA), )
CXXFLAGS += -DGIT_SHA=$(GIT_SHA)
### Syzygy tablebases
ifeq ($(syzygy),no)
CXXFLAGS += -DNO_TABLEBASES
endif
### 3.8.2 Try to include git commit date for versioning
GIT_DATE := $(shell git show -s --date=format:'%Y%m%d' --format=%cd HEAD 2>/dev/null)
ifneq ($(GIT_DATE), )
CXXFLAGS += -DGIT_DATE=$(GIT_DATE)
### 3.8.1 Try to include git info for versioning and avoid recompiles if nothing changes
BUILD_SHA_FILE := .build_sha.txt
BUILD_DATE_FILE := .build_date.txt
BUILD_DIFFINDEX_FILE := .build_diffindex.txt
GIT_SHA := $(shell git rev-parse HEAD 2>/dev/null | cut -c 1-8 || true)
GIT_DATE := $(shell git show -s --date=format:%Y%m%d --format=%cd HEAD 2>/dev/null || true)
GIT_DIFFINDEX := $(shell git diff-index --quiet HEAD -- 2>/dev/null; [ $$? -eq 1 ] && echo 1 || true)
COMPILER_DATE := $(shell date +%Y%m%d 2>/dev/null)
BUILD_DATE := $(if $(GIT_DATE),$(GIT_DATE),$(COMPILER_DATE))
define cache_file_contents
$(shell \
if [ ! -f "$(1)" ] || [ "$$(cat "$(1)" 2>/dev/null)" != "$(2)" ]; then \
printf '%s\n' "$(2)" > "$(1)"; \
fi)
endef
ifneq ($(filter $(MAKECMDGOALS),help strip install clean net objclean profileclean format config-sanity),$(MAKECMDGOALS))
_ := $(call cache_file_contents,$(BUILD_SHA_FILE),$(GIT_SHA))
_ := $(call cache_file_contents,$(BUILD_DATE_FILE),$(BUILD_DATE))
_ := $(call cache_file_contents,$(BUILD_DIFFINDEX_FILE),$(BUILD_DIFFINDEX))
endif
### 3.8.3 Try to include architecture
### 3.8.2 Try to include architecture
ifneq ($(ARCH), )
CXXFLAGS += -DARCH=$(ARCH)
endif
@@ -854,27 +941,41 @@ endif
### This is a mix of compile and link time options because the lto link phase
### needs access to the optimization flags.
ifeq ($(optimize),yes)
ifeq ($(debug), no)
ifeq ($(debug),no)
ifeq ($(arch),wasm32)
else ifneq ($(TARGET_KERNEL),Darwin)
LLD_BIN := $(shell command -v ld.lld 2>/dev/null)
ifeq ($(LLD_BIN),)
LLD_BIN := $(shell command -v lld 2>/dev/null)
endif
ifneq ($(LLD_BIN),)
ifeq ($(comp),clang)
LDFLAGS += -fuse-ld=lld
else ifeq ($(comp),gcc)
ifneq ($(gccisclang),)
LDFLAGS += -fuse-ld=lld
endif
endif
endif
endif
ifeq ($(comp),$(filter $(comp),clang icx))
CXXFLAGS += -flto=full
ifeq ($(comp),icx)
CXXFLAGS += -fwhole-program-vtables
endif
ifeq ($(target_windows),yes)
CXXFLAGS += -fuse-ld=lld
endif
LDFLAGS += $(CXXFLAGS)
# GCC and CLANG use different methods for parallelizing LTO and CLANG pretends to be
# GCC on some systems.
else ifeq ($(comp),gcc)
ifeq ($(gccisclang),)
CXXFLAGS += -flto -flto-partition=one
LDFLAGS += $(CXXFLAGS) -flto=jobserver
else
CXXFLAGS += -flto=full
LDFLAGS += $(CXXFLAGS)
endif
ifeq ($(gccisclang),)
CXXFLAGS += -flto -flto-partition=one
LDFLAGS += $(CXXFLAGS) -flto=jobserver
else
CXXFLAGS += -flto=full
LDFLAGS += $(CXXFLAGS)
endif
# To use LTO and static linking on Windows,
# the tool chain requires gcc version 10.1 or later.
@@ -892,6 +993,13 @@ ifeq ($(OS), Android)
LDFLAGS += -fPIE -pie
endif
### 3.11 Inline settings
ifeq ($(optimize), yes)
ifeq ($(comp), clang)
CXXFLAGS += -Xclang -mllvm -Xclang -inline-threshold=500
endif
endif
### ==========================================================================
### Section 4. Public Targets
### ==========================================================================
@@ -907,6 +1015,7 @@ help:
echo "help > Display architecture details" && \
echo "profile-build > standard build with profile-guided optimization" && \
echo "build > skip profile-guided optimization" && \
echo "macos-lipo > macOS x86-64 + Apple silicon universal binary" && \
echo "net > Download the default nnue nets" && \
echo "strip > Strip executable" && \
echo "install > Install executable" && \
@@ -915,6 +1024,7 @@ help:
echo "Supported archs:" && \
echo "" && \
echo "native > select the best architecture for the host processor (default)" && \
echo "x86-64-universal > x86 64-bit with automatic runtime selection of the best architecture" && \
echo "x86-64-avx512icl > x86 64-bit with minimum avx512 support of Intel Ice Lake or AMD Zen 4" && \
echo "x86-64-vnni512 > x86 64-bit with vnni 512bit support" && \
echo "x86-64-avx512 > x86 64-bit with avx512 support" && \
@@ -935,6 +1045,7 @@ help:
echo "ppc-32 > PPC 32-bit" && \
echo "armv7 > ARMv7 32-bit" && \
echo "armv7-neon > ARMv7 32-bit with popcnt and neon" && \
echo "arm64-universal > ARMv8 64-bit with automatic runtime selection of the best architecture" && \
echo "armv8 > ARMv8 64-bit with popcnt and neon" && \
echo "armv8-dotprod > ARMv8 64-bit with popcnt, neon and dot product support" && \
echo "e2k > Elbrus 2000" && \
@@ -942,6 +1053,8 @@ help:
echo "general-64 > unspecified 64-bit" && \
echo "general-32 > unspecified 32-bit" && \
echo "riscv64 > RISC-V 64-bit" && \
echo "riscv64-rva23 > RISC-V 64-bit RVA23 profile" && \
echo "riscv64-universal > RISC-V 64-bit with automatic runtime selection of the best architecture" && \
echo "loongarch64 > LoongArch 64-bit" && \
echo "loongarch64-lsx > LoongArch 64-bit with SIMD eXtension" && \
echo "loongarch64-lasx > LoongArch 64-bit with Advanced SIMD eXtension" && \
@@ -956,14 +1069,15 @@ help:
echo "" && \
echo "Simple examples. If you don't know what to do, you likely want to run one of: " && \
echo "" && \
echo "make -j profile-build ARCH=x86-64-avx2 # typically a fast compile for common systems " && \
echo "make -j profile-build ARCH=x86-64-sse41-popcnt # A more portable compile for 64-bit systems " && \
echo "make -j profile-build ARCH=x86-64 # A portable compile for 64-bit systems " && \
echo "make -j profile-build ARCH=native # a fast compile for local use" && \
echo "make -j profile-build ARCH=x86-64-avx2 # A portable compile for most x86-64 systems " && \
echo "make -j profile-build ARCH=x86-64 # A portable compile for x86-64 systems " && \
echo "" && \
echo "Advanced examples, for experienced users: " && \
echo "" && \
echo "make -j profile-build ARCH=x86-64-avxvnni" && \
echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc COMPCXX=g++-12.0" && \
echo "make -j profile-build ARCH=x86-64-avxvnni COMP=gcc CXX=g++-12.0" && \
echo 'make -j profile-build ARCH=x86-64-universal RUN_PREFIX="/path/to/sde -future --" CXX=g++-15' && \
echo "make -j build ARCH=x86-64-ssse3 COMP=clang" && \
echo ""
ifneq ($(SUPPORTED_ARCH), true)
@@ -972,7 +1086,7 @@ ifneq ($(SUPPORTED_ARCH), true)
endif
.PHONY: help analyze build profile-build strip install clean net \
.PHONY: help analyze build profile-build macos-lipo strip install clean net \
objclean profileclean config-sanity \
icx-profile-use icx-profile-make \
gcc-profile-use gcc-profile-make \
@@ -982,6 +1096,7 @@ endif
analyze: net config-sanity objclean
$(MAKE) -k ARCH=$(ARCH) COMP=$(COMP) $(OBJS)
ifeq ($(UNIVERSAL_BUILD),false)
build: net config-sanity
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) all
@@ -1000,6 +1115,84 @@ profile-build: net config-sanity objclean profileclean
@echo ""
@echo "Step 4/4. Deleting profile data ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean
endif
# Default network filename, read from evaluate.h's EvalFileDefaultName.
macos-lipo: NNUE_NET = $(shell grep -oE 'nn-[0-9a-f]+\.nnue' $(CURDIR)/evaluate.h | head -1)
macos-lipo:
@echo ""
@echo "Step 1/4. Building x86-64 slice (network shared from Apple-silicon slice) ..."
CXXFLAGS="-arch x86_64" $(MAKE) build ARCH=x86-64-universal COMP=$(COMP) MACOS_X86_SLICE=1
mv $(EXE) $(EXE)-x86_64
@echo ""
@echo "Step 2/4. Building Apple silicon slice ..."
$(MAKE) profile-build ARCH=apple-silicon COMP=$(COMP)
mv $(EXE) $(EXE)-arm64
@echo ""
@echo "Step 3/4. Combining slices and locating the embedded network ..."
lipo -create $(EXE)-x86_64 $(EXE)-arm64 -output $(EXE)
$(SHELL) universal/patch_x86_slice.sh $(EXE) $(EXE)-x86_64 $(NNUE_NET)
@echo ""
@echo "Step 4/4. Re-combining slices with the patched x86-64 slice ..."
lipo -create $(EXE)-x86_64 $(EXE)-arm64 -output $(EXE)
@rm -f $(EXE)-x86_64 $(EXE)-arm64
@echo "Universal macOS binary built: $(EXE)"
ifeq ($(TARGET_KERNEL),Darwin)
# Apple uses ld64 which doesn't have --save-temps
LTO_OBJ_SUFFIX := .lto.o
SAVE_TEMPS := -Wl,-object_path_lto,stockfish.lto.o
else ifeq ($(comp), clang)
LTO_OBJ_SUFFIX := .lto.o
ifeq ($(target_windows),yes)
SAVE_TEMPS := -Wl,--lto-emit-asm # ld.lld doesn't have a save temps functionality
use_lto_emit_asm := yes
else
SAVE_TEMPS := -Wl,--save-temps
endif
else
LTO_OBJ_SUFFIX := .ltrans0.ltrans.o
SAVE_TEMPS := -save-temps
endif
ifeq ($(arch),armv8)
ASM_FLAGS := -mcpu=neoverse-v2
endif
MODNAME := $(subst -,_,$(notdir $(CURDIR)))
universal-object-pgo: objclean profileclean universalclean
@echo ""
@echo "Step 1/4. Building instrumented executable ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(ENV_LDFLAGS) $(NNUE_EMBED_OBJ)' $(profile_make)
@echo ""
@echo "Step 2/4. Running benchmark for pgo-build ..."
$(PGOBENCH) > PGOBENCH.out 2>&1
tail -n 4 PGOBENCH.out
@echo ""
@echo "Step 3/4. Building optimized object files ..."
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(SAVE_TEMPS) $(NNUE_EMBED_OBJ)' $(profile_use)
@echo ""
ifeq ($(use_lto_emit_asm),yes)
@echo "Step 4/4. Renaming ctors and extracting LTO-optimized relocatable object ..."
awk -v MODNAME=$(MODNAME) -f ../../universal/rewrite_asm_sections.awk *.lto.s > renamed.s
# -mcpu=neoverse-v2 (or another CPU supporting dotprod) is required for clang
# to not complain when assembling
$(CXX) -c renamed.s -o stockfish.o $(ASM_FLAGS)
else
@echo "Step 4/4. Extracting LTO-optimized relocatable object ..."
cp "$(basename $(EXE))$(LTO_OBJ_SUFFIX)" stockfish.o
endif
universal-object-nopgo: objclean universalclean
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) ENV_LDFLAGS='$(SAVE_TEMPS) $(NNUE_EMBED_OBJ)' all
ifeq ($(use_lto_emit_asm),yes)
awk -v MODNAME=$(MODNAME) -f ../../universal/rewrite_asm_sections.awk *.lto.s > renamed.s
$(CXX) -c renamed.s -o stockfish.o $(ASM_FLAGS)
else
cp "$(basename $(EXE))$(LTO_OBJ_SUFFIX)" stockfish.o
endif
strip:
$(STRIP) $(EXE)
@@ -1010,12 +1203,12 @@ install:
$(STRIP) $(BINDIR)/$(EXE)
# clean all
clean: objclean profileclean
clean: objclean profileclean universalclean
@rm -f .depend *~ core
# clean binaries and objects
objclean:
@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o
@rm -f stockfish stockfish.exe *.o ./syzygy/*.o ./nnue/*.o ./nnue/features/*.o $(BUILD_SHA_FILE) $(BUILD_DATE_FILE) $(BUILD_DIFFINDEX_FILE)
# clean auxiliary profiling files
profileclean:
@@ -1027,12 +1220,16 @@ profileclean:
@rm -f stockfish.res
@rm -f ./-lstdc++.res
universalclean:
@rm -rf $(TEMP_DIR)
@rm -f universal/network_dump.inc
# evaluation network (nnue)
net:
@$(SHELL) ../scripts/net.sh
format:
$(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file
$(CLANG-FORMAT) -i $(SRCS) $(OTHER_SRCS) $(HEADERS) -style=file
### ==========================================================================
### Section 5. Private Targets
@@ -1070,6 +1267,7 @@ config-sanity: net
echo "arm_version: '$(arm_version)'" && \
echo "lsx: '$(lsx)'" && \
echo "lasx: '$(lasx)'" && \
echo "syzygy: '$(syzygy)'" && \
echo "target_windows: '$(target_windows)'" && \
echo "" && \
echo "Flags:" && \
@@ -1085,7 +1283,8 @@ config-sanity: net
(test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "e2k" || \
test "$(arch)" = "armv7" || test "$(arch)" = "armv8" || test "$(arch)" = "arm64" || \
test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64") && \
test "$(arch)" = "riscv64" || test "$(arch)" = "loongarch64" || \
test "$(arch)" = "wasm32") && \
(test "$(bits)" = "32" || test "$(bits)" = "64") && \
(test "$(prefetch)" = "yes" || test "$(prefetch)" = "no") && \
(test "$(popcnt)" = "yes" || test "$(popcnt)" = "no") && \
@@ -1104,6 +1303,7 @@ config-sanity: net
(test "$(neon)" = "yes" || test "$(neon)" = "no") && \
(test "$(lsx)" = "yes" || test "$(lsx)" = "no") && \
(test "$(lasx)" = "yes" || test "$(lasx)" = "no") && \
(test "$(syzygy)" = "yes" || test "$(syzygy)" = "no") && \
(test "$(comp)" = "gcc" || test "$(comp)" = "icx" || test "$(comp)" = "mingw" || \
test "$(comp)" = "clang" || test "$(comp)" = "armv7a-linux-androideabi16-clang" || \
test "$(comp)" = "aarch64-linux-android21-clang")
@@ -1111,9 +1311,11 @@ config-sanity: net
$(EXE): $(OBJS)
+$(CXX) -o $@ $(OBJS) $(LDFLAGS)
# Force recompilation to ensure version info is up-to-date
misc.o: FORCE
FORCE:
%.o: %.cpp
$(strip $(CXX) $(CPPFLAGS) $(CXXFLAGS)) -c -o $@ $<
misc.o: misc.cpp $(BUILD_SHA_FILE) $(BUILD_DATE_FILE) $(BUILD_DIFFINDEX_FILE)
$(strip $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(if $(GIT_SHA),-DGIT_SHA=$(GIT_SHA)) $(if $(GIT_DATE),-DGIT_DATE=$(GIT_DATE))) $(if $(GIT_DIFFINDEX),-DGIT_DIFFINDEX=$(GIT_DIFFINDEX)) -c -o $@ $<
clang-profile-make:
$(MAKE) ARCH=$(ARCH) COMP=$(COMP) \
@@ -1160,5 +1362,172 @@ icx-profile-use:
-@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
ifeq (, $(filter $(MAKECMDGOALS), help strip install clean net objclean profileclean format config-sanity))
ifeq ($(UNIVERSAL_BUILD),false)
-include .depend
endif
endif
### ==========================================================================
### Section 6. Universal binary driver
### ==========================================================================
#
# Usage:
# make -j ARCH=x86-64-universal build # non-PGO
# make -j ARCH=x86-64-universal profile-build # PGO (use RUN_PREFIX for intel SDE)
# make -j ARCH=arm64-universal build
TEMP_DIR := $(CURDIR)/temp_builds
UNIVERSAL_EXE := $(CURDIR)/$(EXE)
ifeq ($(UNIVERSAL_BUILD),true)
UNIVERSAL_SRC_DIR := $(CURDIR)/universal
NNUE_EMBED_OBJ := $(TEMP_DIR)/nnue_embed.o
NET_SENTINEL := $(TEMP_DIR)/.net-done
ifeq ($(arch),x86_64)
UNIVERSAL_ARCHES := x86-64 x86-64-sse41-popcnt x86-64-avx2 x86-64-bmi2 \
x86-64-avxvnni x86-64-avx512 x86-64-vnni512 x86-64-avx512icl
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_x86.o
UNIVERSAL_ENTRY_SRC := $(UNIVERSAL_SRC_DIR)/entry_x86.cpp
BASELINE_ARCH := x86-64
else ifeq ($(arch),riscv64)
UNIVERSAL_ARCHES := riscv64 riscv64-rva23
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_riscv64.o
UNIVERSAL_ENTRY_SRC := $(UNIVERSAL_SRC_DIR)/entry_riscv64.cpp
BASELINE_ARCH := riscv64
else
UNIVERSAL_ARCHES := armv8 armv8-dotprod
UNIVERSAL_ENTRY_OBJ := $(TEMP_DIR)/entry_arm64.o
UNIVERSAL_ENTRY_SRC := $(UNIVERSAL_SRC_DIR)/entry_arm64.cpp
BASELINE_ARCH := armv8
endif
# Top-level tracked items in src/ (directories and files)
TRACKED_TOP := $(sort $(foreach f,$(SRCS) $(HEADERS) Makefile incbin,$(firstword $(subst /, ,$(f)))))
# Baseline build must come first in the final link (Windows --allow-multiple-definition
# uses the first copy of inline duplicates)
ARCH_OBJS := $(TEMP_DIR)/$(BASELINE_ARCH)/stockfish.o \
$(patsubst %,$(TEMP_DIR)/%/stockfish.o,$(filter-out $(BASELINE_ARCH),$(UNIVERSAL_ARCHES)))
UNIVERSAL_FINAL_FLAGS := -fno-exceptions -Os \
-Wno-c++26-extensions -Wno-c23-extensions # suppress #embed warnings
ifeq ($(TARGET_KERNEL),Darwin)
UNIVERSAL_FINAL_FLAGS += $(mac_target_flags)
else ifeq ($(TARGET_KERNEL),Linux)
UNIVERSAL_FINAL_FLAGS += -static-libstdc++ -static-libgcc
ifeq ($(COMP),ndk)
UNIVERSAL_FINAL_FLAGS += -static
else
UNIVERSAL_FINAL_FLAGS += -lrt -lpthread
endif
else # windows
UNIVERSAL_FINAL_FLAGS += -Wl,--allow-multiple-definition -static
ifeq ($(COMP),clang)
UNIVERSAL_FINAL_FLAGS += -lpthread
endif
endif
# $(call arch-namespace,x86-64-avx2) => Stockfish_x86_64_avx2
arch-suffix = $(subst -,_,$(1))
arch-namespace = Stockfish_$(call arch-suffix,$(1))
# Itanium ABI mangled name for Stockfish_<suffix>::main(int, char**)
arch-mangled = _ZN$(shell printf %s '$(call arch-namespace,$(1))' | wc -c | tr -d ' ')$(call arch-namespace,$(1))4mainEiPPc
# Mach-O initializer section name: strip "x86-64" or "x86-64-" and prefix with "_i_"
# (Mach-O section names are restricted to <=16 chars, hence this naming scheme)
arch-mac-section = _i_$(subst -,_,$(patsubst x86-64,,$(patsubst x86-64-%,%,$(1))))
ifeq ($(TARGET_KERNEL),Darwin)
arch-defmain = -Wl,-alias,_$(call arch-mangled,$(1)),_main
# lld-link, used on Windows+clang, doesn't accept --defsym, so
# we define main directly in main.cpp instead and delete it later.
else ifeq ($(target_windows)+$(COMP),yes+clang)
arch-defmain = -DUNIVERSAL_NEEDS_MAIN_SHIM
else
arch-defmain = -Wl,--defsym=main=$(call arch-mangled,$(1))
endif
EMBED_DIR_SUPPORTED := $(shell echo 'int main(){}' | $(CXX) -x c++ --embed-dir=. -o /dev/null -c - 2>/dev/null && echo 1 || echo 0)
ifeq ($(EMBED_DIR_SUPPORTED),1)
EMBED_DIR = --embed-dir=$(CURDIR)
endif
# macos-lipo sets MACOS_X86_SLICE=1 when building the x86-64 slice so that the
# slice reads the network out of the Apple-silicon slice instead of embedding
# its own copy (see universal/nnue_embed.cpp and universal/patch_x86_slice.sh).
ifeq ($(MACOS_X86_SLICE),1)
SLICE_DEF := -DUNIVERSAL_BINARY_MACOS_X86_SLICE
endif
arch-cxxflags = $(EMBED_DIR) -DStockfish=$(call arch-namespace,$(1)) -DUNIVERSAL_BINARY $(SLICE_DEF) \
$(call arch-defmain,$(1)) -Wno-c++26-extensions -Wno-c23-extensions
UOBJ_TGT ?= universal-object-pgo
ifeq ($(COMP),clang)
OBJCOPY ?= llvm-objcopy
else
OBJCOPY ?= objcopy
endif
.PHONY: build profile-build universal-clean
build: UOBJ_TGT := universal-object-nopgo
build: $(UNIVERSAL_EXE)
profile-build: UOBJ_TGT := universal-object-pgo
profile-build: $(UNIVERSAL_EXE)
$(TEMP_DIR):
@mkdir -p $@
# Download nnue files once in src/ (reused by every sub-arch via --embed-dir).
$(NET_SENTINEL): | $(TEMP_DIR)
@cd $(CURDIR) && $(SHELL) ../scripts/net.sh $(EMBED_DIR_SUPPORTED)
@touch $@
$(NNUE_EMBED_OBJ): $(UNIVERSAL_SRC_DIR)/nnue_embed.cpp $(NET_SENTINEL) | $(TEMP_DIR)
$(CXX) -O2 -std=c++20 -Wno-c++26-extensions $(SLICE_DEF) $(mac_target_flags) $(EXTRACXXFLAGS) $(EMBED_DIR) -c $< -o $@
$(UNIVERSAL_ENTRY_OBJ): $(UNIVERSAL_ENTRY_SRC) | $(TEMP_DIR)
$(CXX) -O2 -std=c++20 $(mac_target_flags) $(EXTRACXXFLAGS) -c $< -o $@
# Symlink tracked top-level items from src/ into temp_builds/<arch>/.
$(TEMP_DIR)/%/.setup: | $(TEMP_DIR)
@rm -rf $(TEMP_DIR)/$*
@mkdir -p $(TEMP_DIR)/$*
@$(foreach f,$(TRACKED_TOP),ln -s $(CURDIR)/$(f) $(TEMP_DIR)/$*/$(f);)
@touch $@
# Per-arch stockfish.o
.SECONDARY: $(patsubst %,$(TEMP_DIR)/%/.setup,$(UNIVERSAL_ARCHES))
$(TEMP_DIR)/%/stockfish.o: $(TEMP_DIR)/%/.setup $(NNUE_EMBED_OBJ) $(NET_SENTINEL)
+ENV_CXXFLAGS='$(ENV_CXXFLAGS) $(call arch-cxxflags,$*)' \
$(MAKE) -C $(TEMP_DIR)/$* $(UOBJ_TGT) \
ARCH=$* CXX=$(CXX) COMP=$(COMP) \
NNUE_EMBED_OBJ=$(NNUE_EMBED_OBJ)
ifeq ($(TARGET_KERNEL),Darwin)
# Rename the initializer section
ld -r -rename_section __DATA __mod_init_func __DATA $(call arch-mac-section,$*) $@ -o $@.tmp
mv $@.tmp $@
else
# Drop COMDAT groups; clang LTO leaves them around internalized LOCAL
# template instantiations which causes problems at the final link
$(OBJCOPY) -R .group $@
ifeq ($(use_lto_emit_asm),yes)
# Delete per-arch main shim
$(OBJCOPY) -N main $@
else
# Rename .init_array (Linux) or .ctors (Windows) so we can manually invoke them
$(OBJCOPY) --rename-section .init_array=$(call arch-suffix,$*)_init $@
$(OBJCOPY) --rename-section .ctors=$(call arch-suffix,$*)_init $@
# Make the array inert
$(OBJCOPY) --set-section-flags $(call arch-suffix,$*)_init=alloc,data $@
endif
endif
$(UNIVERSAL_EXE): $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS)
$(CXX) -o $@ $(UNIVERSAL_ENTRY_OBJ) $(NNUE_EMBED_OBJ) $(ARCH_OBJS) $(UNIVERSAL_FINAL_FLAGS) $(EXTRALDFLAGS)
@echo "Universal binary built: $@"
endif # UNIVERSAL_BUILD
+199
View File
@@ -0,0 +1,199 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "attacks.h"
#include <array>
#include "misc.h"
namespace Stockfish::Attacks {
#ifdef USE_DUAL_HYPERBOLA_QUINT
alignas(64) DualMagic DualMagics[SQUARE_NB];
#endif
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
namespace {
#ifndef USE_DUAL_HYPERBOLA_QUINT
alignas(64) Magic Magics[SQUARE_NB][2];
#endif
}
[[maybe_unused]] static Bitboard line_mask(Square sq, Direction d1, Direction d2) {
Bitboard mask = 0, dest;
for (Direction d : {d1, d2})
{
Square s = sq;
while ((dest = safe_destination(s, d)))
{
mask |= dest;
s += d;
}
}
return mask;
}
#ifdef USE_HYPERBOLA_QUINT
static void init_magics(Magic magics[][2]) {
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
Magic& rook = magics[s][ROOK - BISHOP];
rook.mask1 = line_mask(s, NORTH, SOUTH);
rook.mask2 = line_mask(s, EAST, WEST);
Magic& bishop = magics[s][BISHOP - BISHOP];
bishop.mask1 = line_mask(s, NORTH_EAST, SOUTH_WEST);
bishop.mask2 = line_mask(s, NORTH_WEST, SOUTH_EAST);
}
}
#elif defined(USE_DUAL_HYPERBOLA_QUINT)
// Sliding attacks within a rank, indexed by the slider's file and the
// 8-bit rank occupancy, yielding the 8-bit attack set on that rank
constexpr auto RankAttacks = []() {
std::array<std::array<u8, 256>, FILE_NB> table{};
for (int file = 0; file < 8; ++file)
for (int occ = 0; occ < 256; ++occ)
table[file][occ] = u8(sliding_attack(ROOK, Square(file), occ));
return table;
}();
static void init_dual_magics(DualMagic magics[]) {
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
DualMagic& m = magics[s];
m.maskFile = line_mask(s, NORTH, SOUTH);
m.maskDiag = line_mask(s, NORTH_EAST, SOUTH_WEST);
m.maskNone = 0;
m.maskAntidiag = line_mask(s, NORTH_WEST, SOUTH_EAST);
m.r = square_bb(s) * 2;
m.rr = square_bb(Square(63 - int(s))) * 2;
m.rankAttacksLookup = RankAttacks[int(file_of(s))].data();
m.shift = 8 * int(rank_of(s));
}
}
#else
namespace {
void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) {
int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
{728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
Bitboard occupancy[4096];
int epoch[4096] = {}, cnt = 0;
Bitboard reference[4096] = {};
int size = 0;
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));
Magic& m = magics[s][pt - BISHOP];
Bitboard attacks = sliding_attack(pt, s, 0);
m.mask = attacks & ~edges;
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size;
size = 0;
Bitboard b = 0;
do
{
occupancy[size] = b;
reference[size] = sliding_attack(pt, s, b);
size++;
b = (b - m.mask) & m.mask;
} while (b);
PRNG rng(seeds[Is64Bit][rank_of(s)]);
for (int i = 0; i < size;)
{
for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;)
m.magic = rng.sparse_rand<Bitboard>();
for (++cnt, i = 0; i < size; ++i)
{
unsigned idx = m.index(occupancy[i]);
if (epoch[idx] < cnt)
{
epoch[idx] = cnt;
m.attacks[idx] = reference[i];
}
else if (m.attacks[idx] != reference[i])
break;
}
}
}
}
#if !defined(USE_DUAL_HYPERBOLA_QUINT) && !defined(USE_HYPERBOLA_QUINT)
static std::array<Bitboard, 0x19000> RookTable;
static std::array<Bitboard, 0x1480> BishopTable;
#endif
}
#endif
void init() {
#ifdef USE_HYPERBOLA_QUINT
init_magics(Magics);
#elif defined(USE_DUAL_HYPERBOLA_QUINT)
init_dual_magics(DualMagics);
#else
init_magics(ROOK, RookTable.data(), Magics);
init_magics(BISHOP, BishopTable.data(), Magics);
#endif
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{
for (PieceType pt : {BISHOP, ROOK})
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
{
if (PseudoAttacks[pt][s1] & s2)
{
LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
BetweenBB[s1][s2] =
(attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
RayPassBB[s1][s2] =
attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2);
}
BetweenBB[s1][s2] |= s2;
}
}
}
#ifndef USE_DUAL_HYPERBOLA_QUINT
const Magic& magic(Square s, PieceType pt) {
assert((pt == BISHOP || pt == ROOK) && is_ok(s));
return Magics[s][pt - BISHOP];
}
#endif
} // namespace Stockfish::Attacks
+360
View File
@@ -0,0 +1,360 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ATTACKS_H_INCLUDED
#define ATTACKS_H_INCLUDED
#include <cassert>
#include <array>
#include <initializer_list>
#include <utility>
#include "types.h"
#include "bitboard.h"
#ifdef __aarch64__
#include <arm_acle.h>
#define USE_HYPERBOLA_QUINT
#elif defined(__loongarch__) && __loongarch_grlen == 64
#define USE_HYPERBOLA_QUINT
#elif defined(USE_AVX2)
#include <immintrin.h>
#define USE_DUAL_HYPERBOLA_QUINT
#endif
namespace Stockfish::Attacks {
void init();
#ifdef USE_HYPERBOLA_QUINT
inline Bitboard reverse_bb(Bitboard bb) {
#if __has_builtin(__builtin_bitreverse64)
return __builtin_bitreverse64(bb);
#else
#ifdef __aarch64__
#if defined(__GNUC__) && !defined(__clang__) \
&& (__GNUC__ < 12 || (__GNUC__ == 12 && __GNUC_MINOR__ < 2))
// no rbit in arm_acle.h
Bitboard out;
asm("rbit %0, %1" : "=r"(out) : "r"(bb));
return out;
#else
return __rbitll(bb);
#endif
#else // loongarch
Bitboard out;
asm("bitrev.d %0, %1" : "=r"(out) : "r"(bb));
return out;
#endif
#endif
}
// Hyperbola quintessence implementation for ARM, thanks to the availability of an
// efficient bit reversal instruction.
// See https://www.chessprogramming.org/Hyperbola_Quintessence
struct Magic {
// For rooks: file attacks, rank attacks. For bishops: diagonal/antidiagonal
Bitboard mask1, mask2;
Bitboard hyperbola(Square s, Bitboard occupied, Bitboard mask) const {
Bitboard o = occupied & mask;
Bitboard fwd = o - square_bb(s);
Bitboard rev = reverse_bb(o) - square_bb(Square(63 - int(s)));
return (fwd ^ reverse_bb(rev)) & mask;
}
Bitboard attacks_bb(Square s, Bitboard occupied) const {
return hyperbola(s, occupied, mask1) | hyperbola(s, occupied, mask2);
}
};
const Magic& magic(Square s, PieceType pt);
#elif defined(USE_DUAL_HYPERBOLA_QUINT)
struct alignas(32) DualMagic {
// file, diagonal, unused, antidiagonal
Bitboard maskFile, maskDiag, maskNone, maskAntidiag;
// Precomputed 2 * square_bb(sq), 2 * reverse(square_bb(sq))
Bitboard r, rr;
const u8* RESTRICT rankAttacksLookup;
// 8 * rank_of(sq)
int shift;
// We always compute [bishop, rook] attacks at once, then rely on
// compiler's DCE and CSE to eliminate unneeded re-computations or extractions.
//
// When using hyperbola quintessence, file, diagonal and antidiagonal attacks
// can use a byte reversal rather than a full bit reversal (because all squares
// reside in different bytes). Rank attacks cannot. Thus, for rank attacks
// only, we use a compact lookup table indexed by the 8 bits of the rank's occupancy.
std::pair<Bitboard, Bitboard> both_attacks_bb(Bitboard occupied) const {
// Byteswap within 128-bit elements
const auto bswap = [](__m256i v) {
return _mm256_shuffle_epi8(v, _mm256_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15));
};
// Each lane contains a mask and we follow the same HQ algorithm as
// given above in the ARM64 code path
const __m256i mask = _mm256_load_si256(reinterpret_cast<const __m256i*>(this));
const __m256i rs = _mm256_set1_epi64x(r);
const __m256i rrs = _mm256_set1_epi64x(rr);
__m256i o = _mm256_and_si256(mask, _mm256_set1_epi64x(occupied));
__m256i fwd = _mm256_sub_epi64(o, rs);
__m256i rev = bswap(_mm256_sub_epi64(bswap(o), rrs));
__m256i result = _mm256_and_si256(_mm256_xor_si256(fwd, rev), mask);
// Lane 0: rook attacks (file only); lane 1: bishop attacks
__m128i rookBishop =
_mm_or_si128(_mm256_extracti128_si256(result, 1), _mm256_castsi256_si128(result));
Bitboard rowOccupancy = rankAttacksLookup[(occupied >> shift) & 0xff];
Bitboard rankAttacks = rowOccupancy << shift;
// [bishop, rook]
return {_mm_extract_epi64(rookBishop, 1), _mm_cvtsi128_si64(rookBishop) + rankAttacks};
}
};
extern DualMagic DualMagics[SQUARE_NB];
inline const DualMagic& dual_magic(Square s) { return DualMagics[s]; }
#else
// Magic holds all magic bitboards relevant data for a single square
struct Magic {
Bitboard mask;
Bitboard* attacks;
Bitboard magic;
unsigned shift;
// Compute the attack's index using the 'magic bitboards' approach
unsigned index(Bitboard occupied) const {
if (Is64Bit)
return unsigned(((occupied & mask) * magic) >> shift);
unsigned lo = unsigned(occupied) & unsigned(mask);
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
}
Bitboard attacks_bb([[maybe_unused]] Square s, Bitboard occupied) const {
return attacks[index(occupied)];
}
};
const Magic& magic(Square s, PieceType pt);
#endif
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
inline Bitboard line_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2));
return LineBB[s1][s2];
}
inline Bitboard between_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2));
return BetweenBB[s1][s2];
}
inline Bitboard ray_pass_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2));
return RayPassBB[s1][s2];
}
// Returns the bitboard of target square for the given step
// from the given square. If the step is off the board, returns empty bitboard.
constexpr Bitboard safe_destination(Square s, int step) {
constexpr auto abs = [](int v) { return v < 0 ? -v : v; };
Square to = Square(s + step);
return is_ok(to) && abs(file_of(s) - file_of(to)) <= 2 ? square_bb(to) : Bitboard(0);
}
constexpr Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
Bitboard attacks = 0, dest = 0;
constexpr Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
constexpr Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
{
Square s = sq;
while ((dest = safe_destination(s, d)))
{
attacks |= dest;
s += d;
if (occupied & dest)
{
break;
}
}
}
return attacks;
}
constexpr Bitboard knight_attack(Square sq) {
Bitboard b = {};
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17})
b |= safe_destination(sq, step);
return b;
}
constexpr Bitboard king_attack(Square sq) {
Bitboard b = {};
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9})
b |= safe_destination(sq, step);
return b;
}
constexpr Bitboard pseudo_attacks(PieceType pt, Square sq) {
switch (pt)
{
case PieceType::ROOK :
case PieceType::BISHOP :
return sliding_attack(pt, sq, 0);
case PieceType::QUEEN :
return sliding_attack(PieceType::ROOK, sq, 0) | sliding_attack(PieceType::BISHOP, sq, 0);
case PieceType::KNIGHT :
return knight_attack(sq);
case PieceType::KING :
return king_attack(sq);
default :
assert(false);
return 0;
}
}
inline constexpr auto PseudoAttacks = []() constexpr {
std::array<std::array<Bitboard, SQUARE_NB>, PIECE_TYPE_NB> attacks{};
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{
attacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
attacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
attacks[KING][s1] = pseudo_attacks(KING, s1);
attacks[KNIGHT][s1] = pseudo_attacks(KNIGHT, s1);
attacks[QUEEN][s1] = attacks[BISHOP][s1] = pseudo_attacks(BISHOP, s1);
attacks[QUEEN][s1] |= attacks[ROOK][s1] = pseudo_attacks(ROOK, s1);
}
return attacks;
}();
inline constexpr auto PawnPushOrAttacks = []() constexpr {
std::array<std::array<Bitboard, SQUARE_NB>, COLOR_NB> attacks{};
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{
attacks[WHITE][s1] = pawn_single_push_bb(WHITE, square_bb(s1)) | PseudoAttacks[WHITE][s1];
attacks[BLACK][s1] = pawn_single_push_bb(BLACK, square_bb(s1)) | PseudoAttacks[BLACK][s1];
}
return attacks;
}();
// Returns the pseudo attacks of the given piece type
// assuming an empty board.
template<PieceType Pt>
inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) {
assert((Pt != PAWN || c < COLOR_NB) && is_ok(s));
return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s];
}
// Returns the attacks by the given piece
// assuming the board is occupied according to the passed Bitboard.
// Sliding piece attacks do not continue past an occupied square.
template<PieceType Pt>
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
assert(Pt != PAWN && is_ok(s));
#ifdef USE_DUAL_HYPERBOLA_QUINT
const auto [bishop, rook] = dual_magic(s).both_attacks_bb(occupied);
switch (Pt)
{
case BISHOP :
return bishop;
case ROOK :
return rook;
case QUEEN :
return bishop | rook;
default :
return PseudoAttacks[Pt][s];
}
#else
switch (Pt)
{
case BISHOP :
case ROOK :
return magic(s, Pt).attacks_bb(s, occupied);
case QUEEN :
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
default :
return PseudoAttacks[Pt][s];
}
#endif
}
inline std::pair<Bitboard, Bitboard> both_attacks_bb(Square s, Bitboard occupied) {
#ifdef USE_DUAL_HYPERBOLA_QUINT
return dual_magic(s).both_attacks_bb(occupied);
#else
return {attacks_bb<BISHOP>(s, occupied), attacks_bb<ROOK>(s, occupied)};
#endif
}
// Returns the attacks by the given piece
// assuming the board is occupied according to the passed Bitboard.
// Sliding piece attacks do not continue past an occupied square.
inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
assert(pt != PAWN && is_ok(s));
switch (pt)
{
case BISHOP :
return attacks_bb<BISHOP>(s, occupied);
case ROOK :
return attacks_bb<ROOK>(s, occupied);
case QUEEN :
return attacks_bb<QUEEN>(s, occupied);
default :
return PseudoAttacks[pt][s];
}
}
inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) {
return type_of(pc) == PAWN ? PseudoAttacks[color_of(pc)][s]
: attacks_bb(type_of(pc), s, occupied);
}
} // namespace Stockfish::Attacks
#endif // #ifndef ATTACKS_H_INCLUDED
+5 -13
View File
@@ -18,6 +18,7 @@
#include "benchmark.h"
#include "numa.h"
#include "misc.h"
#include <cstdlib>
#include <fstream>
@@ -64,6 +65,7 @@ const std::vector<std::string> Defaults = {
"r3k2r/3nnpbp/q2pp1p1/p7/Pp1PPPP1/4BNN1/1P5P/R2Q1RK1 w kq - 0 16",
"3Qb1k1/1r2ppb1/pN1n2q1/Pp1Pp1Pr/4P2p/4BP2/4B1R1/1R5K b - - 11 40",
"4k3/3q1r2/1N2r1b1/3ppN2/2nPP3/1B1R2n1/2R1Q3/3K4 w - - 5 1",
"1r6/1P4bk/3qr1p1/N6p/3pp2P/6R1/3Q1PP1/1R4K1 w - - 1 42",
// Positions with high numbers of changed threats
"k7/2n1n3/1nbNbn2/2NbRBn1/1nbRQR2/2NBRBN1/3N1N2/7K w - - 0 1",
@@ -482,15 +484,8 @@ BenchmarkSetup setup_benchmark(std::istream& is) {
float totalTime = 0;
for (const auto& game : BenchmarkPositions)
{
int ply = 1;
for (int i = 0; i < static_cast<int>(game.size()); ++i)
{
const float correctedTime = float(getCorrectedTime(ply));
totalTime += correctedTime;
ply += 1;
}
}
for (usize i = 0; i < game.size(); ++i)
totalTime += float(getCorrectedTime(int(i + 1)));
float timeScaleFactor = static_cast<float>(desiredTimeS * 1000) / totalTime;
@@ -501,11 +496,8 @@ BenchmarkSetup setup_benchmark(std::istream& is) {
for (const std::string& fen : game)
{
setup.commands.emplace_back("position fen " + fen);
const int correctedTime = static_cast<int>(getCorrectedTime(ply) * timeScaleFactor);
const int correctedTime = static_cast<int>(getCorrectedTime(ply++) * timeScaleFactor);
setup.commands.emplace_back("go movetime " + std::to_string(correctedTime));
ply += 1;
}
}
-145
View File
@@ -18,31 +18,8 @@
#include "bitboard.h"
#include <algorithm>
#include <bitset>
#include <initializer_list>
#include "misc.h"
namespace Stockfish {
uint8_t PopCnt16[1 << 16];
uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
Bitboard LineBB[SQUARE_NB][SQUARE_NB];
Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
alignas(64) Magic Magics[SQUARE_NB][2];
namespace {
Bitboard RookTable[0x19000]; // To store rook attacks
Bitboard BishopTable[0x1480]; // To store bishop attacks
void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]);
}
// Returns an ASCII representation of a bitboard suitable
// to be printed to standard output. Useful for debugging.
std::string Bitboards::pretty(Bitboard b) {
@@ -64,126 +41,4 @@ std::string Bitboards::pretty(Bitboard b) {
return s;
}
// Initializes various bitboard tables. It is called at
// startup and relies on global objects to be already zero-initialized.
void Bitboards::init() {
for (unsigned i = 0; i < (1 << 16); ++i)
PopCnt16[i] = uint8_t(std::bitset<16>(i).count());
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
init_magics(ROOK, RookTable, Magics);
init_magics(BISHOP, BishopTable, Magics);
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{
for (PieceType pt : {BISHOP, ROOK})
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
{
if (PseudoAttacks[pt][s1] & s2)
{
LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
BetweenBB[s1][s2] =
(attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
RayPassBB[s1][s2] =
attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2);
}
BetweenBB[s1][s2] |= s2;
}
}
}
namespace {
// Computes all rook and bishop attacks at startup. Magic
// bitboards are used to look up attacks of sliding pieces. As a reference see
// https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use
// the so called "fancy" approach.
void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) {
#ifndef USE_PEXT
// Optimal PRNG seeds to pick the correct magics in the shortest time
int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
{728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
Bitboard occupancy[4096];
int epoch[4096] = {}, cnt = 0;
#endif
Bitboard reference[4096];
int size = 0;
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
// Board edges are not considered in the relevant occupancies
Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s));
// Given a square 's', the mask is the bitboard of sliding attacks from
// 's' computed on an empty board. The index must be big enough to contain
// all the attacks for each possible subset of the mask and so is 2 power
// the number of 1s of the mask. Hence we deduce the size of the shift to
// apply to the 64 or 32 bits word to get the index.
Magic& m = magics[s][pt - BISHOP];
m.mask = Bitboards::sliding_attack(pt, s, 0) & ~edges;
#ifndef USE_PEXT
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
#endif
// Set the offset for the attacks table of the square. We have individual
// table sizes for each square with "Fancy Magic Bitboards".
m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size;
size = 0;
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and
// store the corresponding sliding attack bitboard in reference[].
Bitboard b = 0;
do
{
#ifndef USE_PEXT
occupancy[size] = b;
#endif
reference[size] = Bitboards::sliding_attack(pt, s, b);
if (HasPext)
m.attacks[pext(b, m.mask)] = reference[size];
size++;
b = (b - m.mask) & m.mask;
} while (b);
#ifndef USE_PEXT
PRNG rng(seeds[Is64Bit][rank_of(s)]);
// Find a magic for square 's' picking up an (almost) random number
// until we find the one that passes the verification test.
for (int i = 0; i < size;)
{
for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;)
m.magic = rng.sparse_rand<Bitboard>();
// A good magic must map every possible occupancy to an index that
// looks up the correct sliding attack in the attacks[s] database.
// Note that we build up the database for square 's' as a side
// effect of verifying the magic. Keep track of the attempt count
// and save it in epoch[], little speed-up trick to avoid resetting
// m.attacks[] after every failed attempt.
for (++cnt, i = 0; i < size; ++i)
{
unsigned idx = m.index(occupancy[i]);
if (epoch[idx] < cnt)
{
epoch[idx] = cnt;
m.attacks[idx] = reference[i];
}
else if (m.attacks[idx] != reference[i])
break;
}
}
#endif
}
}
}
} // namespace Stockfish
+66 -248
View File
@@ -20,26 +20,31 @@
#define BITBOARD_H_INCLUDED
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstring>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <initializer_list>
#include <array>
#include <cassert>
#include <string>
#include <type_traits>
#include "types.h"
#include "misc.h"
namespace Stockfish {
namespace Bitboards {
void init();
std::string pretty(Bitboard b);
} // namespace Stockfish::Bitboards
#ifdef USE_AVX512
// clang-format off
inline const __m512i AllSquares = _mm512_set_epi8(
63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41,
40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18,
17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
// clang-format on
#endif
constexpr Bitboard FileABB = 0x0101010101010101ULL;
constexpr Bitboard FileBBB = FileABB << 1;
constexpr Bitboard FileCBB = FileABB << 2;
@@ -58,45 +63,9 @@ constexpr Bitboard Rank6BB = Rank1BB << (8 * 5);
constexpr Bitboard Rank7BB = Rank1BB << (8 * 6);
constexpr Bitboard Rank8BB = Rank1BB << (8 * 7);
extern uint8_t PopCnt16[1 << 16];
extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB];
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
// Magic holds all magic bitboards relevant data for a single square
struct Magic {
Bitboard mask;
Bitboard* attacks;
#ifndef USE_PEXT
Bitboard magic;
unsigned shift;
#endif
// Compute the attack's index using the 'magic bitboards' approach
unsigned index(Bitboard occupied) const {
#ifdef USE_PEXT
return unsigned(pext(occupied, mask));
#else
if (Is64Bit)
return unsigned(((occupied & mask) * magic) >> shift);
unsigned lo = unsigned(occupied) & unsigned(mask);
unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32);
return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift;
#endif
}
Bitboard attacks_bb(Bitboard occupied) const { return attacks[index(occupied)]; }
};
extern Magic Magics[SQUARE_NB][2];
constexpr Bitboard square_bb(Square s) {
assert(is_ok(s));
return (1ULL << s);
return 1ULL << s;
}
@@ -155,73 +124,58 @@ constexpr Bitboard pawn_attacks_bb(Bitboard b) {
: shift<SOUTH_WEST>(b) | shift<SOUTH_EAST>(b);
}
// Returns a bitboard representing an entire line (from board edge
// to board edge) that intersects the two given squares. If the given squares
// are not on a same file/rank/diagonal, the function returns 0. For instance,
// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal.
inline Bitboard line_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2));
return LineBB[s1][s2];
constexpr Bitboard pawn_single_push_bb(Color c, Bitboard b) {
return c == WHITE ? shift<NORTH>(b) : shift<SOUTH>(b);
}
inline constexpr auto PawnPairBB = []() {
std::array<Bitboard, SQUARE_NB> result{};
for (Square s = SQ_A1; s <= SQ_H8; ++s)
{
Bitboard file = file_bb(s);
Bitboard files = file | shift<EAST>(file) | shift<WEST>(file);
result[s] = files & ~(Rank1BB | Rank8BB) & ~square_bb(s);
}
return result;
}();
// Returns a bitboard representing the squares in the semi-open
// segment between the squares s1 and s2 (excluding s1 but including s2). If the
// given squares are not on a same file/rank/diagonal, it returns s2. For instance,
// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but
// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick
// allows to generate non-king evasion moves faster: the defending piece must either
// interpose itself to cover the check or capture the checking piece.
inline Bitboard between_bb(Square s1, Square s2) {
// Returns the squares that can host a pawn forming a "pawn pair" with a pawn
// on s: own file plus adjacent files, restricted to ranks 2-7, excluding s.
// The geometry is color-independent.
constexpr Bitboard pawn_pair_bb(Square s) { return PawnPairBB[s]; }
assert(is_ok(s1) && is_ok(s2));
return BetweenBB[s1][s2];
}
constexpr int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
// distance() functions return the distance between x and y, defined as the
// number of steps for a king in x to reach y.
template<typename T>
constexpr int constexpr_popcount(T v) {
static_assert(std::is_integral_v<T>, "constexpr_popcount is undefined for non-integral types");
template<typename T1 = Square>
inline int distance(Square x, Square y);
if constexpr (sizeof(T) <= 8)
{
u64 b = static_cast<std::make_unsigned_t<T>>(v);
template<>
inline int distance<File>(Square x, Square y) {
return std::abs(file_of(x) - file_of(y));
}
b = b - ((b >> 1) & 0x5555555555555555ULL);
b = (b & 0x3333333333333333ULL) + ((b >> 2) & 0x3333333333333333ULL);
b = (b + (b >> 4)) & 0x0F0F0F0F0F0F0F0FULL;
template<>
inline int distance<Rank>(Square x, Square y) {
return std::abs(rank_of(x) - rank_of(y));
}
return static_cast<int>((b * 0x0101010101010101ULL) >> 56);
}
else
{
int result = 0;
template<>
inline int distance<Square>(Square x, Square y) {
return SquareDistance[x][y];
}
for (; v; v >>= static_cast<T>(1))
if (v & static_cast<T>(1))
++result;
inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
constexpr int constexpr_popcount(Bitboard b) {
b = b - ((b >> 1) & 0x5555555555555555ULL);
b = (b & 0x3333333333333333ULL) + ((b >> 2) & 0x3333333333333333ULL);
b = (b + (b >> 4)) & 0x0F0F0F0F0F0F0F0FULL;
return static_cast<int>((b * 0x0101010101010101ULL) >> 56);
return result;
}
}
// Counts the number of non-zero bits in a bitboard.
inline int popcount(Bitboard b) {
#ifndef USE_POPCNT
std::uint16_t indices[4];
std::memcpy(indices, &b, sizeof(b));
return PopCnt16[indices[0]] + PopCnt16[indices[1]] + PopCnt16[indices[2]]
+ PopCnt16[indices[3]];
#elif defined(_MSC_VER)
#ifdef _MSC_VER
return int(_mm_popcnt_u64(b));
@@ -232,6 +186,17 @@ inline int popcount(Bitboard b) {
#endif
}
inline constexpr int lsb_index64[64] = {
0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42,
21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43,
31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63};
constexpr int constexpr_lsb(u64 bb) {
assert(bb != 0);
constexpr u64 debruijn64 = 0x03F79D71B4CB0A89ULL;
return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58];
}
// Returns the least significant bit in a non-zero bitboard.
inline Square lsb(Bitboard b) {
assert(b);
@@ -252,12 +217,12 @@ inline Square lsb(Bitboard b) {
if (b & 0xffffffff)
{
_BitScanForward(&idx, int32_t(b));
_BitScanForward(&idx, i32(b));
return Square(idx);
}
else
{
_BitScanForward(&idx, int32_t(b >> 32));
_BitScanForward(&idx, i32(b >> 32));
return Square(idx + 32);
}
#endif
@@ -287,12 +252,12 @@ inline Square msb(Bitboard b) {
if (b >> 32)
{
_BitScanReverse(&idx, int32_t(b >> 32));
_BitScanReverse(&idx, i32(b >> 32));
return Square(idx + 32);
}
else
{
_BitScanReverse(&idx, int32_t(b));
_BitScanReverse(&idx, i32(b));
return Square(idx);
}
#endif
@@ -316,153 +281,6 @@ inline Square pop_lsb(Bitboard& b) {
return s;
}
namespace Bitboards {
// Returns the bitboard of target square for the given step
// from the given square. If the step is off the board, returns empty bitboard.
constexpr Bitboard safe_destination(Square s, int step) {
constexpr auto abs = [](int v) { return v < 0 ? -v : v; };
Square to = Square(s + step);
return is_ok(to) && abs(file_of(s) - file_of(to)) <= 2 ? square_bb(to) : Bitboard(0);
}
constexpr Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
Bitboard attacks = 0;
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
{
Square s = sq;
while (safe_destination(s, d))
{
attacks |= (s += d);
if (occupied & s)
{
break;
}
}
}
return attacks;
}
constexpr Bitboard knight_attack(Square sq) {
Bitboard b = {};
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17})
b |= safe_destination(sq, step);
return b;
}
constexpr Bitboard king_attack(Square sq) {
Bitboard b = {};
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9})
b |= safe_destination(sq, step);
return b;
}
constexpr Bitboard pseudo_attacks(PieceType pt, Square sq) {
switch (pt)
{
case PieceType::ROOK :
case PieceType::BISHOP :
return sliding_attack(pt, sq, 0);
case PieceType::QUEEN :
return sliding_attack(PieceType::ROOK, sq, 0) | sliding_attack(PieceType::BISHOP, sq, 0);
case PieceType::KNIGHT :
return knight_attack(sq);
case PieceType::KING :
return king_attack(sq);
default :
assert(false);
return 0;
}
}
}
inline constexpr auto PseudoAttacks = []() constexpr {
std::array<std::array<Bitboard, SQUARE_NB>, PIECE_TYPE_NB> attacks{};
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{
attacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
attacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
attacks[KING][s1] = Bitboards::pseudo_attacks(KING, s1);
attacks[KNIGHT][s1] = Bitboards::pseudo_attacks(KNIGHT, s1);
attacks[QUEEN][s1] = attacks[BISHOP][s1] = Bitboards::pseudo_attacks(BISHOP, s1);
attacks[QUEEN][s1] |= attacks[ROOK][s1] = Bitboards::pseudo_attacks(ROOK, s1);
}
return attacks;
}();
// Returns the pseudo attacks of the given piece type
// assuming an empty board.
template<PieceType Pt>
inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) {
assert((Pt != PAWN || c < COLOR_NB) && (is_ok(s)));
return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s];
}
// Returns the attacks by the given piece
// assuming the board is occupied according to the passed Bitboard.
// Sliding piece attacks do not continue passed an occupied square.
template<PieceType Pt>
inline Bitboard attacks_bb(Square s, Bitboard occupied) {
assert((Pt != PAWN) && (is_ok(s)));
switch (Pt)
{
case BISHOP :
case ROOK :
return Magics[s][Pt - BISHOP].attacks_bb(occupied);
case QUEEN :
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
default :
return PseudoAttacks[Pt][s];
}
}
// Returns the attacks by the given piece
// assuming the board is occupied according to the passed Bitboard.
// Sliding piece attacks do not continue passed an occupied square.
inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
assert((pt != PAWN) && (is_ok(s)));
switch (pt)
{
case BISHOP :
return attacks_bb<BISHOP>(s, occupied);
case ROOK :
return attacks_bb<ROOK>(s, occupied);
case QUEEN :
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
default :
return PseudoAttacks[pt][s];
}
}
inline Bitboard attacks_bb(Piece pc, Square s) {
if (type_of(pc) == PAWN)
return PseudoAttacks[color_of(pc)][s];
return PseudoAttacks[type_of(pc)][s];
}
inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) {
if (type_of(pc) == PAWN)
return PseudoAttacks[color_of(pc)][s];
return attacks_bb(type_of(pc), s, occupied);
}
} // namespace Stockfish
#endif // #ifndef BITBOARD_H_INCLUDED
+69 -74
View File
@@ -20,6 +20,7 @@
#include <algorithm>
#include <cassert>
#include <filesystem>
#include <deque>
#include <iosfwd>
#include <memory>
@@ -33,7 +34,6 @@
#include "misc.h"
#include "nnue/network.h"
#include "nnue/nnue_common.h"
#include "nnue/nnue_misc.h"
#include "numa.h"
#include "perft.h"
#include "position.h"
@@ -48,9 +48,8 @@ namespace Stockfish {
namespace NN = Eval::NNUE;
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency()));
constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency()));
// The default configuration will attempt to group L3 domains up to 32 threads.
// This size was found to be a good balance between the Elo gain of increased
@@ -58,27 +57,26 @@ int MaxThreads = std::max(1024, 4 * int(get_hardware_concurrency()));
// PR#6526). The user can always explicitly override this behavior.
constexpr NumaAutoPolicy DefaultNumaPolicy = BundledL3Policy{32};
Engine::Engine(std::optional<std::string> path) :
binaryDirectory(path ? CommandLine::get_binary_directory(*path) : ""),
Engine::Engine(std::optional<std::filesystem::path> path) :
binaryDirectory(path ? CommandLine::get_binary_directory(*path) : std::filesystem::path{}),
numaContext(NumaConfig::from_system(DefaultNumaPolicy)),
states(new std::deque<StateInfo>(1)),
threads(),
networks(numaContext,
// Heap-allocate because sizeof(NN::Networks) is large
std::make_unique<NN::Networks>(NN::EvalFile{EvalFileDefaultNameBig, "None", ""},
NN::EvalFile{EvalFileDefaultNameSmall, "None", ""})) {
networkFile{std::nullopt, ""},
network(numaContext) {
pos.set(StartFEN, false, &states->back());
options.add( //
"Debug Log File", Option("", [](const Option& o) {
start_logger(o);
start_logger(path_from_utf8(std::string(o)));
return std::nullopt;
}));
options.add( //
"NumaPolicy", Option("auto", [this](const Option& o) {
set_numa_config_from_option(o);
if (!set_numa_config_from_option(o))
return "NumaPolicy: invalid value '" + std::string(o) + "', keeping previous config.";
return numa_config_information_as_string() + "\n"
+ thread_allocation_information_as_string();
}));
@@ -136,30 +134,27 @@ Engine::Engine(std::optional<std::string> path) :
options.add("SyzygyProbeLimit", Option(7, 0, 7));
options.add( //
"EvalFile", Option(EvalFileDefaultNameBig, [this](const Option& o) {
load_big_network(o);
"EvalFile", Option(EvalFileDefaultName, [this](const Option& o) {
load_network(path_from_utf8(std::string(o)));
return std::nullopt;
}));
options.add( //
"EvalFileSmall", Option(EvalFileDefaultNameSmall, [this](const Option& o) {
load_small_network(o);
return std::nullopt;
}));
load_networks();
network = get_default_network();
threads.clear();
threads.ensure_network_replicated();
resize_threads();
}
std::uint64_t Engine::perft(const std::string& fen, Depth depth, bool isChess960) {
verify_networks();
std::variant<u64, PositionSetError>
Engine::perft(const std::string& fen, Depth depth, bool isChess960) {
verify_network();
return Benchmark::perft(fen, depth, isChess960);
}
void Engine::go(Search::LimitsType& limits) {
assert(limits.perft == 0);
verify_networks();
verify_network();
threads.start_thinking(options, pos, states, limits);
}
@@ -171,7 +166,7 @@ void Engine::search_clear() {
tt.clear(threads);
threads.clear();
// @TODO wont work with multiple instances
// TODO: does not work with multiple instances
Tablebases::init(options["SyzygyPath"]); // Free mapped files
}
@@ -191,32 +186,37 @@ void Engine::set_on_bestmove(std::function<void(std::string_view, std::string_vi
updateContext.onBestmove = std::move(f);
}
void Engine::set_on_verify_networks(std::function<void(std::string_view)>&& f) {
onVerifyNetworks = std::move(f);
void Engine::set_on_verify_network(std::function<void(std::string_view)>&& f) {
onVerifyNetwork = std::move(f);
}
void Engine::wait_for_search_finished() { threads.main_thread()->wait_for_search_finished(); }
void Engine::set_position(const std::string& fen, const std::vector<std::string>& moves) {
std::optional<PositionSetError> Engine::set_position(const std::string& fen,
const std::vector<std::string>& moves) {
// Drop the old state and create a new one
states = StateListPtr(new std::deque<StateInfo>(1));
pos.set(fen, options["UCI_Chess960"], &states->back());
states = StateListPtr(new std::deque<StateInfo>(1));
auto err = pos.set(fen, options["UCI_Chess960"], &states->back());
if (err.has_value())
return err;
for (const auto& move : moves)
{
auto m = UCIEngine::to_move(pos, move);
if (m == Move::none())
break;
return PositionSetError("Illegal move: " + move);
states->emplace_back();
pos.do_move(m, states->back());
}
return std::nullopt;
}
// modifiers
void Engine::set_numa_config_from_option(const std::string& o) {
bool Engine::set_numa_config_from_option(const std::string& o) {
if (o == "auto" || o == "system")
{
numaContext.set_numa_config(NumaConfig::from_system(DefaultNumaPolicy));
@@ -232,17 +232,21 @@ void Engine::set_numa_config_from_option(const std::string& o) {
}
else
{
numaContext.set_numa_config(NumaConfig::from_string(o));
auto parsed = NumaConfig::from_string(o);
if (!parsed.has_value())
return false;
numaContext.set_numa_config(std::move(*parsed));
}
// Force reallocation of threads in case affinities need to change.
resize_threads();
threads.ensure_network_replicated();
return true;
}
void Engine::resize_threads() {
threads.wait_for_search_finished();
threads.set(numaContext.get_numa_config(), {options, threads, tt, sharedHists, networks},
threads.set(numaContext.get_numa_config(), {options, threads, tt, sharedHists, network},
updateContext);
// Reallocate the hash with the new threadpool size
@@ -250,7 +254,7 @@ void Engine::resize_threads() {
threads.ensure_network_replicated();
}
void Engine::set_tt_size(size_t mb) {
void Engine::set_tt_size(usize mb) {
wait_for_search_finished();
tt.resize(mb, threads);
}
@@ -259,12 +263,12 @@ void Engine::set_ponderhit(bool b) { threads.main_manager()->ponder = b; }
// network related
void Engine::verify_networks() const {
networks->big.verify(options["EvalFile"], onVerifyNetworks);
networks->small.verify(options["EvalFileSmall"], onVerifyNetworks);
void Engine::verify_network() const {
const auto file = path_from_utf8(std::string(options["EvalFile"]));
network->verify(onVerifyNetwork, networkFile, file);
auto statuses = networks.get_status_and_errors();
for (size_t i = 0; i < statuses.size(); ++i)
auto statuses = network.get_status_and_errors();
for (usize i = 0; i < statuses.size(); ++i)
{
const auto [status, error] = statuses[i];
std::string message = "Network replica " + std::to_string(i + 1) + ": ";
@@ -290,38 +294,29 @@ void Engine::verify_networks() const {
message += " " + *error;
}
onVerifyNetworks(message);
onVerifyNetwork(message);
}
}
void Engine::load_networks() {
networks.modify_and_replicate([this](NN::Networks& networks_) {
networks_.big.load(binaryDirectory, options["EvalFile"]);
networks_.small.load(binaryDirectory, options["EvalFileSmall"]);
});
std::unique_ptr<Eval::NNUE::Network> Engine::get_default_network() {
auto network_ = std::make_unique<NN::Network>();
network_->load(binaryDirectory, std::filesystem::path{}, networkFile);
return network_;
}
void Engine::load_network(const std::filesystem::path& file) {
network.modify_and_replicate(
[this, &file](NN::Network& network_) { network_.load(binaryDirectory, file, networkFile); });
threads.clear();
threads.ensure_network_replicated();
}
void Engine::load_big_network(const std::string& file) {
networks.modify_and_replicate(
[this, &file](NN::Networks& networks_) { networks_.big.load(binaryDirectory, file); });
threads.clear();
threads.ensure_network_replicated();
}
void Engine::load_small_network(const std::string& file) {
networks.modify_and_replicate(
[this, &file](NN::Networks& networks_) { networks_.small.load(binaryDirectory, file); });
threads.clear();
threads.ensure_network_replicated();
}
void Engine::save_network(const std::pair<std::optional<std::string>, std::string> files[2]) {
networks.modify_and_replicate([&files](NN::Networks& networks_) {
networks_.big.save(files[0].first);
networks_.small.save(files[1].first);
});
void Engine::save_network(const std::optional<std::filesystem::path>& file) {
network.modify_and_replicate(
[&file, this](NN::Network& network_) { network_.save(networkFile, file); });
}
// utility functions
@@ -331,9 +326,9 @@ void Engine::trace_eval() const {
Position p;
p.set(pos.fen(), options["UCI_Chess960"], &trace_states->back());
verify_networks();
verify_network();
sync_cout << "\n" << Eval::trace(p, *networks) << sync_endl;
sync_cout << "\n" << Eval::trace(p, *network) << sync_endl;
}
const OptionsMap& Engine::get_options() const { return options; }
@@ -341,7 +336,7 @@ OptionsMap& Engine::get_options() { return options; }
std::string Engine::fen() const { return pos.fen(); }
void Engine::flip() { pos.flip(); }
std::optional<PositionSetError> Engine::flip() { return pos.flip(); }
std::string Engine::visualize() const {
std::stringstream ss;
@@ -351,11 +346,11 @@ std::string Engine::visualize() const {
int Engine::get_hashfull(int maxAge) const { return tt.hashfull(maxAge); }
std::vector<std::pair<size_t, size_t>> Engine::get_bound_thread_count_by_numa_node() const {
auto counts = threads.get_bound_thread_count_by_numa_node();
const NumaConfig& cfg = numaContext.get_numa_config();
std::vector<std::pair<size_t, size_t>> ratios;
NumaIndex n = 0;
std::vector<std::pair<usize, usize>> Engine::get_bound_thread_count_by_numa_node() const {
auto counts = threads.get_bound_thread_count_by_numa_node();
const NumaConfig& cfg = numaContext.get_numa_config();
std::vector<std::pair<usize, usize>> ratios;
NumaIndex n = 0;
for (; n < counts.size(); ++n)
ratios.emplace_back(counts[n], cfg.num_cpus_in_numa_node(n));
if (!counts.empty())
@@ -395,7 +390,7 @@ std::string Engine::thread_binding_information_as_string() const {
std::string Engine::thread_allocation_information_as_string() const {
std::stringstream ss;
size_t threadsSize = threads.size();
usize threadsSize = threads.size();
ss << "Using " << threadsSize << (threadsSize > 1 ? " threads" : " thread");
auto boundThreadsByNodeStr = thread_binding_information_as_string();
+31 -27
View File
@@ -19,18 +19,21 @@
#ifndef ENGINE_H_INCLUDED
#define ENGINE_H_INCLUDED
#include <cstddef>
#include <cstdint>
#include <functional>
#include <filesystem>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include <vector>
#include "misc.h"
#include "history.h"
#include "nnue/network.h"
#include "nnue/nnue_misc.h"
#include "numa.h"
#include "position.h"
#include "search.h"
@@ -47,7 +50,7 @@ class Engine {
using InfoFull = Search::InfoFull;
using InfoIter = Search::InfoIteration;
Engine(std::optional<std::string> path = std::nullopt);
Engine(std::optional<std::filesystem::path> path = std::nullopt);
// Cannot be movable due to components holding backreferences to fields
Engine(const Engine&) = delete;
@@ -57,7 +60,7 @@ class Engine {
~Engine() { wait_for_search_finished(); }
std::uint64_t perft(const std::string& fen, Depth depth, bool isChess960);
std::variant<u64, PositionSetError> perft(const std::string& fen, Depth depth, bool isChess960);
// non blocking call to start searching
void go(Search::LimitsType&);
@@ -67,13 +70,14 @@ class Engine {
// blocking call to wait for search to finish
void wait_for_search_finished();
// set a new position, moves are in UCI format
void set_position(const std::string& fen, const std::vector<std::string>& moves);
std::optional<PositionSetError> set_position(const std::string& fen,
const std::vector<std::string>& moves);
// modifiers
void set_numa_config_from_option(const std::string& o);
bool set_numa_config_from_option(const std::string& o);
void resize_threads();
void set_tt_size(size_t mb);
void set_tt_size(usize mb);
void set_ponderhit(bool);
void search_clear();
@@ -81,15 +85,14 @@ class Engine {
void set_on_update_full(std::function<void(const InfoFull&)>&&);
void set_on_iter(std::function<void(const InfoIter&)>&&);
void set_on_bestmove(std::function<void(std::string_view, std::string_view)>&&);
void set_on_verify_networks(std::function<void(std::string_view)>&&);
void set_on_verify_network(std::function<void(std::string_view)>&&);
// network related
void verify_networks() const;
void load_networks();
void load_big_network(const std::string& file);
void load_small_network(const std::string& file);
void save_network(const std::pair<std::optional<std::string>, std::string> files[2]);
void verify_network() const;
std::unique_ptr<Eval::NNUE::Network> get_default_network();
void load_network(const std::filesystem::path& file);
void save_network(const std::optional<std::filesystem::path>& file);
// utility functions
@@ -100,30 +103,31 @@ class Engine {
int get_hashfull(int maxAge = 0) const;
std::string fen() const;
void flip();
std::string visualize() const;
std::vector<std::pair<size_t, size_t>> get_bound_thread_count_by_numa_node() const;
std::string get_numa_config_as_string() const;
std::string numa_config_information_as_string() const;
std::string thread_allocation_information_as_string() const;
std::string thread_binding_information_as_string() const;
std::string fen() const;
std::optional<PositionSetError> flip();
std::string visualize() const;
std::vector<std::pair<usize, usize>> get_bound_thread_count_by_numa_node() const;
std::string get_numa_config_as_string() const;
std::string numa_config_information_as_string() const;
std::string thread_allocation_information_as_string() const;
std::string thread_binding_information_as_string() const;
private:
const std::string binaryDirectory;
const std::filesystem::path binaryDirectory;
NumaReplicationContext numaContext;
Position pos;
StateListPtr states;
OptionsMap options;
ThreadPool threads;
TranspositionTable tt;
LazyNumaReplicatedSystemWide<Eval::NNUE::Networks> networks;
OptionsMap options;
ThreadPool threads;
TranspositionTable tt;
Eval::NNUE::EvalFile networkFile;
LazyNumaReplicatedSystemWide<Eval::NNUE::Network> network;
Search::SearchManager::UpdateContext updateContext;
std::function<void(std::string_view)> onVerifyNetworks;
std::function<void(std::string_view)> onVerifyNetwork;
std::map<NumaIndex, SharedHistories> sharedHists;
};
+18 -37
View File
@@ -26,8 +26,8 @@
#include <iostream>
#include <memory>
#include <sstream>
#include <tuple>
#include "misc.h"
#include "nnue/network.h"
#include "nnue/nnue_misc.h"
#include "position.h"
@@ -37,20 +37,9 @@
namespace Stockfish {
// Returns a static, purely materialistic evaluation of the position from
// the point of view of the side to move. It can be divided by PawnValue to get
// an approximation of the material advantage on the board in terms of pawns.
int Eval::simple_eval(const Position& pos) {
Color c = pos.side_to_move();
return PawnValue * (pos.count<PAWN>(c) - pos.count<PAWN>(~c)) + pos.non_pawn_material(c)
- pos.non_pawn_material(~c);
}
bool Eval::use_smallnet(const Position& pos) { return std::abs(simple_eval(pos)) > 962; }
// Evaluate is the evaluator for the outer world. It returns a static evaluation
// of the position from the point of view of the side to move.
Value Eval::evaluate(const Eval::NNUE::Networks& networks,
Value Eval::evaluate(const Eval::NNUE::Network& network,
const Position& pos,
Eval::NNUE::AccumulatorStack& accumulators,
Eval::NNUE::AccumulatorCaches& caches,
@@ -58,27 +47,17 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
assert(!pos.checkers());
bool smallNet = use_smallnet(pos);
auto [psqt, positional] = smallNet ? networks.small.evaluate(pos, accumulators, caches.small)
: networks.big.evaluate(pos, accumulators, caches.big);
auto [psqt, positional] = network.evaluate(pos, accumulators, caches);
Value nnue = (125 * psqt + 131 * positional) / 128;
// Re-evaluate the position when higher eval accuracy is worth the time spent
if (smallNet && (std::abs(nnue) < 277))
{
std::tie(psqt, positional) = networks.big.evaluate(pos, accumulators, caches.big);
nnue = (125 * psqt + 131 * positional) / 128;
smallNet = false;
}
Value nnue = psqt + positional;
// Blend optimism and eval with nnue complexity
int nnueComplexity = std::abs(psqt - positional);
optimism += optimism * nnueComplexity / 476;
nnue -= nnue * nnueComplexity / 18236;
optimism += optimism * i64(nnueComplexity) / 476;
nnue -= nnue * i64(nnueComplexity) / 18236;
int material = 534 * pos.count<PAWN>() + pos.non_pawn_material();
int v = (nnue * (77871 + material) + optimism * (7191 + material)) / 77871;
int v = (nnue * i64(77871 + material) + optimism * i64(7191 + material)) / 77871;
// Damp down the evaluation linearly when shuffling
v -= v * pos.rule50_count() / 199;
@@ -93,30 +72,32 @@ Value Eval::evaluate(const Eval::NNUE::Networks& networks,
// a string (suitable for outputting to stdout) that contains the detailed
// descriptions and values of each evaluation term. Useful for debugging.
// Trace scores are from white's point of view
std::string Eval::trace(Position& pos, const Eval::NNUE::Networks& networks) {
std::string Eval::trace(Position& pos, const Eval::NNUE::Network& network) {
if (pos.checkers())
return "Final evaluation: none (in check)";
auto accumulators = std::make_unique<Eval::NNUE::AccumulatorStack>();
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(networks);
auto caches = std::make_unique<Eval::NNUE::AccumulatorCaches>(network);
std::stringstream ss;
ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2);
ss << '\n' << NNUE::trace(pos, networks, *caches) << '\n';
ss << '\n' << NNUE::trace(pos, network, *caches) << '\n';
ss << std::showpoint << std::showpos << std::fixed << std::setprecision(2) << std::setw(15);
auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches->big);
auto [psqt, positional] = network.evaluate(pos, *accumulators, *caches);
Value v = psqt + positional;
v = pos.side_to_move() == WHITE ? v : -v;
ss << "NNUE evaluation " << v << " (side to move, internal units)\n";
v = pos.side_to_move() == WHITE ? v : -v;
ss << "NNUE evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)\n";
v = evaluate(networks, pos, *accumulators, *caches, VALUE_ZERO);
v = evaluate(network, pos, *accumulators, *caches, VALUE_ZERO);
v = pos.side_to_move() == WHITE ? v : -v;
ss << "Final evaluation " << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)";
ss << " [with scaled NNUE, ...]";
ss << "\n";
ss << "Final evaluation ";
ss << 0.01 * UCIEngine::to_cp(v, pos) << " (white side)";
ss << " [with scaled NNUE, ...]\n";
return ss.str();
}
+4 -7
View File
@@ -33,20 +33,17 @@ namespace Eval {
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro or the location where this macro is defined, as it is used
// in the Makefile/Fishtest.
#define EvalFileDefaultNameBig "nn-c288c895ea92.nnue"
#define EvalFileDefaultNameSmall "nn-37f18f62d772.nnue"
#define EvalFileDefaultName "nn-89cb98a217f7.nnue"
namespace NNUE {
struct Networks;
class Network;
struct AccumulatorCaches;
class AccumulatorStack;
}
std::string trace(Position& pos, const Eval::NNUE::Networks& networks);
std::string trace(Position& pos, const Eval::NNUE::Network& network);
int simple_eval(const Position& pos);
bool use_smallnet(const Position& pos);
Value evaluate(const NNUE::Networks& networks,
Value evaluate(const NNUE::Network& network,
const Position& pos,
Eval::NNUE::AccumulatorStack& accumulators,
Eval::NNUE::AccumulatorCaches& caches,
+32 -51
View File
@@ -36,7 +36,7 @@
namespace Stockfish {
constexpr int PAWN_HISTORY_BASE_SIZE = 8192; // has to be a power of 2
constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits<uint16_t>::max() + 1;
constexpr int UINT_16_HISTORY_SIZE = std::numeric_limits<u16>::max() + 1;
constexpr int CORRHIST_BASE_SIZE = UINT_16_HISTORY_SIZE;
constexpr int CORRECTION_HISTORY_LIMIT = 1024;
constexpr int LOW_PLY_HISTORY_SIZE = 5;
@@ -52,27 +52,20 @@ static_assert((CORRHIST_BASE_SIZE & (CORRHIST_BASE_SIZE - 1)) == 0,
// the entry. The first template parameter T is the base type of the array,
// and the second template parameter D limits the range of updates in [-D, D]
// when we update values with the << operator
template<typename T, int D, bool Atomic = false>
template<typename T, int D, bool Shared = false>
struct StatsEntry {
static_assert(std::is_arithmetic_v<T>, "Not an arithmetic type");
static_assert(std::is_integral_v<T> && std::is_signed_v<T>, "Not a signed integer type");
static_assert(D > 0 && D <= std::numeric_limits<T>::max()
&& D <= std::numeric_limits<int>::max() / D,
"D can lead to overflows");
private:
std::conditional_t<Atomic, std::atomic<T>, T> entry;
std::conditional_t<Shared, RelaxedAtomic<T>, T> entry;
public:
void operator=(const T& v) {
if constexpr (Atomic)
entry.store(v, std::memory_order_relaxed);
else
entry = v;
}
void operator=(const T& v) { entry = v; }
operator T() const {
if constexpr (Atomic)
return entry.load(std::memory_order_relaxed);
else
return entry;
}
operator T() const { return entry; }
void operator<<(int bonus) {
// Make sure that bonus is in range [-D, D]
@@ -89,10 +82,10 @@ enum StatsType {
Captures
};
template<typename T, int D, std::size_t... Sizes>
template<typename T, int D, usize... Sizes>
using Stats = MultiArray<StatsEntry<T, D>, Sizes...>;
template<typename T, int D, std::size_t... Sizes>
template<typename T, int D, usize... Sizes>
using AtomicStats = MultiArray<StatsEntry<T, D, true>, Sizes...>;
// DynStats is a dynamically sized array of Stats, used for thread-shared histories
@@ -100,31 +93,31 @@ using AtomicStats = MultiArray<StatsEntry<T, D, true>, Sizes...>;
// the per-thread allocation count of T.
template<typename T, int SizeMultiplier>
struct DynStats {
explicit DynStats(size_t s) {
explicit DynStats(usize s) {
size = s * SizeMultiplier;
data = make_unique_large_page<T[]>(size);
}
// Sets all values in the range to 0
void clear_range(int value, size_t threadIdx, size_t numaTotal) {
size_t start = uint64_t(threadIdx) * size / numaTotal;
void clear_range(int value, usize threadIdx, usize numaTotal) {
usize start = u64(threadIdx) * size / numaTotal;
assert(start < size);
size_t end = threadIdx + 1 == numaTotal ? size : uint64_t(threadIdx + 1) * size / numaTotal;
usize end = threadIdx + 1 == numaTotal ? size : u64(threadIdx + 1) * size / numaTotal;
while (start < end)
data[start++].fill(value);
}
size_t get_size() const { return size; }
T& operator[](size_t index) {
usize get_size() const { return size; }
T& operator[](usize index) {
assert(index < size);
return data.get()[index];
}
const T& operator[](size_t index) const {
const T& operator[](usize index) const {
assert(index < size);
return data.get()[index];
}
private:
size_t size;
usize size;
LargePagePtr<T[]> data;
};
@@ -132,17 +125,17 @@ struct DynStats {
// during the current search, and is used for reduction and move ordering decisions.
// It uses 2 tables (one for each color) indexed by the move's from and to squares,
// see https://www.chessprogramming.org/Butterfly_Boards
using ButterflyHistory = Stats<std::int16_t, 7183, COLOR_NB, UINT_16_HISTORY_SIZE>;
using ButterflyHistory = Stats<i16, 7183, COLOR_NB, UINT_16_HISTORY_SIZE>;
// LowPlyHistory is addressed by ply and move's from and to squares, used
// to improve move ordering near the root
using LowPlyHistory = Stats<std::int16_t, 7183, LOW_PLY_HISTORY_SIZE, UINT_16_HISTORY_SIZE>;
using LowPlyHistory = Stats<i16, 7183, LOW_PLY_HISTORY_SIZE, UINT_16_HISTORY_SIZE>;
// CapturePieceToHistory is addressed by a move's [piece][to][captured piece type]
using CapturePieceToHistory = Stats<std::int16_t, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;
using CapturePieceToHistory = Stats<i16, 10692, PIECE_NB, SQUARE_NB, PIECE_TYPE_NB>;
// PieceToHistory is like ButterflyHistory but is addressed by a move's [piece][to]
using PieceToHistory = Stats<std::int16_t, 30000, PIECE_NB, SQUARE_NB>;
using PieceToHistory = AtomicStats<i16, 30000, PIECE_NB, SQUARE_NB>;
// ContinuationHistory is the combined history of a given pair of moves, usually
// the current one given a previous one. The nested history table is based on
@@ -150,17 +143,13 @@ using PieceToHistory = Stats<std::int16_t, 30000, PIECE_NB, SQUARE_NB>;
using ContinuationHistory = MultiArray<PieceToHistory, PIECE_NB, SQUARE_NB>;
// PawnHistory is addressed by the pawn structure and a move's [piece][to]
using PawnHistory =
DynStats<AtomicStats<std::int16_t, 8192, PIECE_NB, SQUARE_NB>, PAWN_HISTORY_BASE_SIZE>;
using PawnHistory = DynStats<AtomicStats<i16, 8192, PIECE_NB, SQUARE_NB>, PAWN_HISTORY_BASE_SIZE>;
// Correction histories record differences between the static evaluation of
// positions and their search score. It is used to improve the static evaluation
// used by some search heuristics.
// see https://www.chessprogramming.org/Static_Evaluation_Correction_History
enum CorrHistType {
Pawn, // By color and pawn structure
Minor, // By color and positions of minor pieces (Knight, Bishop)
NonPawn, // By non-pawn material positions and color
PieceTo, // By [piece][to] move
Continuation, // Combined history of move pairs
};
@@ -183,14 +172,11 @@ struct CorrectionBundle {
namespace Detail {
template<CorrHistType>
struct CorrHistTypedef {
using type =
DynStats<Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB>, CORRHIST_BASE_SIZE>;
};
struct CorrHistTypedef;
template<>
struct CorrHistTypedef<PieceTo> {
using type = Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, PIECE_NB, SQUARE_NB>;
using type = Stats<i16, CORRECTION_HISTORY_LIMIT, PIECE_NB, SQUARE_NB>;
};
template<>
@@ -198,29 +184,23 @@ struct CorrHistTypedef<Continuation> {
using type = MultiArray<CorrHistTypedef<PieceTo>::type, PIECE_NB, SQUARE_NB>;
};
template<>
struct CorrHistTypedef<NonPawn> {
using type = DynStats<Stats<std::int16_t, CORRECTION_HISTORY_LIMIT, COLOR_NB, COLOR_NB>,
CORRHIST_BASE_SIZE>;
};
}
using UnifiedCorrectionHistory =
DynStats<MultiArray<CorrectionBundle<std::int16_t, CORRECTION_HISTORY_LIMIT>, COLOR_NB>,
DynStats<MultiArray<CorrectionBundle<i16, CORRECTION_HISTORY_LIMIT>, COLOR_NB>,
CORRHIST_BASE_SIZE>;
template<CorrHistType T>
using CorrectionHistory = typename Detail::CorrHistTypedef<T>::type;
using TTMoveHistory = StatsEntry<std::int16_t, 8192>;
using TTMoveHistory = StatsEntry<i16, 8192>;
// Set of histories shared between groups of threads. To avoid excessive
// cross-node data transfer, histories are shared only between threads
// on a given NUMA node. The passed size must be a power of two to make
// the indexing more efficient.
struct SharedHistories {
SharedHistories(size_t threadCount) :
SharedHistories(usize threadCount) :
correctionHistory(threadCount),
pawnHistory(threadCount) {
assert((threadCount & (threadCount - 1)) == 0 && threadCount != 0);
@@ -228,7 +208,7 @@ struct SharedHistories {
pawnHistSizeMinus1 = pawnHistory.get_size() - 1;
}
size_t get_size() const { return sizeMinus1 + 1; }
usize get_size() const { return sizeMinus1 + 1; }
auto& pawn_entry(const Position& pos) {
return pawnHistory[pos.pawn_key() & pawnHistSizeMinus1];
@@ -261,11 +241,12 @@ struct SharedHistories {
}
UnifiedCorrectionHistory correctionHistory;
ContinuationHistory continuationHistory[2][2];
PawnHistory pawnHistory;
private:
size_t sizeMinus1, pawnHistSizeMinus1;
usize sizeMinus1, pawnHistSizeMinus1;
};
} // namespace Stockfish
+21 -3
View File
@@ -18,8 +18,9 @@
#include <iostream>
#include <memory>
#include <utility>
#include "bitboard.h"
#include "attacks.h"
#include "misc.h"
#include "position.h"
#include "tune.h"
@@ -27,13 +28,22 @@
using namespace Stockfish;
#ifdef UNIVERSAL_BINARY
namespace Stockfish {
int main(int argc, char* argv[]); // silence 'no previous declaration'
__attribute__((used)) // keep main alive
#endif
int main(int argc, char* argv[]) {
std::cout << engine_info() << std::endl;
Bitboards::init();
Attacks::init();
Position::init();
auto uci = std::make_unique<UCIEngine>(argc, argv);
auto cli = CommandLine(argc, argv);
auto uci = std::make_unique<UCIEngine>(std::move(cli));
Tune::init(uci->engine_options());
@@ -41,3 +51,11 @@ int main(int argc, char* argv[]) {
return 0;
}
#ifdef UNIVERSAL_BINARY
} // namespace Stockfish
#ifdef UNIVERSAL_NEEDS_MAIN_SHIM
int main(int argc, char* argv[]) { return Stockfish::main(argc, argv); }
#endif
#endif
+71 -16
View File
@@ -19,13 +19,19 @@
#include "memory.h"
#include <cstdlib>
#include <iostream> // std::cerr
#if __has_include("features.h")
#include <features.h>
#endif
#if defined(__linux__) && !defined(__ANDROID__)
#include <errno.h>
#include <sys/mman.h>
// IWYU pragma: no_include <bits/mman-map-flags-generic.h>
#include <cstring>
#include <mutex>
#include <map>
#endif
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \
@@ -45,9 +51,7 @@
#define NOMINMAX
#endif
#include <ios> // std::hex, std::dec
#include <iostream> // std::cerr
#include <ostream> // std::endl
#include <ios> // std::hex, std::dec
#include <windows.h>
// The needed Windows API for processor groups could be missed from old Windows
@@ -64,7 +68,7 @@ namespace Stockfish {
// availability of aligned_alloc(). Memory allocated with std_aligned_alloc()
// must be freed with std_aligned_free().
void* std_aligned_alloc(size_t alignment, size_t size) {
void* std_aligned_alloc(usize alignment, usize size) {
#if defined(_ISOC11_SOURCE)
return aligned_alloc(alignment, size);
#elif defined(POSIXALIGNEDALLOC)
@@ -98,19 +102,19 @@ void std_aligned_free(void* ptr) {
#if defined(_WIN32)
static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize) {
static void* aligned_large_pages_alloc_windows([[maybe_unused]] usize allocSize) {
return windows_try_with_large_page_priviliges(
[&](size_t largePageSize) {
[&](usize largePageSize) {
// Round up size to full pages and allocate
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
allocSize = (allocSize + largePageSize - 1) & ~usize(largePageSize - 1);
return VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES,
PAGE_READWRITE);
},
[]() { return (void*) nullptr; });
}
void* aligned_large_pages_alloc(size_t allocSize) {
void* aligned_large_pages_alloc_with_hint(usize allocSize, bool) {
// Try to allocate large pages
void* mem = aligned_large_pages_alloc_windows(allocSize);
@@ -124,17 +128,45 @@ void* aligned_large_pages_alloc(size_t allocSize) {
#else
void* aligned_large_pages_alloc(size_t allocSize) {
#if defined(__linux__) && defined(MAP_HUGE_SHIFT) && defined(__x86_64__)
#define HAS_HUGE_PAGES
static std::map<void*, usize> huge_pages;
static std::mutex huge_pages_mtx;
static void* try_huge_pages_alloc(usize allocSize) {
usize size = ((allocSize + HugePageSize - 1) / HugePageSize) * HugePageSize;
void* mem = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | (30 << MAP_HUGE_SHIFT), -1, 0);
if (mem == MAP_FAILED)
return nullptr;
std::lock_guard lg(huge_pages_mtx);
huge_pages[mem] = size;
return mem;
}
#endif // defined(__linux__) && defined(MAP_HUGE_SHIFT) && defined(__x86_64__)
void* aligned_large_pages_alloc_with_hint(usize allocSize, [[maybe_unused]] bool hugePageHint) {
#ifdef HAS_HUGE_PAGES
if (hugePageHint && allocSize >= HugePageSize)
{
void* mem = try_huge_pages_alloc(allocSize);
if (mem)
return mem;
}
#endif
#if defined(__linux__)
constexpr size_t alignment = 2 * 1024 * 1024; // 2MB page size assumed
constexpr usize alignment = 2 * 1024 * 1024; // 2MB page size assumed
#else
constexpr size_t alignment = 4096; // small page size assumed
constexpr usize alignment = 4096; // small page size assumed
#endif
// Round up to multiples of alignment
size_t size = ((allocSize + alignment - 1) / alignment) * alignment;
void* mem = std_aligned_alloc(alignment, size);
usize size = ((allocSize + alignment - 1) / alignment) * alignment;
void* mem = std_aligned_alloc(alignment, size);
#if defined(MADV_HUGEPAGE)
madvise(mem, size, MADV_HUGEPAGE);
#endif
@@ -143,12 +175,16 @@ void* aligned_large_pages_alloc(size_t allocSize) {
#endif
void* aligned_large_pages_alloc(usize size) {
return aligned_large_pages_alloc_with_hint(size, false);
}
bool has_large_pages() {
#if defined(_WIN32)
constexpr size_t page_size = 2 * 1024 * 1024; // 2MB page size assumed
void* mem = aligned_large_pages_alloc_windows(page_size);
constexpr usize page_size = 2 * 1024 * 1024; // 2MB page size assumed
void* mem = aligned_large_pages_alloc_windows(page_size);
if (mem == nullptr)
{
return false;
@@ -193,7 +229,26 @@ void aligned_large_pages_free(void* mem) {
#else
void aligned_large_pages_free(void* mem) { std_aligned_free(mem); }
void aligned_large_pages_free(void* mem) {
if (!mem)
return;
#ifdef HAS_HUGE_PAGES
std::lock_guard lg(huge_pages_mtx);
if (auto it = huge_pages.find(mem); it != huge_pages.end())
{
if (munmap(mem, it->second) != 0)
{
std::cerr << "munmap failed: " << strerror(errno) << std::endl;
exit(EXIT_FAILURE);
}
huge_pages.erase(it);
return;
}
#endif
std_aligned_free(mem);
}
#endif
} // namespace Stockfish
+40 -16
View File
@@ -20,14 +20,16 @@
#define MEMORY_H_INCLUDED
#include <algorithm>
#include <cstddef>
#include <cassert>
#include <cstdint>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>
#include <cstring>
#include "types.h"
#include "misc.h"
#if defined(_WIN64)
@@ -61,11 +63,14 @@ using AdjustTokenPrivileges_t =
namespace Stockfish {
void* std_aligned_alloc(size_t alignment, size_t size);
constexpr usize HugePageSize = usize(1) << 30;
void* std_aligned_alloc(usize alignment, usize size);
void std_aligned_free(void* ptr);
// Memory aligned by page size, min alignment: 4096 bytes
void* aligned_large_pages_alloc(size_t size);
void* aligned_large_pages_alloc_with_hint(usize size, bool hugePageHint);
void* aligned_large_pages_alloc(usize size);
void aligned_large_pages_free(void* mem);
bool has_large_pages();
@@ -93,15 +98,15 @@ void memory_deleter_array(T* ptr, FREE_FUNC free_func) {
// Move back on the pointer to where the size is allocated
const size_t array_offset = std::max(sizeof(size_t), alignof(T));
char* raw_memory = reinterpret_cast<char*>(ptr) - array_offset;
const usize array_offset = std::max(sizeof(usize), alignof(T));
char* raw_memory = reinterpret_cast<char*>(ptr) - array_offset;
if constexpr (!std::is_trivially_destructible_v<T>)
{
const size_t size = *reinterpret_cast<size_t*>(raw_memory);
const usize size = *reinterpret_cast<usize*>(raw_memory);
// Explicitly call the destructor for each element in reverse order
for (size_t i = size; i-- > 0;)
for (usize i = size; i-- > 0;)
ptr[i].~T();
}
@@ -120,19 +125,19 @@ inline std::enable_if_t<!std::is_array_v<T>, T*> memory_allocator(ALLOC_FUNC all
// Allocates memory for an array of unknown bound and places it there with placement new
template<typename T, typename ALLOC_FUNC>
inline std::enable_if_t<std::is_array_v<T>, std::remove_extent_t<T>*>
memory_allocator(ALLOC_FUNC alloc_func, size_t num) {
memory_allocator(ALLOC_FUNC alloc_func, usize num) {
using ElementType = std::remove_extent_t<T>;
const size_t array_offset = std::max(sizeof(size_t), alignof(ElementType));
const usize array_offset = std::max(sizeof(usize), alignof(ElementType));
// Save the array size in the memory location
char* raw_memory =
reinterpret_cast<char*>(alloc_func(array_offset + num * sizeof(ElementType)));
ASSERT_ALIGNED(raw_memory, alignof(T));
new (raw_memory) size_t(num);
new (raw_memory) usize(num);
for (size_t i = 0; i < num; ++i)
for (usize i = 0; i < num; ++i)
new (raw_memory + array_offset + i * sizeof(ElementType)) ElementType();
// Need to return the pointer at the start of the array so that
@@ -175,7 +180,7 @@ std::enable_if_t<!std::is_array_v<T>, LargePagePtr<T>> make_unique_large_page(Ar
// make_unique_large_page for arrays of unknown bound
template<typename T>
std::enable_if_t<std::is_array_v<T>, LargePagePtr<T>> make_unique_large_page(size_t num) {
std::enable_if_t<std::is_array_v<T>, LargePagePtr<T>> make_unique_large_page(usize num) {
using ElementType = std::remove_extent_t<T>;
static_assert(alignof(ElementType) <= 4096,
@@ -211,7 +216,7 @@ using AlignedPtr =
// make_unique_aligned for single objects
template<typename T, typename... Args>
std::enable_if_t<!std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(Args&&... args) {
const auto func = [](size_t size) { return std_aligned_alloc(alignof(T), size); };
const auto func = [](usize size) { return std_aligned_alloc(alignof(T), size); };
T* obj = memory_allocator<T>(func, std::forward<Args>(args)...);
return AlignedPtr<T>(obj);
@@ -219,10 +224,10 @@ std::enable_if_t<!std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(Args&&.
// make_unique_aligned for arrays of unknown bound
template<typename T>
std::enable_if_t<std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(size_t num) {
std::enable_if_t<std::is_array_v<T>, AlignedPtr<T>> make_unique_aligned(usize num) {
using ElementType = std::remove_extent_t<T>;
const auto func = [](size_t size) { return std_aligned_alloc(alignof(ElementType), size); };
const auto func = [](usize size) { return std_aligned_alloc(alignof(ElementType), size); };
ElementType* memory = memory_allocator<T>(func, num);
return AlignedPtr<T>(memory);
@@ -253,7 +258,7 @@ auto windows_try_with_large_page_priviliges([[maybe_unused]] FuncYesT&& fyes, Fu
HANDLE hProcessToken{};
LUID luid{};
const size_t largePageSize = GetLargePageMinimum();
const usize largePageSize = GetLargePageMinimum();
if (!largePageSize)
return fno();
@@ -317,6 +322,25 @@ auto windows_try_with_large_page_priviliges([[maybe_unused]] FuncYesT&& fyes, Fu
#endif
template<typename T, typename ByteT>
T load_as(const ByteT* buffer) {
static_assert(std::is_trivially_copyable<T>::value, "Type must be trivially copyable");
static_assert(sizeof(ByteT) == 1);
if (reinterpret_cast<uintptr_t>(buffer) % alignof(T) != 0)
{
assert(false);
#ifdef __GNUC__
__builtin_unreachable();
#endif
}
T value;
std::memcpy(&value, buffer, sizeof(T));
return value;
}
} // namespace Stockfish
#endif // #ifndef MEMORY_H_INCLUDED
+155 -75
View File
@@ -22,8 +22,11 @@
#include <atomic>
#include <cassert>
#include <cctype>
#include <cstring>
#include <cerrno>
#include <cmath>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
@@ -33,14 +36,25 @@
#include <sstream>
#include <string_view>
#if defined(_WIN32)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <direct.h>
#include <windows.h>
#include <shellapi.h>
#endif
#include "types.h"
namespace Stockfish {
namespace fs = std::filesystem;
namespace {
// Version number or dev.
constexpr std::string_view version = "18";
constexpr std::string_view version = "dev";
// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
@@ -83,7 +97,7 @@ class Logger {
Tie in, out;
public:
static void start(const std::string& fname) {
static void start(const fs::path& fname) {
static Logger l;
@@ -144,6 +158,10 @@ std::string engine_version_info() {
<< std::setw(2) << std::setfill('0') << day;
#endif
#ifdef GIT_DIFFINDEX
ss << "-m";
#endif
ss << "-";
#ifdef GIT_SHA
@@ -263,6 +281,12 @@ std::string compiler_info() {
compiler += " NEON_DOTPROD";
#elif defined(USE_NEON)
compiler += " NEON";
#endif
#if defined(USE_LASX)
compiler += " LASX";
#endif
#if defined(USE_LSX)
compiler += " LSX";
#endif
compiler += (HasPopCnt ? " POPCNT" : "");
@@ -288,17 +312,17 @@ constexpr int MaxDebugSlots = 32;
namespace {
template<size_t N>
template<usize N>
struct DebugInfo {
std::array<std::atomic<int64_t>, N> data = {0};
std::array<std::atomic<i64>, N> data = {0};
[[nodiscard]] constexpr std::atomic<int64_t>& operator[](size_t index) {
[[nodiscard]] constexpr std::atomic<i64>& operator[](usize index) {
assert(index < N);
return data[index];
}
constexpr DebugInfo& operator=(const DebugInfo& other) {
for (size_t i = 0; i < N; i++)
for (usize i = 0; i < N; i++)
data[i].store(other.data[i].load());
return *this;
}
@@ -306,8 +330,8 @@ struct DebugInfo {
struct DebugExtremes: public DebugInfo<3> {
DebugExtremes() {
data[1] = std::numeric_limits<int64_t>::min();
data[2] = std::numeric_limits<int64_t>::max();
data[1] = std::numeric_limits<i64>::min();
data[2] = std::numeric_limits<i64>::max();
}
};
@@ -326,32 +350,32 @@ void dbg_hit_on(bool cond, int slot) {
++hit.at(slot)[1];
}
void dbg_mean_of(int64_t value, int slot) {
void dbg_mean_of(i64 value, int slot) {
++mean.at(slot)[0];
mean.at(slot)[1] += value;
}
void dbg_stdev_of(int64_t value, int slot) {
void dbg_stdev_of(i64 value, int slot) {
++stdev.at(slot)[0];
stdev.at(slot)[1] += value;
stdev.at(slot)[2] += value * value;
}
void dbg_extremes_of(int64_t value, int slot) {
void dbg_extremes_of(i64 value, int slot) {
++extremes.at(slot)[0];
int64_t current_max = extremes.at(slot)[1].load();
i64 current_max = extremes.at(slot)[1].load();
while (current_max < value && !extremes.at(slot)[1].compare_exchange_weak(current_max, value))
{}
int64_t current_min = extremes.at(slot)[2].load();
i64 current_min = extremes.at(slot)[2].load();
while (current_min > value && !extremes.at(slot)[2].compare_exchange_weak(current_min, value))
{}
}
void dbg_correl_of(int64_t value1, int64_t value2, int slot) {
void dbg_correl_of(i64 value1, i64 value2, int slot) {
++correl.at(slot)[0];
correl.at(slot)[1] += value1;
@@ -363,9 +387,9 @@ void dbg_correl_of(int64_t value1, int64_t value2, int slot) {
void dbg_print() {
int64_t n;
auto E = [&n](int64_t x) { return double(x) / n; };
auto sqr = [](double x) { return x * x; };
i64 n;
auto E = [&n](i64 x) { return double(x) / n; };
auto sqr = [](double x) { return x * x; };
for (int i = 0; i < MaxDebugSlots; ++i)
if ((n = hit[i][0]))
@@ -428,40 +452,112 @@ std::ostream& operator<<(std::ostream& os, SyncCout sc) {
void sync_cout_start() { std::cout << IO_LOCK; }
void sync_cout_end() { std::cout << IO_UNLOCK; }
// Trampoline helper to avoid moving Logger to misc.h
void start_logger(const std::string& fname) { Logger::start(fname); }
// Hash function based on public domain MurmurHash64A, by Austin Appleby.
u64 hash_bytes(const char* data, usize size) {
const u64 m = 0xc6a4a7935bd1e995ull;
const int r = 47;
u64 h = size * m;
#ifdef NO_PREFETCH
const char* end = data + (size & ~(usize) 7);
void prefetch(const void*) {}
for (const char* p = data; p != end; p += 8)
{
u64 k;
std::memcpy(&k, p, sizeof(k));
#else
k *= m;
k ^= k >> r;
k *= m;
void prefetch(const void* addr) {
h ^= k;
h *= m;
}
#if defined(_MSC_VER)
_mm_prefetch((char const*) addr, _MM_HINT_T0);
#else
__builtin_prefetch(addr);
#endif
if (size & 7)
{
u64 k = 0;
for (int i = (size & 7) - 1; i >= 0; i--)
k = (k << 8) | u64(end[i]);
h ^= k;
h *= m;
}
h ^= h >> r;
h *= m;
h ^= h >> r;
return h;
}
#endif
// Trampoline helper to avoid moving Logger to misc.h
void start_logger(const fs::path& fname) { Logger::start(fname); }
std::string utf8_from_wstring(std::wstring_view s) {
#ifdef _WIN32
#include <direct.h>
#define GETCWD _getcwd
#else
#include <unistd.h>
#define GETCWD getcwd
#endif
if (s.empty())
return {};
size_t str_to_size_t(const std::string& s) {
unsigned long long value = std::stoull(s);
if (value > std::numeric_limits<size_t>::max())
std::exit(EXIT_FAILURE);
return static_cast<size_t>(value);
int size =
WideCharToMultiByte(CP_UTF8, 0, s.data(), int(s.size()), nullptr, 0, nullptr, nullptr);
if (size <= 0)
return {};
std::string out(size, '\0');
WideCharToMultiByte(CP_UTF8, 0, s.data(), int(s.size()), out.data(), size, nullptr, nullptr);
return out;
#else
return std::string(s.begin(), s.end());
#endif
}
fs::path path_from_utf8(const std::string& path) {
#ifdef _WIN32
int u8len = static_cast<int>(path.size());
int wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), u8len, NULL, 0);
std::wstring wstr(static_cast<usize>(wlen), L'\0');
MultiByteToWideChar(CP_UTF8, 0, path.c_str(), u8len, wstr.data(), wlen);
return {wstr};
#else
return {path};
#endif
}
CommandLine::CommandLine(int _argc, char** _argv) :
argc(_argc),
argv(_argv) {
#ifdef _WIN32
// Convert any non-ANSI characters passed on the command line to UTF-8
int wargc = 0;
if (LPWSTR* wargv = CommandLineToArgvW(GetCommandLineW(), &wargc))
{
for (int i = 0; i < wargc; ++i)
argv_storage.push_back(utf8_from_wstring(wargv[i]));
LocalFree(wargv);
for (std::string& s : argv_storage)
argv_utf8.push_back(s.data());
argv_utf8.push_back(nullptr);
argc = wargc;
argv = argv_utf8.data();
}
#endif
}
std::optional<usize> str_to_size_t(const std::string& s) {
if (s.empty() || s[0] == '-')
return std::nullopt;
errno = 0;
char* endptr = nullptr;
const unsigned long long value = std::strtoull(s.c_str(), &endptr, 10);
if (errno == ERANGE || (*endptr != '\0' && !std::isspace(*endptr))
|| value > std::numeric_limits<usize>::max())
return std::nullopt;
return static_cast<usize>(value);
}
std::optional<std::string> read_file_to_string(const std::string& path) {
@@ -479,49 +575,33 @@ bool is_whitespace(std::string_view s) {
return std::all_of(s.begin(), s.end(), [](char c) { return std::isspace(c); });
}
std::string CommandLine::get_binary_directory(std::string argv0) {
std::string pathSeparator;
fs::path CommandLine::get_binary_directory(fs::path argv0) {
#ifdef _WIN32
pathSeparator = "\\";
#ifdef _MSC_VER
// Under windows argv[0] may not have the extension. Also _get_pgmptr() had
// issues in some Windows 10 versions, so check returned values carefully.
char* pgmptr = nullptr;
if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr)
argv0 = pgmptr;
#endif
#else
pathSeparator = "/";
// Prefer the executable path reported by Windows. Unlike _get_wpgmptr,
// this does not depend on whether the CRT used a narrow or wide entry
// point. Windows paths cannot exceed 32767 characters, so a fixed
// buffer is always sufficient. Falls back to argv0 if the API fails.
constexpr DWORD MaxPath = 32768;
wchar_t path[MaxPath];
if (const DWORD length = GetModuleFileNameW(nullptr, path, MaxPath))
argv0 = fs::path(path, path + length);
#endif
// Extract the working directory
auto workingDirectory = CommandLine::get_working_directory();
// Extract the binary directory path from argv0
auto binaryDirectory = argv0;
size_t pos = binaryDirectory.find_last_of("\\/");
if (pos == std::string::npos)
binaryDirectory = "." + pathSeparator;
else
binaryDirectory.resize(pos + 1);
// Pattern replacement: "./" at the start of path is replaced by the working directory
if (binaryDirectory.find("." + pathSeparator) == 0)
binaryDirectory.replace(0, 1, workingDirectory);
auto binaryDirectory = argv0.parent_path();
if (binaryDirectory.empty())
binaryDirectory = fs::path(".");
return binaryDirectory;
}
std::string CommandLine::get_working_directory() {
std::string workingDirectory = "";
char buff[40000];
char* cwd = GETCWD(buff, 40000);
if (cwd)
workingDirectory = cwd;
fs::path CommandLine::get_working_directory() { return std::filesystem::current_path(); }
return workingDirectory;
void set_console_utf8() {
#ifdef _WIN32
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
#endif
}
} // namespace Stockfish
+285 -154
View File
@@ -21,67 +21,136 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <exception> // IWYU pragma: keep
// IWYU pragma: no_include <__exception/terminate.h>
#include <functional>
#include <filesystem>
#include <iosfwd>
#include <optional>
#include <cstring>
#include <memory>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
#if !defined(NO_PREFETCH) && (defined(_MSC_VER) || defined(__INTEL_COMPILER))
#include <immintrin.h>
#endif
#define stringify2(x) #x
#define stringify(x) stringify2(x)
namespace Stockfish {
using u64 = std::uint64_t;
using u32 = std::uint32_t;
using u16 = std::uint16_t;
using u8 = std::uint8_t;
using i64 = std::int64_t;
using i32 = std::int32_t;
using i16 = std::int16_t;
using i8 = std::int8_t;
using usize = std::size_t;
using isize = std::ptrdiff_t;
#if defined(__GNUC__) && defined(IS_64BIT)
__extension__ using u128 = unsigned __int128;
__extension__ using i128 = signed __int128;
#endif
std::string engine_version_info();
std::string engine_info(bool to_uci = false);
std::string compiler_info();
// Preloads the given address in L1/L2 cache. This is a non-blocking
// function that doesn't stall the CPU waiting for data to be loaded from memory,
// which can be quite slow.
void prefetch(const void* addr);
void start_logger(const std::string& fname);
size_t str_to_size_t(const std::string& s);
#if defined(__linux__)
struct PipeDeleter {
void operator()(FILE* file) const {
if (file != nullptr)
{
pclose(file);
}
}
// Prefetch hint enums for explicit call-site control.
enum class PrefetchRw {
READ,
WRITE
};
// NOTE: PrefetchLoc controls locality / cache level, not whether a prefetch
// is issued. In particular, PrefetchLoc::NONE maps to a non-temporal /
// lowest-locality prefetch (Intel: _MM_HINT_NTA, GCC/Clang: locality = 0)
// and therefore still performs a prefetch. To completely disable
// prefetching, define NO_PREFETCH so that prefetch() becomes a no-op.
enum class PrefetchLoc {
NONE, // Non-temporal / no cache locality (still issues a prefetch)
LOW, // Low locality (e.g. T2 / L2)
MODERATE, // Moderate locality (e.g. T1 / L1)
HIGH // High locality (e.g. T0 / closest cache)
};
// Preloads the given address into cache. This is a non-blocking
// function that doesn't stall the CPU waiting for data to be loaded from memory,
// which can be quite slow.
#ifdef NO_PREFETCH
template<PrefetchRw RW = PrefetchRw::READ, PrefetchLoc LOC = PrefetchLoc::HIGH>
void prefetch(const void*) {}
#elif defined(_MSC_VER) || defined(__INTEL_COMPILER)
constexpr int get_intel_hint(PrefetchRw rw, PrefetchLoc loc) {
if (rw == PrefetchRw::WRITE)
{
#ifdef _MM_HINT_ET0
return _MM_HINT_ET0;
#else
// Fallback when write-prefetch hint is not available: use T0
return _MM_HINT_T0;
#endif
}
switch (loc)
{
case PrefetchLoc::NONE :
return _MM_HINT_NTA;
case PrefetchLoc::LOW :
return _MM_HINT_T2;
case PrefetchLoc::MODERATE :
return _MM_HINT_T1;
case PrefetchLoc::HIGH :
return _MM_HINT_T0;
default :
return _MM_HINT_T0;
}
}
template<PrefetchRw RW = PrefetchRw::READ, PrefetchLoc LOC = PrefetchLoc::HIGH>
void prefetch(const void* addr) {
_mm_prefetch(static_cast<const char*>(addr), get_intel_hint(RW, LOC));
}
#else
template<PrefetchRw RW = PrefetchRw::READ, PrefetchLoc LOC = PrefetchLoc::HIGH>
void prefetch(const void* addr) {
__builtin_prefetch(addr, static_cast<int>(RW), static_cast<int>(LOC));
}
#endif
void start_logger(const std::filesystem::path& fname);
std::optional<usize> str_to_size_t(const std::string& s);
std::string utf8_from_wstring(std::wstring_view s);
std::filesystem::path path_from_utf8(const std::string& path);
// Reads the file as bytes.
// Returns std::nullopt if the file does not exist.
std::optional<std::string> read_file_to_string(const std::string& path);
void dbg_hit_on(bool cond, int slot = 0);
void dbg_mean_of(int64_t value, int slot = 0);
void dbg_stdev_of(int64_t value, int slot = 0);
void dbg_extremes_of(int64_t value, int slot = 0);
void dbg_correl_of(int64_t value1, int64_t value2, int slot = 0);
void dbg_mean_of(i64 value, int slot = 0);
void dbg_stdev_of(i64 value, int slot = 0);
void dbg_extremes_of(i64 value, int slot = 0);
void dbg_correl_of(i64 value1, i64 value2, int slot = 0);
void dbg_print();
void dbg_clear();
using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
static_assert(sizeof(TimePoint) == sizeof(i64), "TimePoint should be 64 bits");
inline TimePoint now() {
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch())
@@ -94,10 +163,10 @@ inline std::vector<std::string_view> split(std::string_view s, std::string_view
if (s.empty())
return res;
size_t begin = 0;
usize begin = 0;
for (;;)
{
const size_t end = s.find(delimiter, begin);
const usize end = s.find(delimiter, begin);
if (end == std::string::npos)
break;
@@ -126,25 +195,31 @@ void sync_cout_start();
void sync_cout_end();
// True if and only if the binary is compiled on a little-endian machine
static inline const std::uint16_t Le = 1;
static inline const bool IsLittleEndian = *reinterpret_cast<const char*>(&Le) == 1;
static inline const u16 Le = 1;
static inline const bool IsLittleEndian = *reinterpret_cast<const char*>(&Le) == 1;
template<typename T, std::size_t MaxSize>
template<typename T, usize MaxSize>
class ValueList {
public:
std::size_t size() const { return size_; }
int ssize() const { return int(size_); }
void push_back(const T& value) {
usize size() const { return size_; }
int ssize() const { return int(size_); }
void push_back(const T& value) {
assert(size_ < MaxSize);
values_[size_++] = value;
}
// pushes back value if value < max
void push_back_if_lt(const T& value, const T& max) {
assert(size_ < MaxSize);
values_[size_] = value;
size_ += (value < max);
}
const T* begin() const { return values_; }
const T* end() const { return values_ + size_; }
const T& operator[](int index) const { return values_[index]; }
T* make_space(size_t count) {
T* make_space(usize count) {
T* result = &values_[size_];
size_ += count;
assert(size_ <= MaxSize);
@@ -152,22 +227,22 @@ class ValueList {
}
private:
T values_[MaxSize];
std::size_t size_ = 0;
T values_[MaxSize];
usize size_ = 0;
};
template<typename T, std::size_t Size, std::size_t... Sizes>
template<typename T, usize Size, usize... Sizes>
class MultiArray;
namespace Detail {
template<typename T, std::size_t Size, std::size_t... Sizes>
template<typename T, usize Size, usize... Sizes>
struct MultiArrayHelper {
using ChildType = MultiArray<T, Sizes...>;
};
template<typename T, std::size_t Size>
template<typename T, usize Size>
struct MultiArrayHelper<T, Size> {
using ChildType = T;
};
@@ -180,7 +255,7 @@ constexpr bool is_strictly_assignable_v =
// MultiArray is a generic N-dimensional array.
// The template parameters (Size and Sizes) encode the dimensions of the array.
template<typename T, std::size_t Size, std::size_t... Sizes>
template<typename T, usize Size, usize... Sizes>
class MultiArray {
using ChildType = typename Detail::MultiArrayHelper<T, Size, Sizes...>::ChildType;
using ArrayType = std::array<ChildType, Size>;
@@ -199,11 +274,17 @@ class MultiArray {
using reverse_iterator = typename ArrayType::reverse_iterator;
using const_reverse_iterator = typename ArrayType::const_reverse_iterator;
constexpr auto& at(size_type index) noexcept { return data_.at(index); }
constexpr const auto& at(size_type index) const noexcept { return data_.at(index); }
constexpr auto& at(size_type index) { return data_.at(index); }
constexpr const auto& at(size_type index) const { return data_.at(index); }
constexpr auto& operator[](size_type index) noexcept { return data_[index]; }
constexpr const auto& operator[](size_type index) const noexcept { return data_[index]; }
constexpr auto& operator[](size_type index) noexcept {
assert(index < Size);
return data_[index];
}
constexpr const auto& operator[](size_type index) const noexcept {
assert(index < Size);
return data_[index];
}
constexpr auto& front() noexcept { return data_.front(); }
constexpr const auto& front() const noexcept { return data_.front(); }
@@ -247,6 +328,102 @@ class MultiArray {
constexpr void swap(MultiArray<T, Size, Sizes...>& other) noexcept { data_.swap(other.data_); }
};
// Wrapper around std::atomic<T> which uses relaxed accesses or plain
// accesses, depending on the config. Intended use is e.g. wasm where
// the overhead of atomic instructions can be significant, and we only
// require non-tearing for the updates, while ensuring we use relaxed
// accesses otherwise.
template<typename T>
class RelaxedAtomic {
static constexpr bool UseAtomic =
#ifdef USE_SLOPPY_ATOMICS
!std::atomic<T>::is_always_lock_free || sizeof(T) > sizeof(usize);
#else
true;
#endif
public:
RelaxedAtomic() = default;
RelaxedAtomic(T val) :
inner(val) {}
RelaxedAtomic(const RelaxedAtomic& a) :
inner(static_cast<T>(a)) {}
T operator=(T val) {
if constexpr (UseAtomic)
inner.store(val, std::memory_order_relaxed);
else
inner = val;
return val;
}
RelaxedAtomic& operator=(const RelaxedAtomic& a) {
this->store(static_cast<T>(a), std::memory_order_relaxed);
return *this;
}
operator T() const {
if constexpr (UseAtomic)
return inner.load(std::memory_order_relaxed);
else
return inner;
}
RelaxedAtomic& operator+=(T val) {
T res = this->load(std::memory_order_relaxed) + val;
this->store(res, std::memory_order_relaxed);
return *this;
}
RelaxedAtomic& operator++() {
T res = this->load(std::memory_order_relaxed) + 1;
this->store(res, std::memory_order_relaxed);
return *this;
}
RelaxedAtomic& operator--() {
T res = this->load(std::memory_order_relaxed) - 1;
this->store(res, std::memory_order_relaxed);
return *this;
}
T operator++(int) {
T val = this->load(std::memory_order_relaxed);
this->store(val + 1, std::memory_order_relaxed);
return val;
}
T operator--(int) {
T val = this->load(std::memory_order_relaxed);
this->store(val - 1, std::memory_order_relaxed);
return val;
}
RelaxedAtomic& operator-=(T val) {
T res = this->load(std::memory_order_relaxed) - val;
this->store(res, std::memory_order_relaxed);
return *this;
}
T load(std::memory_order order) const {
assert(order == std::memory_order_relaxed);
if constexpr (UseAtomic)
return inner.load(order);
else
return inner;
}
void store(T val, std::memory_order order) {
assert(order == std::memory_order_relaxed);
if constexpr (UseAtomic)
inner.store(val, order);
else
inner = val;
}
private:
std::conditional_t<UseAtomic, std::atomic<T>, T> inner;
};
// xorshift64star Pseudo-Random Number Generator
// This class is based on original code written and dedicated
@@ -265,16 +442,16 @@ class MultiArray {
class PRNG {
uint64_t s;
u64 s;
uint64_t rand64() {
u64 rand64() {
s ^= s >> 12, s ^= s << 25, s ^= s >> 27;
return s * 2685821657736338717LL;
}
public:
PRNG(uint64_t seed) :
PRNG(u64 seed) :
s(seed) {
assert(seed);
}
@@ -292,41 +469,38 @@ class PRNG {
}
};
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
#if defined(__GNUC__) && defined(IS_64BIT)
__extension__ using uint128 = unsigned __int128;
return (uint128(a) * uint128(b)) >> 64;
inline usize mul_hi64(u64 a, usize b) {
#if defined(__GNUC__) && defined(IS_64BIT) && !defined(__wasm__)
return (u128(a) * u128(b)) >> 64;
#else
uint64_t aL = uint32_t(a), aH = a >> 32;
uint64_t bL = uint32_t(b), bH = b >> 32;
uint64_t c1 = (aL * bL) >> 32;
uint64_t c2 = aH * bL + c1;
uint64_t c3 = aL * bH + uint32_t(c2);
u64 aL = u32(a), aH = a >> 32;
u64 bL = u32(b), bH = u64(b) >> 32;
u64 c1 = (aL * bL) >> 32;
u64 c2 = aH * bL + c1;
u64 c3 = aL * bH + u32(c2);
return aH * bH + (c2 >> 32) + (c3 >> 32);
#endif
}
inline std::uint64_t hash_bytes(const char* data, std::size_t size) {
// FNV-1a 64-bit
const char* p = data;
std::uint64_t h = 14695981039346656037ull;
for (std::size_t i = 0; i < size; ++i)
h = (h ^ p[i]) * 1099511628211ull;
return h;
template<typename T1, typename T2>
inline constexpr T2 interpolate(T1 x, T1 x0, T1 x1, T2 y0, T2 y1) {
assert(x0 != x1);
return T2(y0 + (y1 - y0) * (x - x0) / (x1 - x0));
}
u64 hash_bytes(const char*, usize);
template<typename T>
inline std::size_t get_raw_data_hash(const T& value) {
inline usize get_raw_data_hash(const T& value) {
// We must have no padding bytes because we're reinterpreting as char
static_assert(std::has_unique_object_representations<T>());
return static_cast<std::size_t>(
hash_bytes(reinterpret_cast<const char*>(&value), sizeof(value)));
return static_cast<usize>(hash_bytes(reinterpret_cast<const char*>(&value), sizeof(value)));
}
template<typename T>
inline void hash_combine(std::size_t& seed, const T& v) {
std::size_t x;
inline void hash_combine(usize& seed, const T& v) {
usize x;
// For primitive types we avoid using the default hasher, which may be
// nondeterministic across program invocations
if constexpr (std::is_integral<T>())
@@ -336,90 +510,28 @@ inline void hash_combine(std::size_t& seed, const T& v) {
seed ^= x + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
inline std::uint64_t hash_string(const std::string& sv) { return hash_bytes(sv.data(), sv.size()); }
template<std::size_t Capacity>
class FixedString {
public:
FixedString() :
length_(0) {
data_[0] = '\0';
}
FixedString(const char* str) {
size_t len = std::strlen(str);
if (len > Capacity)
std::terminate();
std::memcpy(data_, str, len);
length_ = len;
data_[length_] = '\0';
}
FixedString(const std::string& str) {
if (str.size() > Capacity)
std::terminate();
std::memcpy(data_, str.data(), str.size());
length_ = str.size();
data_[length_] = '\0';
}
std::size_t size() const { return length_; }
std::size_t capacity() const { return Capacity; }
const char* c_str() const { return data_; }
const char* data() const { return data_; }
char& operator[](std::size_t i) { return data_[i]; }
const char& operator[](std::size_t i) const { return data_[i]; }
FixedString& operator+=(const char* str) {
size_t len = std::strlen(str);
if (length_ + len > Capacity)
std::terminate();
std::memcpy(data_ + length_, str, len);
length_ += len;
data_[length_] = '\0';
return *this;
}
FixedString& operator+=(const FixedString& other) { return (*this += other.c_str()); }
operator std::string() const { return std::string(data_, length_); }
operator std::string_view() const { return std::string_view(data_, length_); }
template<typename T>
bool operator==(const T& other) const noexcept {
return (std::string_view) (*this) == other;
}
template<typename T>
bool operator!=(const T& other) const noexcept {
return (std::string_view) (*this) != other;
}
void clear() {
length_ = 0;
data_[0] = '\0';
}
private:
char data_[Capacity + 1]; // +1 for null terminator
std::size_t length_;
};
inline u64 hash_string(const std::string& sv) { return hash_bytes(sv.data(), sv.size()); }
struct CommandLine {
public:
CommandLine(int _argc, char** _argv) :
argc(_argc),
argv(_argv) {}
CommandLine(int _argc, char** _argv);
static std::string get_binary_directory(std::string argv0);
static std::string get_working_directory();
CommandLine(const CommandLine&) = delete;
CommandLine& operator=(const CommandLine&) = delete;
CommandLine(CommandLine&&) = default;
CommandLine& operator=(CommandLine&&) = default;
static std::filesystem::path get_binary_directory(std::filesystem::path argv0);
static std::filesystem::path get_working_directory();
int argc;
char** argv;
private:
#ifdef _WIN32
std::vector<std::string> argv_storage;
std::vector<char*> argv_utf8;
#endif
};
namespace Utility {
@@ -435,6 +547,10 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
}
}
#ifndef __has_builtin
#define __has_builtin(x) 0
#endif
#if defined(__GNUC__)
#define sf_always_inline __attribute__((always_inline))
#elif defined(_MSC_VER)
@@ -444,7 +560,9 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
#define sf_always_inline
#endif
#if defined(__GNUC__) && !defined(__clang__)
#if defined(__clang__)
#define sf_assume(cond) __builtin_assume(cond)
#elif defined(__GNUC__)
#if __GNUC__ >= 13
#define sf_assume(cond) __attribute__((assume(cond)))
#else
@@ -455,18 +573,31 @@ void move_to_front(std::vector<T>& vec, Predicate pred) {
__builtin_unreachable(); \
} while (0)
#endif
#elif defined(_MSC_VER)
#define sf_assume(cond) __assume(cond)
#else
// do nothing for other compilers
#define sf_assume(cond)
#endif
#ifdef __GNUC__
#define sf_unreachable() __builtin_unreachable()
#elif defined(_MSC_VER)
#define sf_unreachable() __assume(0)
#else
#define sf_unreachable()
#endif
#ifdef __GNUC__
#define RESTRICT __restrict__
#elif defined(_MSC_VER)
#define RESTRICT __restrict
#else
#define RESTRICT
#endif
void set_console_utf8();
} // namespace Stockfish
template<std::size_t N>
struct std::hash<Stockfish::FixedString<N>> {
std::size_t operator()(const Stockfish::FixedString<N>& fstr) const noexcept {
return Stockfish::hash_bytes(fstr.data(), fstr.size());
}
};
#endif // #ifndef MISC_H_INCLUDED
+20 -42
View File
@@ -21,6 +21,7 @@
#include <cassert>
#include <initializer_list>
#include "attacks.h"
#include "bitboard.h"
#include "position.h"
@@ -36,53 +37,30 @@ namespace {
#if defined(USE_AVX512ICL)
inline Move* write_moves(Move* moveList, uint32_t mask, __m512i vector) {
// Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4
_mm512_storeu_si512(reinterpret_cast<__m512i*>(moveList),
_mm512_maskz_compress_epi16(mask, vector));
return moveList + popcount(mask);
}
template<Direction offset>
inline Move* splat_pawn_moves(Move* moveList, Bitboard to_bb) {
alignas(64) static constexpr auto SPLAT_TABLE = [] {
std::array<Move, 64> table{};
for (int i = 0; i < 64; i++)
{
Square from{uint8_t(std::clamp(i - offset, 0, 63))};
table[i] = {Move(from, Square{uint8_t(i)})};
}
return table;
}();
assert(popcount(to_bb) <= 8); // <= 8 pawns per side
auto table = reinterpret_cast<const __m512i*>(SPLAT_TABLE.data());
const __m128i toSquares =
_mm_cvtepi8_epi16(_mm512_castsi512_si128(_mm512_maskz_compress_epi8(to_bb, AllSquares)));
const __m128i fromSquares = _mm_subs_epi16(toSquares, _mm_set1_epi16(offset));
const __m128i moves = _mm_or_si128(_mm_slli_epi16(fromSquares, Move::FromSqShift),
_mm_slli_epi16(toSquares, Move::ToSqShift));
moveList =
write_moves(moveList, static_cast<uint32_t>(to_bb >> 0), _mm512_load_si512(table + 0));
moveList =
write_moves(moveList, static_cast<uint32_t>(to_bb >> 32), _mm512_load_si512(table + 1));
return moveList;
_mm_storeu_si128(reinterpret_cast<__m128i*>(moveList), moves);
return moveList + popcount(to_bb);
}
inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) {
alignas(64) static constexpr auto SPLAT_TABLE = [] {
std::array<Move, 64> table{};
for (uint8_t i = 0; i < 64; i++)
table[i] = {Move(SQUARE_ZERO, Square{i})};
return table;
}();
assert(popcount(to_bb) <= 32); // Q can attack up to 27 squares
__m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw());
const __m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw());
const __m512i toSquares =
_mm512_cvtepi8_epi16(_mm512_castsi512_si256(_mm512_maskz_compress_epi8(to_bb, AllSquares)));
const __m512i moves = _mm512_or_si512(fromVec, _mm512_slli_epi16(toSquares, Move::ToSqShift));
auto table = reinterpret_cast<const __m512i*>(SPLAT_TABLE.data());
moveList = write_moves(moveList, static_cast<uint32_t>(to_bb >> 0),
_mm512_or_si512(_mm512_load_si512(table + 0), fromVec));
moveList = write_moves(moveList, static_cast<uint32_t>(to_bb >> 32),
_mm512_or_si512(_mm512_load_si512(table + 1), fromVec));
return moveList;
_mm512_storeu_si512(moveList, moves);
return moveList + popcount(to_bb);
}
#else
@@ -193,7 +171,7 @@ Move* generate_pawn_moves(const Position& pos, Move* moveList, Bitboard target)
if (Type == EVASIONS && (target & (pos.ep_square() + Up)))
return moveList;
b1 = pawnsNotOn7 & attacks_bb<PAWN>(pos.ep_square(), Them);
b1 = pawnsNotOn7 & Attacks::attacks_bb<PAWN>(pos.ep_square(), Them);
assert(b1);
@@ -216,7 +194,7 @@ Move* generate_moves(const Position& pos, Move* moveList, Bitboard target) {
while (bb)
{
Square from = pop_lsb(bb);
Bitboard b = attacks_bb<Pt>(from, pos.pieces()) & target;
Bitboard b = Attacks::attacks_bb<Pt>(from, pos.pieces()) & target;
moveList = splat_moves(moveList, from, b);
}
@@ -236,7 +214,7 @@ Move* generate_all(const Position& pos, Move* moveList) {
// Skip generating non-king moves when in double check
if (Type != EVASIONS || !more_than_one(pos.checkers()))
{
target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers()))
target = Type == EVASIONS ? Attacks::between_bb(ksq, lsb(pos.checkers()))
: Type == NON_EVASIONS ? ~pos.pieces(Us)
: Type == CAPTURES ? pos.pieces(~Us)
: ~pos.pieces(); // QUIETS
@@ -248,7 +226,7 @@ Move* generate_all(const Position& pos, Move* moveList) {
moveList = generate_moves<Us, QUEEN>(pos, moveList, target);
}
Bitboard b = attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
Bitboard b = Attacks::attacks_bb<KING>(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target);
moveList = splat_moves(moveList, ksq, b);
+2 -2
View File
@@ -20,8 +20,8 @@
#define MOVEGEN_H_INCLUDED
#include <algorithm> // IWYU pragma: keep
#include <cstddef>
#include "misc.h"
#include "types.h"
namespace Stockfish {
@@ -61,7 +61,7 @@ struct MoveList {
last(generate<T>(pos, moveList)) {}
const Move* begin() const { return moveList; }
const Move* end() const { return last; }
size_t size() const { return last - moveList; }
usize size() const { return last - moveList; }
bool contains(Move move) const { return std::find(begin(), end(), move) != end(); }
private:
+76 -6
View File
@@ -56,12 +56,82 @@ enum Stages {
QCAPTURE
};
#ifdef USE_AVX512
// Load the Move, and the ExtMove value, into all lanes of 512-bit registers
static void splat_extmove(const ExtMove& m, __m512i& move, __m512i& value) {
move = _mm512_set1_epi32(m.raw());
value = _mm512_set1_epi32(m.value);
}
// Sorts up to 16 moves.
struct MoveSorter {
static constexpr int MAX_ELEMENTS = 16;
__m512i sortedValues, sortedMoves;
explicit MoveSorter(const ExtMove& first) {
splat_extmove(first, sortedMoves, sortedValues);
// Set the uninitialized move values to INT_MIN, so that they sort less than any other move
sortedValues = _mm512_mask_set1_epi32(sortedValues, ~1, std::numeric_limits<int>::min());
}
void insert(const ExtMove& m) {
__m512i move, value;
splat_extmove(m, move, value);
// Mask of all elements except the insertion point
assert(m.value != std::numeric_limits<int>::min());
const u16 expand = _kadd_mask16(_mm512_cmplt_epi32_mask(sortedValues, value), -1);
sortedValues = _mm512_mask_expand_epi32(value, expand, sortedValues);
sortedMoves = _mm512_mask_expand_epi32(move, expand, sortedMoves);
}
void write_sorted(ExtMove* moves, isize count) const {
static_assert(sizeof(ExtMove) == 8);
assert(count <= MAX_ELEMENTS);
// Because values and moves are stored separately, we need to reassemble the ExtMoves
auto write = [&](int offset, const __m512i indices) {
const __m512i extMoves = _mm512_permutex2var_epi32(sortedMoves, indices, sortedValues);
const isize storeCount = count - offset;
if (storeCount > 0)
_mm512_mask_storeu_epi64(moves + offset, (1 << storeCount) - 1, extMoves);
};
write(0, _mm512_setr_epi32(0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23));
write(8, _mm512_setr_epi32(8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31));
}
};
#endif
// Sort moves in descending order up to and including a given limit.
// The order of moves smaller than the limit is left unspecified.
void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
ExtMove *sortedEnd = begin, *p = begin + 1;
for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p)
#ifdef USE_AVX512
if (begin == end)
return;
MoveSorter sorter(*begin);
for (; p < end; ++p)
{
if (p->value >= limit)
{
if (sortedEnd - begin + 1 >= MoveSorter::MAX_ELEMENTS) // sorter full
break;
sorter.insert(*p);
*p = *++sortedEnd;
}
}
sorter.write_sorted(begin, sortedEnd - begin + 1);
// Use scalar implementation for any remaining elements
#endif
for (; p < end; ++p)
if (p->value >= limit)
{
ExtMove tmp = *p, *q;
@@ -120,9 +190,9 @@ MovePicker::MovePicker(const Position& p, Move ttm, int th, const CapturePieceTo
// Assigns a numerical value to each move in a list, used for sorting.
// Captures are ordered by Most Valuable Victim (MVV), preferring captures
// with a good history. Quiets moves are ordered using the history tables.
// with a good history. Quiet moves are ordered using the history tables.
template<GenType Type>
ExtMove* MovePicker::score(MoveList<Type>& ml) {
ExtMove* MovePicker::score(const MoveList<Type>& ml) {
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
@@ -136,7 +206,7 @@ ExtMove* MovePicker::score(MoveList<Type>& ml) {
threatByLesser[ROOK] =
pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatByLesser[KNIGHT];
threatByLesser[QUEEN] = pos.attacks_by<ROOK>(~us) | threatByLesser[ROOK];
threatByLesser[KING] = pos.attacks_by<QUEEN>(~us) | threatByLesser[QUEEN];
threatByLesser[KING] = 0;
}
ExtMove* it = cur;
@@ -167,11 +237,11 @@ ExtMove* MovePicker::score(MoveList<Type>& ml) {
m.value += (*continuationHistory[5])[pc][to];
// bonus for checks
m.value += (bool(pos.check_squares(pt) & to) && pos.see_ge(m, -75)) * 16384;
m.value += ((pos.check_squares(pt) & to) && pos.see_ge(m, -75)) * 16384;
// penalty for moving to a square threatened by a lesser piece
// or bonus for escaping an attack by a lesser piece.
int v = threatByLesser[pt] & to ? -19 : 20 * bool(threatByLesser[pt] & from);
int v = 20 * (bool(threatByLesser[pt] & from) - bool(threatByLesser[pt] & to));
m.value += PieceValue[pt] * v;
+1 -3
View File
@@ -55,9 +55,7 @@ class MovePicker {
template<typename Pred>
Move select(Pred);
template<GenType T>
ExtMove* score(MoveList<T>&);
ExtMove* begin() { return cur; }
ExtMove* end() { return endCur; }
ExtMove* score(const MoveList<T>&);
const Position& pos;
const ButterflyHistory* mainHistory;
+68 -108
View File
@@ -25,6 +25,7 @@
#include <initializer_list>
#include <utility>
#include "../../attacks.h"
#include "../../bitboard.h"
#include "../../misc.h"
#include "../../position.h"
@@ -46,11 +47,11 @@ template<PieceType PT>
constexpr auto make_piece_indices_type() {
static_assert(PT != PieceType::PAWN);
std::array<std::array<uint8_t, SQUARE_NB>, SQUARE_NB> out{};
std::array<std::array<u8, SQUARE_NB>, SQUARE_NB> out{};
for (Square from = SQ_A1; from <= SQ_H8; ++from)
{
Bitboard attacks = PseudoAttacks[PT][from];
Bitboard attacks = Attacks::PseudoAttacks[PT][from];
for (Square to = SQ_A1; to <= SQ_H8; ++to)
{
@@ -65,13 +66,13 @@ template<Piece P>
constexpr auto make_piece_indices_piece() {
static_assert(type_of(P) == PieceType::PAWN);
std::array<std::array<uint8_t, SQUARE_NB>, SQUARE_NB> out{};
std::array<std::array<u8, SQUARE_NB>, SQUARE_NB> out{};
constexpr Color C = color_of(P);
for (Square from = SQ_A1; from <= SQ_H8; ++from)
{
Bitboard attacks = PseudoAttacks[C][from];
Bitboard attacks = Attacks::PseudoAttacks[C][from];
for (Square to = SQ_A1; to <= SQ_H8; ++to)
{
@@ -89,7 +90,7 @@ constexpr auto index_lut2_array() {
constexpr auto QUEEN_ATTACKS = make_piece_indices_type<PieceType::QUEEN>();
constexpr auto KING_ATTACKS = make_piece_indices_type<PieceType::KING>();
std::array<std::array<std::array<uint8_t, SQUARE_NB>, SQUARE_NB>, PIECE_NB> indices{};
std::array<std::array<std::array<u8, SQUARE_NB>, SQUARE_NB>, PIECE_NB> indices{};
indices[W_PAWN] = make_piece_indices_piece<W_PAWN>();
indices[B_PAWN] = make_piece_indices_piece<B_PAWN>();
@@ -128,14 +129,14 @@ constexpr auto init_threat_offsets() {
if (type_of(piece) != PAWN)
{
Bitboard attacks = PseudoAttacks[type_of(piece)][from];
Bitboard attacks = Attacks::PseudoAttacks[type_of(piece)][from];
cumulativePieceOffset += constexpr_popcount(attacks);
}
else if (from >= SQ_A2 && from <= SQ_H7)
{
Bitboard attacks = (pieceIdx < 8) ? pawn_attacks_bb<WHITE>(square_bb(from))
: pawn_attacks_bb<BLACK>(square_bb(from));
Bitboard attacks = (pieceIdx < 8) ? Attacks::PseudoAttacks[WHITE][from]
: Attacks::PseudoAttacks[BLACK][from];
cumulativePieceOffset += constexpr_popcount(attacks);
}
}
@@ -153,7 +154,7 @@ constexpr auto helper_offsets = init_threat_offsets().first;
constexpr auto offsets = init_threat_offsets().second;
constexpr auto init_index_luts() {
std::array<std::array<std::array<uint32_t, 2>, PIECE_NB>, PIECE_NB> indices{};
std::array<std::array<std::array<u32, 2>, PIECE_NB>, PIECE_NB> indices{};
for (Piece attacker : AllPieces)
{
@@ -190,13 +191,13 @@ constexpr auto index_lut2 = index_lut2_array();
// Index of a feature for a given king position and another piece on some square
inline sf_always_inline IndexType FullThreats::make_index(
Color perspective, Piece attacker, Square from, Square to, Piece attacked, Square ksq) {
const std::int8_t orientation = OrientTBL[ksq] ^ (56 * perspective);
unsigned from_oriented = uint8_t(from) ^ orientation;
unsigned to_oriented = uint8_t(to) ^ orientation;
const i8 orientation = OrientTBL[ksq] ^ (56 * perspective);
unsigned from_oriented = u8(from) ^ orientation;
unsigned to_oriented = u8(to) ^ orientation;
std::int8_t swap = 8 * perspective;
unsigned attacker_oriented = attacker ^ swap;
unsigned attacked_oriented = attacked ^ swap;
i8 swap = 8 * perspective;
unsigned attacker_oriented = attacker ^ swap;
unsigned attacked_oriented = attacked ^ swap;
return index_lut1[attacker_oriented][attacked_oriented][from_oriented < to_oriented]
+ offsets[attacker_oriented][from_oriented]
@@ -206,65 +207,57 @@ inline sf_always_inline IndexType FullThreats::make_index(
// Get a list of indices for active features in ascending order
void FullThreats::append_active_indices(Color perspective, const Position& pos, IndexList& active) {
Square ksq = pos.square<KING>(perspective);
Bitboard occupied = pos.pieces();
const Square ksq = pos.square<KING>(perspective);
const Bitboard occupied = pos.pieces();
const Bitboard pawnTargets = pos.pieces(KNIGHT, ROOK);
const Bitboard minorSliderTargets = pos.pieces(PAWN, KNIGHT, BISHOP, ROOK);
const Bitboard queenTargets = pos.pieces(PAWN, KNIGHT, BISHOP, ROOK, QUEEN);
for (Color color : {WHITE, BLACK})
{
for (PieceType pt = PAWN; pt <= KING; ++pt)
const Color c = Color(perspective ^ color);
{
Color c = Color(perspective ^ color);
Piece attacker = make_piece(c, pt);
Bitboard bb = pos.pieces(c, pt);
const Piece attacker = make_piece(c, PAWN);
const Bitboard cPawns = pos.pieces(c, PAWN);
auto process_pawn_attacks = [&](Bitboard attacks, Direction attkDir) {
while (attacks)
{
Square to = pop_lsb(attacks);
Square from = to - attkDir;
Piece attacked = pos.piece_on(to);
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
active.push_back_if_lt(index, Dimensions);
}
};
if (pt == PAWN)
if (c == WHITE)
{
auto right = (c == WHITE) ? NORTH_EAST : SOUTH_WEST;
auto left = (c == WHITE) ? NORTH_WEST : SOUTH_EAST;
auto attacks_left =
((c == WHITE) ? shift<NORTH_EAST>(bb) : shift<SOUTH_WEST>(bb)) & occupied;
auto attacks_right =
((c == WHITE) ? shift<NORTH_WEST>(bb) : shift<SOUTH_EAST>(bb)) & occupied;
while (attacks_left)
{
Square to = pop_lsb(attacks_left);
Square from = to - right;
Piece attacked = pos.piece_on(to);
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
while (attacks_right)
{
Square to = pop_lsb(attacks_right);
Square from = to - left;
Piece attacked = pos.piece_on(to);
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
process_pawn_attacks(shift<NORTH_EAST>(cPawns) & pawnTargets, NORTH_EAST);
process_pawn_attacks(shift<NORTH_WEST>(cPawns) & pawnTargets, NORTH_WEST);
}
else
{
while (bb)
process_pawn_attacks(shift<SOUTH_WEST>(cPawns) & pawnTargets, SOUTH_WEST);
process_pawn_attacks(shift<SOUTH_EAST>(cPawns) & pawnTargets, SOUTH_EAST);
}
}
for (PieceType pt = KNIGHT; pt < KING; ++pt)
{
Piece attacker = make_piece(c, pt);
Bitboard bb = pos.pieces(c, pt);
Bitboard targets = pt == KNIGHT || pt == QUEEN ? queenTargets : minorSliderTargets;
while (bb)
{
Square from = pop_lsb(bb);
Bitboard attacks = Attacks::attacks_bb(pt, from, occupied) & targets;
while (attacks)
{
Square from = pop_lsb(bb);
Bitboard attacks = (attacks_bb(pt, from, occupied)) & occupied;
while (attacks)
{
Square to = pop_lsb(attacks);
Piece attacked = pos.piece_on(to);
IndexType index =
make_index(perspective, attacker, from, to, attacked, ksq);
if (index < Dimensions)
active.push_back(index);
}
Square to = pop_lsb(attacks);
Piece attacked = pos.piece_on(to);
IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
active.push_back_if_lt(index, Dimensions);
}
}
}
@@ -273,13 +266,13 @@ void FullThreats::append_active_indices(Color perspective, const Position& pos,
// Get a list of indices for recently changed features
void FullThreats::append_changed_indices(Color perspective,
Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fusedData,
bool first) {
void FullThreats::append_changed_indices(Color perspective,
Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
const ThreatWeightType* prefetchBase,
IndexType prefetchStride) {
for (const auto& dirty : diff.list)
{
@@ -289,47 +282,14 @@ void FullThreats::append_changed_indices(Color perspective,
auto to = dirty.threatened_sq();
auto add = dirty.add();
if (fusedData)
{
if (from == fusedData->dp2removed)
{
if (add)
{
if (first)
{
fusedData->dp2removedOriginBoard |= to;
continue;
}
}
else if (fusedData->dp2removedOriginBoard & to)
continue;
}
if (to != SQ_NONE && to == fusedData->dp2removed)
{
if (add)
{
if (first)
{
fusedData->dp2removedTargetBoard |= from;
continue;
}
}
else if (fusedData->dp2removedTargetBoard & from)
continue;
}
}
auto& insert = add ? added : removed;
const IndexType index = make_index(perspective, attacker, from, to, attacked, ksq);
if (index < Dimensions)
insert.push_back(index);
if (prefetchBase)
prefetch<PrefetchRw::READ, PrefetchLoc::LOW>(reinterpret_cast<const void*>(
reinterpret_cast<uintptr_t>(prefetchBase) + index * prefetchStride));
insert.push_back_if_lt(index, Dimensions);
}
}
bool FullThreats::requires_refresh(const DiffType& diff, Color perspective) {
return perspective == diff.us && (int8_t(diff.ksq) & 0b100) != (int8_t(diff.prevKsq) & 0b100);
}
} // namespace Stockfish::Eval::NNUE::Features
+23 -37
View File
@@ -13,12 +13,11 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//Definition of input features Simplified_Threats of NNUE evaluation function
//Definition of input features Full_Threats of NNUE evaluation function
#ifndef NNUE_FEATURES_FULL_THREATS_INCLUDED
#define NNUE_FEATURES_FULL_THREATS_INCLUDED
#include <cstdint>
#include "../../misc.h"
#include "../../types.h"
@@ -30,23 +29,22 @@ class Position;
namespace Stockfish::Eval::NNUE::Features {
static constexpr int numValidTargets[PIECE_NB] = {0, 6, 12, 10, 10, 12, 8, 0,
0, 6, 12, 10, 10, 12, 8, 0};
// Pawn diagonal threats only target knights and rooks (pawn-pawn relationships
// are handled by the PP_3Wide feature set), so pawns have 4 valid targets.
static constexpr int numValidTargets[PIECE_NB] = {0, 4, 10, 8, 8, 10, 0, 0,
0, 4, 10, 8, 8, 10, 0, 0};
class FullThreats {
public:
// Feature name
static constexpr const char* Name = "Full_Threats(Friend)";
// Hash value embedded in the evaluation file
static constexpr std::uint32_t HashValue = 0x8f234cb8u;
static constexpr u32 HashValue = 0x2e6b9d04u;
// Number of feature dimensions
static constexpr IndexType Dimensions = 79856;
static constexpr IndexType Dimensions = 59808;
// clang-format off
// Orient a square according to perspective (rotates by 180 for black)
static constexpr std::int8_t OrientTBL[SQUARE_NB] = {
static constexpr i8 OrientTBL[SQUARE_NB] = {
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
SQ_A1, SQ_A1, SQ_A1, SQ_A1, SQ_H1, SQ_H1, SQ_H1, SQ_H1,
@@ -58,26 +56,18 @@ class FullThreats {
};
static constexpr int map[PIECE_TYPE_NB-2][PIECE_TYPE_NB-2] = {
{0, 1, -1, 2, -1, -1},
{0, 1, 2, 3, 4, 5},
{0, 1, 2, 3, -1, 4},
{0, 1, 2, 3, -1, 4},
{0, 1, 2, 3, 4, 5},
{0, 1, 2, 3, -1, -1}
{-1, 0, -1, 1, -1, -1},
{ 0, 1, 2, 3, 4, -1},
{ 0, 1, 2, 3, -1, -1},
{ 0, 1, 2, 3, -1, -1},
{ 0, 1, 2, 3, 4, -1},
{-1, -1, -1, -1, -1, -1}
};
// clang-format on
struct FusedUpdateData {
Bitboard dp2removedOriginBoard = 0;
Bitboard dp2removedTargetBoard = 0;
Square dp2removed;
};
// Maximum number of simultaneously active features.
static constexpr IndexType MaxActiveDimensions = 128;
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
using DiffType = DirtyThreats;
using IndexList = ValueList<u16, 256>;
using DiffType = DirtyThreats;
static IndexType
make_index(Color perspective, Piece attkr, Square from, Square to, Piece attkd, Square ksq);
@@ -86,17 +76,13 @@ class FullThreats {
static void append_active_indices(Color perspective, const Position& pos, IndexList& active);
// Get a list of indices for recently changed features
static void append_changed_indices(Color perspective,
Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
FusedUpdateData* fd = nullptr,
bool first = false);
// Returns whether the change stored in this DirtyPiece means
// that a full accumulator refresh is required.
static bool requires_refresh(const DiffType& diff, Color perspective);
static void append_changed_indices(Color perspective,
Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
const ThreatWeightType* prefetchBase = nullptr,
IndexType prefetchStride = 0);
};
} // namespace Stockfish::Eval::NNUE::Features
+56 -14
View File
@@ -20,13 +20,67 @@
#include "half_ka_v2_hm.h"
#include "../../bitboard.h"
#include "../../position.h"
#include "../../types.h"
#include "../nnue_common.h"
#if defined(USE_AVX512ICL)
#include "../../bitboard.h"
#endif
namespace Stockfish::Eval::NNUE::Features {
#if defined(USE_AVX512ICL)
void HalfKAv2_hm::write_indices(const std::array<Piece, SQUARE_NB>& oldPieces,
const std::array<Piece, SQUARE_NB>& newPieces,
Bitboard removedBB,
Bitboard addedBB,
Color perspective,
Square ksq,
IndexList& removed,
IndexList& added) {
auto* write_removed = removed.make_space(popcount(removedBB));
auto* write_added = added.make_space(popcount(addedBB));
const __m512i vecOldPieces = _mm512_loadu_si512(oldPieces.data());
const __m512i vecNewPieces = _mm512_loadu_si512(newPieces.data());
// PieceSquareIndex and KingBuckets are multiples of 64, while s and orient
// use only the low six bits. Therefore no carry crosses bit 6, and
// (s ^ orient) + psi[pc] + bucket == s ^ (psi[pc] + bucket + orient),
// allowing the orientation to be folded into the per-piece lookup offset.
const u16 flip = 56 * perspective;
const u16 orient = u16(OrientTBL[ksq]) ^ flip;
const __m512i psi =
_mm512_castsi256_si512(_mm256_loadu_si256((const __m256i*) PieceSquareIndex[perspective]));
const __m512i psi_plus_offset =
_mm512_add_epi16(psi, _mm512_set1_epi16(u16(KingBuckets[int(ksq) ^ flip] + orient)));
__m512i removed_squares = _mm512_maskz_compress_epi8(removedBB, AllSquares);
__m512i added_squares = _mm512_maskz_compress_epi8(addedBB, AllSquares);
__m512i removed_pieces = _mm512_maskz_compress_epi8(removedBB, vecOldPieces);
__m512i added_pieces = _mm512_maskz_compress_epi8(addedBB, vecNewPieces);
removed_squares = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(removed_squares));
added_squares = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(added_squares));
removed_pieces = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(removed_pieces));
added_pieces = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(added_pieces));
const __m512i removed_indices =
_mm512_xor_si512(removed_squares, _mm512_permutexvar_epi16(removed_pieces, psi_plus_offset));
const __m512i added_indices =
_mm512_xor_si512(added_squares, _mm512_permutexvar_epi16(added_pieces, psi_plus_offset));
_mm512_storeu_si512(write_removed,
_mm512_cvtepu16_epi32(_mm512_castsi512_si256(removed_indices)));
_mm512_storeu_si512(write_removed + 16,
_mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(removed_indices, 1)));
_mm512_storeu_si512(write_added, _mm512_cvtepu16_epi32(_mm512_castsi512_si256(added_indices)));
_mm512_storeu_si512(write_added + 16,
_mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(added_indices, 1)));
}
#endif
// Index of a feature for a given king position and another piece on some square
IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square ksq) {
@@ -35,18 +89,6 @@ IndexType HalfKAv2_hm::make_index(Color perspective, Square s, Piece pc, Square
+ KingBuckets[int(ksq) ^ flip];
}
// Get a list of indices for active features
void HalfKAv2_hm::append_active_indices(Color perspective, const Position& pos, IndexList& active) {
Square ksq = pos.square<KING>(perspective);
Bitboard bb = pos.pieces();
while (bb)
{
Square s = pop_lsb(bb);
active.push_back(make_index(perspective, s, pos.piece_on(s), ksq));
}
}
// Get a list of indices for recently changed features
void HalfKAv2_hm::append_changed_indices(
+15 -16
View File
@@ -16,21 +16,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
//Definition of input features HalfKP of NNUE evaluation function
//Definition of input features HalfKAv2_hm of NNUE evaluation function
#ifndef NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
#define NNUE_FEATURES_HALF_KA_V2_HM_H_INCLUDED
#include <cstdint>
#include "../../misc.h"
#include "../../types.h"
#include "../nnue_common.h"
namespace Stockfish {
class Position;
}
namespace Stockfish::Eval::NNUE::Features {
// Feature HalfKAv2_hm: Combination of the position of own king and the
@@ -54,7 +48,7 @@ class HalfKAv2_hm {
PS_NB = 11 * SQUARE_NB
};
static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
alignas(64) static constexpr u16 PieceSquareIndex[COLOR_NB][PIECE_NB] = {
// Convention: W - us, B - them
// Viewed from other side, W and B are reversed
{PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,
@@ -63,11 +57,8 @@ class HalfKAv2_hm {
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}};
public:
// Feature name
static constexpr const char* Name = "HalfKAv2_hm(Friend)";
// Hash value embedded in the evaluation file
static constexpr std::uint32_t HashValue = 0x7f234cb8u;
static constexpr u32 HashValue = 0x7f234cb8u;
// Number of feature dimensions
static constexpr IndexType Dimensions =
@@ -106,14 +97,22 @@ class HalfKAv2_hm {
using IndexList = ValueList<IndexType, MaxActiveDimensions>;
using DiffType = DirtyPiece;
#if defined(USE_AVX512ICL)
// Compute all changed feature indices and write them to the given lists
static void write_indices(const std::array<Piece, SQUARE_NB>& oldPieces,
const std::array<Piece, SQUARE_NB>& newPieces,
Bitboard removedBB,
Bitboard addedBB,
Color perspective,
Square ksq,
IndexList& removed,
IndexList& added);
#endif
// Index of a feature for a given king position and another piece on some square
static IndexType make_index(Color perspective, Square s, Piece pc, Square ksq);
// Get a list of indices for active features
static void append_active_indices(Color perspective, const Position& pos, IndexList& active);
// Get a list of indices for recently changed features
static void append_changed_indices(
Color perspective, Square ksq, const DiffType& diff, IndexList& removed, IndexList& added);
+171
View File
@@ -0,0 +1,171 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "pp_3wide.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include "full_threats.h"
#include "../../bitboard.h"
#include "../../misc.h"
#include "../../position.h"
#include "../../types.h"
#include "../nnue_common.h"
namespace Stockfish::Eval::NNUE::Features {
constexpr IndexType make_pawn_id(Color color, Square square) {
assert(square >= SQ_A2 && square <= SQ_H7);
return 48 * int(color) + square - SQ_A2;
}
#ifdef USE_AVX512ICL
static inline __m256i pp_idx_epi16(__m256i a, __m256i b) {
const __m256i hi = _mm256_max_epu16(a, b);
const __m256i lo = _mm256_min_epu16(a, b);
const __m256i prod = _mm256_mullo_epi16(hi, _mm256_sub_epi16(hi, _mm256_set1_epi16(1)));
return _mm256_add_epi16(_mm256_add_epi16(_mm256_srli_epi16(prod, 1), lo),
_mm256_set1_epi16(i16(PP_3Wide::IndexBase)));
}
#endif
inline sf_always_inline IndexType PP_3Wide::make_index(
Color perspective, Color color, Square from, Square to, Color pairedColor, Square ksq) {
const i8 orientation = FullThreats::OrientTBL[ksq] ^ (56 * perspective);
unsigned from_oriented = u8(from) ^ orientation;
unsigned to_oriented = u8(to) ^ orientation;
Color color_oriented = Color(color ^ perspective);
Color pairedColor_oriented = Color(pairedColor ^ perspective);
assert(from_oriented >= SQ_A2 && from_oriented <= SQ_H7);
assert(to_oriented >= SQ_A2 && to_oriented <= SQ_H7);
const IndexType idA = make_pawn_id(color_oriented, Square(from_oriented));
const IndexType idB = make_pawn_id(pairedColor_oriented, Square(to_oriented));
const IndexType hi = std::max(idA, idB);
const IndexType lo = std::min(idA, idB);
return hi * (hi - 1) / 2 + lo + IndexBase;
}
void PP_3Wide::append_active_indices(Color perspective, const Position& pos, IndexList& active) {
const Square ksq = pos.square<KING>(perspective);
const Bitboard white = pos.pieces(WHITE, PAWN);
const Bitboard black = pos.pieces(BLACK, PAWN);
Bitboard bb = white;
while (bb)
{
Square from = pop_lsb(bb);
const Bitboard band = pawn_pair_bb(from);
for (Bitboard ww = band & bb; ww;)
active.push_back(make_index(perspective, WHITE, from, pop_lsb(ww), WHITE, ksq));
for (Bitboard wb = band & black; wb;)
active.push_back(make_index(perspective, WHITE, from, pop_lsb(wb), BLACK, ksq));
}
bb = black;
while (bb)
{
Square from = pop_lsb(bb);
const Bitboard band = pawn_pair_bb(from);
for (Bitboard bbk = band & bb; bbk;)
active.push_back(make_index(perspective, BLACK, from, pop_lsb(bbk), BLACK, ksq));
}
}
void PP_3Wide::append_changed_indices(Color perspective,
Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
[[maybe_unused]] const ThreatWeightType* prefetchBase,
[[maybe_unused]] IndexType prefetchStride) {
const Bitboard whiteBefore = diff.before[WHITE];
const Bitboard blackBefore = diff.before[BLACK];
const Bitboard whiteAfter = diff.after[WHITE];
const Bitboard blackAfter = diff.after[BLACK];
if (whiteBefore == whiteAfter && blackBefore == blackAfter)
return;
#ifdef USE_AVX512ICL
const u8 orientation = u8(FullThreats::OrientTBL[ksq]) ^ u8(56 * perspective);
const __m512i iota = AllSquares;
const __m512i adjusted =
_mm512_sub_epi8(_mm512_xor_si512(iota, _mm512_set1_epi8(orientation)), _mm512_set1_epi8(8));
auto generate = [&](Bitboard updatedW, Bitboard updatedB, Bitboard pawnsW, Bitboard pawnsB,
IndexList& out) {
const Bitboard friendly = perspective == WHITE ? pawnsW : pawnsB;
const Bitboard enemy = perspective == WHITE ? pawnsB : pawnsW;
const __m512i ids = _mm512_mask_blend_epi8(
friendly, _mm512_add_epi8(adjusted, _mm512_set1_epi8(48)), adjusted);
const Bitboard unchanged = (pawnsW | pawnsB) & ~(updatedW | updatedB);
for (Bitboard u = updatedW | updatedB; u;)
{
const Square a = pop_lsb(u);
const Bitboard partners = pawn_pair_bb(a) & (unchanged | u);
const int n = popcount(partners);
if (!n)
continue;
const u16 colorOff = (enemy & a) ? 48 : 0;
const u16 aId = u16(((u8(a) ^ orientation) - 8) + colorOff);
const __m256i pids = _mm256_cvtepu8_epi16(
_mm512_castsi512_si128(_mm512_maskz_compress_epi8(partners, ids)));
const __m256i feats = pp_idx_epi16(_mm256_set1_epi16(aId), pids);
u16* w = out.make_space(n);
_mm256_storeu_epi16(w, feats);
}
};
#else
auto generate = [&](Bitboard updatedW, Bitboard updatedB, Bitboard pawnsW, Bitboard pawnsB,
IndexList& out) {
auto push = [&](IndexType index) {
if (prefetchBase)
prefetch<PrefetchRw::READ, PrefetchLoc::LOW>(reinterpret_cast<const void*>(
reinterpret_cast<uintptr_t>(prefetchBase) + index * prefetchStride));
out.push_back(index);
};
const Bitboard unchanged = (pawnsW | pawnsB) & ~(updatedW | updatedB);
for (Bitboard u = updatedW | updatedB; u;)
{
const Square a = pop_lsb(u);
const Bitboard mask = pawn_pair_bb(a) & (unchanged | u);
const Color aCol = (pawnsB & a) ? BLACK : WHITE;
for (Bitboard pb = pawnsB & mask; pb;)
push(make_index(perspective, aCol, a, pop_lsb(pb), BLACK, ksq));
for (Bitboard pw = pawnsW & mask; pw;)
push(make_index(perspective, aCol, a, pop_lsb(pw), WHITE, ksq));
}
};
#endif
generate(whiteAfter & ~whiteBefore, blackAfter & ~blackBefore, whiteAfter, blackAfter, added);
generate(whiteBefore & ~whiteAfter, blackBefore & ~blackAfter, whiteBefore, blackBefore,
removed);
}
} // namespace Stockfish::Eval::NNUE::Features
+61
View File
@@ -0,0 +1,61 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NNUE_FEATURES_PP_3WIDE_INCLUDED
#define NNUE_FEATURES_PP_3WIDE_INCLUDED
#include "../../misc.h"
#include "../../types.h"
#include "../nnue_common.h"
namespace Stockfish {
class Position;
}
namespace Stockfish::Eval::NNUE::Features {
class PP_3Wide {
public:
static constexpr u32 HashValue = 0x86f2b1ddu;
static constexpr IndexType PawnIds = COLOR_NB * 48;
static constexpr IndexType Dimensions = PawnIds * (PawnIds - 1) / 2;
// Pawn pair feature indices are concatenated to threats, so this must equal ThreatFeatureSet::Dimensions;
// see nnue_feature_transformer.h
static constexpr IndexType IndexBase = 59808;
using IndexList = ValueList<u16, 256>;
using DiffType = DirtyPawnPairs;
static IndexType make_index(
Color perspective, Color color, Square from, Square to, Color pairedColor, Square ksq);
static void append_active_indices(Color perspective, const Position& pos, IndexList& active);
static void append_changed_indices(Color perspective,
Square ksq,
const DiffType& diff,
IndexList& removed,
IndexList& added,
const ThreatWeightType* prefetchBase = nullptr,
IndexType prefetchStride = 0);
};
} // namespace Stockfish::Eval::NNUE::Features
#endif // #ifndef NNUE_FEATURES_PP_3WIDE_INCLUDED
+112 -26
View File
@@ -24,6 +24,7 @@
#include <cstdint>
#include <iostream>
#include "../../memory.h"
#include "../nnue_common.h"
#include "../simd.h"
@@ -39,7 +40,7 @@
namespace Stockfish::Eval::NNUE::Layers {
#if defined(USE_SSSE3) || defined(USE_NEON_DOTPROD)
#if defined(USE_SSSE3) || defined(USE_NEON_DOTPROD) || defined(USE_LSX) || defined(USE_LASX)
#define ENABLE_SEQ_OPT
#endif
@@ -48,10 +49,8 @@ namespace Stockfish::Eval::NNUE::Layers {
#ifndef ENABLE_SEQ_OPT
template<IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
static void affine_transform_non_ssse3(std::int32_t* output,
const std::int8_t* weights,
const std::int32_t* biases,
const std::uint8_t* input) {
static void
affine_transform_non_ssse3(i32* output, const i8* weights, const i32* biases, const u8* input) {
#if defined(USE_SSE2) || defined(USE_NEON)
#if defined(USE_SSE2)
// At least a multiple of 16, with SSE2.
@@ -95,7 +94,7 @@ static void affine_transform_non_ssse3(std::int32_t* output,
#elif defined(USE_NEON)
int32x4_t sum = {biases[i]};
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
const auto row = reinterpret_cast<const SIMD::vec_i8x8_t*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j)
{
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
@@ -106,15 +105,35 @@ static void affine_transform_non_ssse3(std::int32_t* output,
#endif
}
#elif defined(USE_RVV)
for (IndexType i = 0; i < OutputDimensions; ++i)
{
const i8* row = &weights[i * PaddedInputDimensions];
vint32m1_t vsum = __riscv_vmv_v_x_i32m1(0, __riscv_vsetvlmax_e32m1());
for (usize j = 0; j < InputDimensions;)
{
usize vl = __riscv_vsetvl_e8m4(InputDimensions - j);
vint8m4_t w = __riscv_vle8_v_i8m4(&row[j], vl);
vuint8m4_t x = __riscv_vle8_v_u8m4(&input[j], vl);
vint16m8_t prod = __riscv_vwmulsu_vv_i16m8(w, x, vl);
vsum = __riscv_vwredsum_vs_i16m8_i32m1(prod, vsum, vl);
j += vl;
}
output[i] = biases[i] + __riscv_vmv_x_s_i32m1_i32(vsum);
}
#else
std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions);
std::memcpy(output, biases, sizeof(i32) * OutputDimensions);
// Traverse weights in transpose order to take advantage of input sparsity
for (IndexType i = 0; i < InputDimensions; ++i)
if (input[i])
{
const std::int8_t* w = &weights[i];
const int in = input[i];
const i8* w = &weights[i];
const int in = input[i];
for (IndexType j = 0; j < OutputDimensions; ++j)
output[j] += w[j * PaddedInputDimensions] * in;
}
@@ -123,12 +142,12 @@ static void affine_transform_non_ssse3(std::int32_t* output,
#endif // !ENABLE_SEQ_OPT
template<IndexType InDims, IndexType OutDims>
template<IndexType InDims, IndexType OutDims, bool ScrambledInput = false>
class AffineTransform {
public:
// Input/output type
using InputType = std::uint8_t;
using OutputType = std::int32_t;
using InputType = u8;
using OutputType = i32;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = InDims;
@@ -142,8 +161,8 @@ class AffineTransform {
using OutputBuffer = OutputType[PaddedOutputDimensions];
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
std::uint32_t hashValue = 0xCC03DAE4u;
static constexpr u32 get_hash_value(u32 prevHash) {
u32 hashValue = 0xCC03DAE4u;
hashValue += OutputDimensions;
hashValue ^= prevHash >> 1;
hashValue ^= prevHash << 31;
@@ -151,8 +170,21 @@ class AffineTransform {
}
static constexpr IndexType get_weight_index_scrambled(IndexType i) {
return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4
+ i / PaddedInputDimensions * 4 + i % 4;
IndexType inputIndex = i % PaddedInputDimensions;
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
if constexpr (ScrambledInput)
{
// AVX2 packs operate independently on 128-bit lanes. Keep their interleaved output
// order and rearrange the following layer's weights instead of issuing VPERMD.
const IndexType block = inputIndex / 32;
const IndexType chunk = (inputIndex % 32) / 4;
inputIndex = block * 32 + ((chunk % 2) * 4 + chunk / 2) * 4 + inputIndex % 4;
}
#endif
return inputIndex / 4 * OutputDimensions * 4 + i / PaddedInputDimensions * 4
+ inputIndex % 4;
}
static constexpr IndexType get_weight_index(IndexType i) {
@@ -182,8 +214,8 @@ class AffineTransform {
return !stream.fail();
}
std::size_t get_content_hash() const {
std::size_t h = 0;
usize get_content_hash() const {
usize h = 0;
hash_combine(h, get_raw_data_hash(biases));
hash_combine(h, get_raw_data_hash(weights));
hash_combine(h, get_hash_value(0));
@@ -200,10 +232,12 @@ class AffineTransform {
#if defined(USE_AVX512)
using vec_t = __m512i;
#define vec_set_32 _mm512_set1_epi32
#define vec_add_32 _mm512_add_epi32
#define vec_add_dpbusd_32 SIMD::m512_add_dpbusd_epi32
#elif defined(USE_AVX2)
using vec_t = __m256i;
#define vec_set_32 _mm256_set1_epi32
#define vec_add_32 _mm256_add_epi32
#define vec_add_dpbusd_32 SIMD::m256_add_dpbusd_epi32
#elif defined(USE_SSSE3)
using vec_t = __m128i;
@@ -215,6 +249,16 @@ class AffineTransform {
#define vec_add_dpbusd_32(acc, a, b) \
SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \
vreinterpretq_s8_s32(b))
#elif defined(USE_LASX)
using vec_t = __m256i;
#define vec_set_32 __lasx_xvreplgr2vr_w
#define vec_add_32 __lasx_xvadd_w
#define vec_add_dpbusd_32 SIMD::lasx_m256_add_dpbusd_epi32
#elif defined(USE_LSX)
using vec_t = __m128i;
#define vec_set_32 __lsx_vreplgr2vr_w
#define vec_add_32 __lsx_vadd_w
#define vec_add_dpbusd_32 SIMD::lsx_m128_add_dpbusd_epi32
#endif
static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);
@@ -222,26 +266,58 @@ class AffineTransform {
static_assert(OutputDimensions % OutputSimdWidth == 0);
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / 4;
constexpr IndexType NumRegs = OutputDimensions / OutputSimdWidth;
constexpr IndexType NumAccums = OutputDimensions / OutputSimdWidth;
#if defined(USE_VNNI) || defined(USE_NEON_DOTPROD)
constexpr IndexType NumRegs = 2 * NumAccums;
#else
constexpr IndexType NumRegs = NumAccums;
#endif
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
const vec_t* biasvec = reinterpret_cast<const vec_t*>(biases);
vec_t acc[NumRegs];
for (IndexType k = 0; k < NumRegs; ++k)
for (IndexType k = 0; k < NumAccums; ++k)
acc[k] = biasvec[k];
for (IndexType k = NumAccums; k < NumRegs; ++k)
acc[k] = vec_set_32(0);
for (IndexType i = 0; i < NumChunks; ++i)
IndexType i = 0;
#if defined(USE_VNNI) || defined(USE_NEON_DOTPROD)
for (; i < NumChunks; i += 2)
{
const vec_t in0 = vec_set_32(input32[i]);
const vec_t in0 = vec_set_32(load_as<i32>(input + i * sizeof(i32)));
const vec_t in1 = vec_set_32(load_as<i32>(input + (i + 1) * sizeof(i32)));
const auto col0 =
reinterpret_cast<const vec_t*>(&weights[i * OutputDimensions * 4]);
const auto col1 =
reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
for (IndexType k = 0; k < NumAccums; ++k)
{
vec_add_dpbusd_32(acc[k], in0, col0[k]);
vec_add_dpbusd_32(acc[k + NumAccums], in1, col1[k]);
}
}
for (IndexType k = 0; k < NumAccums; ++k)
#if defined(USE_NEON_DOTPROD)
acc[k] = vaddq_s32(acc[k], acc[k + NumAccums]);
#else
acc[k] = vec_add_32(acc[k], acc[k + NumAccums]);
#endif
#endif
for (; i < NumChunks; ++i)
{
const vec_t in0 = vec_set_32(load_as<i32>(input + i * sizeof(i32)));
const auto col0 =
reinterpret_cast<const vec_t*>(&weights[i * OutputDimensions * 4]);
for (IndexType k = 0; k < NumRegs; ++k)
for (IndexType k = 0; k < NumAccums; ++k)
vec_add_dpbusd_32(acc[k], in0, col0[k]);
}
vec_t* outptr = reinterpret_cast<vec_t*>(output);
for (IndexType k = 0; k < NumRegs; ++k)
for (IndexType k = 0; k < NumAccums; ++k)
outptr[k] = acc[k];
#undef vec_set_32
@@ -268,6 +344,16 @@ class AffineTransform {
SIMD::dotprod_m128_add_dpbusd_epi32(acc, vreinterpretq_s8_s32(a), \
vreinterpretq_s8_s32(b))
#define vec_hadd SIMD::neon_m128_hadd
#elif defined(USE_LASX)
using vec_t = __m256i;
#define vec_setzero() __lasx_xvldi(0)
#define vec_add_dpbusd_32 SIMD::lasx_m256_add_dpbusd_epi32
#define vec_hadd SIMD::lasx_m256_hadd
#elif defined(USE_LSX)
using vec_t = __m128i;
#define vec_setzero() __lsx_vldi(0)
#define vec_add_dpbusd_32 SIMD::lsx_m128_add_dpbusd_epi32
#define vec_hadd SIMD::lsx_m128_hadd
#endif
const auto inputVector = reinterpret_cast<const vec_t*>(input);
@@ -300,7 +386,7 @@ class AffineTransform {
private:
using BiasType = OutputType;
using WeightType = std::int8_t;
using WeightType = i8;
alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
+234 -173
View File
@@ -24,11 +24,14 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <iostream>
#include "../../bitboard.h"
#include "../../memory.h"
#include "../simd.h"
#include "../nnue_common.h"
#include "../nnz_helper.h"
/*
This file contains the definition for a fully connected layer (aka affine transform) with block sparse input.
@@ -36,147 +39,13 @@
namespace Stockfish::Eval::NNUE::Layers {
#if (USE_SSSE3 | (USE_NEON >= 8))
static constexpr int lsb_index64[64] = {
0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42,
21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43,
31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63};
constexpr int constexpr_lsb(uint64_t bb) {
assert(bb != 0);
constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL;
return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58];
}
alignas(CacheLineSize) static constexpr struct OffsetIndices {
std::uint16_t offset_indices[256][8];
constexpr OffsetIndices() :
offset_indices() {
for (int i = 0; i < 256; ++i)
{
std::uint64_t j = i, k = 0;
while (j)
{
offset_indices[i][k++] = constexpr_lsb(j);
j &= j - 1;
}
while (k < 8)
offset_indices[i][k++] = 0;
}
}
} Lookup;
#if defined(__GNUC__) || defined(__clang__)
#define RESTRICT __restrict__
#elif defined(_MSC_VER)
#define RESTRICT __restrict
#else
#define RESTRICT
#endif
// Find indices of nonzero numbers in an int32_t array
template<const IndexType InputDimensions>
void find_nnz(const std::int32_t* RESTRICT input,
std::uint16_t* RESTRICT out,
IndexType& count_out) {
#if defined(USE_AVX512ICL)
constexpr IndexType SimdWidthIn = 16; // 512 bits / 32 bits
constexpr IndexType SimdWidthOut = 32; // 512 bits / 16 bits
constexpr IndexType NumChunks = InputDimensions / SimdWidthOut;
const __m512i increment = _mm512_set1_epi16(SimdWidthOut);
__m512i base = _mm512_set_epi16( // Same permute order as _mm512_packus_epi32()
31, 30, 29, 28, 15, 14, 13, 12, 27, 26, 25, 24, 11, 10, 9, 8, 23, 22, 21, 20, 7, 6, 5, 4, 19,
18, 17, 16, 3, 2, 1, 0);
IndexType count = 0;
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m512i inputV0 = _mm512_load_si512(input + i * 2 * SimdWidthIn);
const __m512i inputV1 = _mm512_load_si512(input + i * 2 * SimdWidthIn + SimdWidthIn);
// Get a bitmask and gather non zero indices
const __m512i inputV01 = _mm512_packus_epi32(inputV0, inputV1);
const __mmask32 nnzMask = _mm512_test_epi16_mask(inputV01, inputV01);
// Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4
__m512i nnz = _mm512_maskz_compress_epi16(nnzMask, base);
_mm512_storeu_si512(out + count, nnz);
count += popcount(nnzMask);
base = _mm512_add_epi16(base, increment);
}
count_out = count;
#elif defined(USE_AVX512)
constexpr IndexType SimdWidth = 16; // 512 bits / 32 bits
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m512i increment = _mm512_set1_epi32(SimdWidth);
__m512i base = _mm512_set_epi32(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
IndexType count = 0;
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m512i inputV = _mm512_load_si512(input + i * SimdWidth);
// Get a bitmask and gather non zero indices
const __mmask16 nnzMask = _mm512_test_epi32_mask(inputV, inputV);
const __m512i nnzV = _mm512_maskz_compress_epi32(nnzMask, base);
_mm512_mask_cvtepi32_storeu_epi16(out + count, 0xFFFF, nnzV);
count += popcount(nnzMask);
base = _mm512_add_epi32(base, increment);
}
count_out = count;
#else
using namespace SIMD;
constexpr IndexType InputSimdWidth = sizeof(vec_uint_t) / sizeof(std::int32_t);
// Outputs are processed 8 elements at a time, even if the SIMD width is narrower
constexpr IndexType ChunkSize = 8;
constexpr IndexType NumChunks = InputDimensions / ChunkSize;
constexpr IndexType InputsPerChunk = ChunkSize / InputSimdWidth;
static_assert(InputsPerChunk > 0 && "SIMD width too wide");
const auto inputVector = reinterpret_cast<const vec_uint_t*>(input);
IndexType count = 0;
vec128_t base = vec128_zero;
const vec128_t increment = vec128_set_16(8);
for (IndexType i = 0; i < NumChunks; ++i)
{
// bitmask of nonzero values in this chunk
unsigned nnz = 0;
for (IndexType j = 0; j < InputsPerChunk; ++j)
{
const vec_uint_t inputChunk = inputVector[i * InputsPerChunk + j];
nnz |= unsigned(vec_nnz(inputChunk)) << (j * InputSimdWidth);
}
const vec128_t offsets =
vec128_load(reinterpret_cast<const vec128_t*>(&Lookup.offset_indices[nnz]));
vec128_storeu(reinterpret_cast<vec128_t*>(out + count), vec128_add(base, offsets));
count += popcount(nnz);
base = vec128_add(base, increment);
}
count_out = count;
#endif
}
#endif
// Sparse input implementation
template<IndexType InDims, IndexType OutDims>
class AffineTransformSparseInput {
public:
// Input/output type
using InputType = std::uint8_t;
using OutputType = std::int32_t;
using InputType = u8;
using OutputType = i32;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = InDims;
@@ -190,7 +59,8 @@ class AffineTransformSparseInput {
static constexpr IndexType PaddedOutputDimensions =
ceil_to_multiple<IndexType>(OutputDimensions, MaxSimdWidth);
#if (USE_SSSE3 | (USE_NEON >= 8))
#if (defined(USE_SSSE3) || defined(USE_LSX) || defined(USE_LASX) || (USE_NEON >= 8) \
|| defined(USE_RVV))
static constexpr IndexType ChunkSize = 4;
#else
static constexpr IndexType ChunkSize = 1;
@@ -199,8 +69,8 @@ class AffineTransformSparseInput {
using OutputBuffer = OutputType[PaddedOutputDimensions];
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
std::uint32_t hashValue = 0xCC03DAE4u;
static constexpr u32 get_hash_value(u32 prevHash) {
u32 hashValue = 0xCC03DAE4u;
hashValue += OutputDimensions;
hashValue ^= prevHash >> 1;
hashValue ^= prevHash << 31;
@@ -213,7 +83,8 @@ class AffineTransformSparseInput {
}
static constexpr IndexType get_weight_index(IndexType i) {
#if (USE_SSSE3 | (USE_NEON >= 8))
#if (defined(USE_SSSE3) || defined(USE_LSX) || defined(USE_LASX) || (USE_NEON >= 8) \
|| defined(USE_RVV))
return get_weight_index_scrambled(i);
#else
return i;
@@ -239,8 +110,8 @@ class AffineTransformSparseInput {
return !stream.fail();
}
std::size_t get_content_hash() const {
std::size_t h = 0;
usize get_content_hash() const {
usize h = 0;
hash_combine(h, get_raw_data_hash(biases));
hash_combine(h, get_raw_data_hash(weights));
hash_combine(h, get_hash_value(0));
@@ -248,9 +119,11 @@ class AffineTransformSparseInput {
}
// Forward propagation
void propagate(const InputType* input, OutputType* output) const {
void propagate(const InputType* input,
OutputType* output,
[[maybe_unused]] const NNZInfo<InDims>& nnzInfo) const {
#if (USE_SSSE3 | (USE_NEON >= 8))
#if (defined(USE_SSSE3) || defined(USE_LSX) || defined(USE_LASX) || (USE_NEON >= 8))
#if defined(USE_AVX512)
using invec_t = __m512i;
using outvec_t = __m512i;
@@ -278,49 +151,64 @@ class AffineTransformSparseInput {
using outvec_t = int32x4_t;
#define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
#define vec_add_dpbusd_32 SIMD::neon_m128_add_dpbusd_epi32
#elif defined(USE_LASX)
using invec_t = __m256i;
using outvec_t = __m256i;
#define vec_add_32 __lasx_xvadd_w
#define vec_set_32 __lasx_xvreplgr2vr_w
#define vec_add_dpbusd_32 SIMD::lasx_m256_add_dpbusd_epi32
#elif defined(USE_LSX)
using invec_t = __m128i;
using outvec_t = __m128i;
#define vec_add_32 __lsx_vadd_w
#define vec_set_32 __lsx_vreplgr2vr_w
#define vec_add_dpbusd_32 SIMD::lsx_m128_add_dpbusd_epi32
#endif
constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType);
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;
constexpr IndexType NumAccums = OutputDimensions / OutputSimdWidth;
constexpr IndexType NumAccums = OutputDimensions / OutputSimdWidth;
// If we're using high-latency dot product instructions, split the accumulators
// to create 3 separate dependency chains and merge at the end
// into separate dependency chains and merge at the end
constexpr IndexType NumRegs =
#if defined(USE_VNNI)
#if (defined(USE_VNNI) && defined(USE_AVX512)) || defined(USE_NEON_DOTPROD)
3 * NumAccums;
#elif defined(USE_AVXVNNI)
2 * NumAccums;
#else
NumAccums;
#endif
std::uint16_t nnz[NumChunks];
IndexType count;
const auto input32 = reinterpret_cast<const std::int32_t*>(input);
// Find indices of nonzero 32-bit blocks
find_nnz<NumChunks>(input32, nnz, count);
const outvec_t* biasvec = reinterpret_cast<const outvec_t*>(biases);
outvec_t acc[NumRegs];
for (IndexType k = 0; k < NumAccums; ++k)
acc[k] = biasvec[k];
const auto* start = nnz;
const auto* end = nnz + count;
#if defined(USE_AVXVNNI)
for (IndexType k = NumAccums; k < NumRegs; ++k)
acc[k] = vec_set_32(0);
#elif defined(USE_NEON_DOTPROD)
for (IndexType k = NumAccums; k < NumRegs; ++k)
acc[k] = vdupq_n_s32(0);
#endif
// convince GCC to not do weird pointer arithmetic in the following loops
const i8* weights_cp = weights;
#if defined(USE_AVX512)
const auto* start = nnzInfo.nnz;
const auto* end = nnzInfo.nnz + nnzInfo.count;
// convince GCC to not do weird pointer arithmetic in the following loop
const std::int8_t* weights_cp = weights;
#if defined(USE_VNNI)
for (IndexType k = NumAccums; k < NumRegs; ++k)
acc[k] = vec_zero();
#if defined(USE_VNNI)
while (start < end - 2)
{
const std::ptrdiff_t i0 = *start++;
const std::ptrdiff_t i1 = *start++;
const std::ptrdiff_t i2 = *start++;
const invec_t in0 = vec_set_32(input32[i0]);
const invec_t in1 = vec_set_32(input32[i1]);
const invec_t in2 = vec_set_32(input32[i2]);
const auto col0 =
const isize i0 = *start++;
const isize i1 = *start++;
const isize i2 = *start++;
const invec_t in0 = vec_set_32(load_as<i32>(input + i0 * sizeof(i32)));
const invec_t in1 = vec_set_32(load_as<i32>(input + i1 * sizeof(i32)));
const invec_t in2 = vec_set_32(load_as<i32>(input + i2 * sizeof(i32)));
const auto col0 =
reinterpret_cast<const invec_t*>(&weights_cp[i0 * OutputDimensions * ChunkSize]);
const auto col1 =
reinterpret_cast<const invec_t*>(&weights_cp[i1 * OutputDimensions * ChunkSize]);
@@ -333,19 +221,147 @@ class AffineTransformSparseInput {
vec_add_dpbusd_32(acc[k + 2 * NumAccums], in2, col2[k]);
}
}
for (IndexType k = 0; k < NumAccums; ++k)
acc[k] = vec_add_32(vec_add_32(acc[k], acc[k + NumAccums]), acc[k + 2 * NumAccums]);
#endif
#endif
while (start < end)
{
const std::ptrdiff_t i = *start++;
const invec_t in = vec_set_32(input32[i]);
const auto col =
const isize i = *start++;
const invec_t in = vec_set_32(load_as<i32>(input + i * sizeof(i32)));
const auto col =
reinterpret_cast<const invec_t*>(&weights_cp[i * OutputDimensions * ChunkSize]);
for (IndexType k = 0; k < NumAccums; ++k)
vec_add_dpbusd_32(acc[k], in, col[k]);
}
#else
static_assert(InputDimensions % 256 == 0);
for (IndexType k = 0; k < InputDimensions / 256; ++k)
{
u64 bits = load_as<u64>(nnzInfo.bitset + k * 8);
isize base = k * 64;
auto* base_addr = input + base * sizeof(i32);
auto* weights_base = &weights_cp[base * OutputDimensions * ChunkSize];
#if defined(USE_NEON_DOTPROD) && defined(__GNUC__) && !defined(__clang__)
// GCC 15 pessimizes the following code on ARM64 by eliding the intermediate
// computation of key pointers (base_addr, weights_base, col, input_addr), leading
// to a lot of redundant indexing arithmetic in the while (bits) loop. The
// optimization barriers force these pointers to be calculated and used.
#if __GNUC__ >= 15
#define FIX_GCC15_MISOPTIMIZATION
#endif
#endif
#ifdef FIX_GCC15_MISOPTIMIZATION
asm("" : "+r"(base_addr), "+r"(weights_base)); // opt barrier
#endif
#if defined(USE_AVXVNNI)
while (bits)
{
const isize i0 = pop_lsb(bits);
const invec_t in0 = vec_set_32(load_as<i32>(base_addr + i0 * sizeof(i32)));
const auto col0 = reinterpret_cast<const invec_t*>(
&weights_base[i0 * OutputDimensions * ChunkSize]);
if (!bits)
{
for (IndexType l = 0; l < NumAccums; ++l)
vec_add_dpbusd_32(acc[l], in0, col0[l]);
break;
}
const isize i1 = pop_lsb(bits);
const invec_t in1 = vec_set_32(load_as<i32>(base_addr + i1 * sizeof(i32)));
const auto col1 = reinterpret_cast<const invec_t*>(
&weights_base[i1 * OutputDimensions * ChunkSize]);
for (IndexType l = 0; l < NumAccums; ++l)
{
vec_add_dpbusd_32(acc[l], in0, col0[l]);
vec_add_dpbusd_32(acc[l + NumAccums], in1, col1[l]);
}
}
#elif defined(USE_NEON_DOTPROD)
while (bits)
{
const isize i0 = pop_lsb(bits);
if (!bits)
{
const invec_t in0 = vec_set_32(load_as<i32>(base_addr + i0 * sizeof(i32)));
const auto col0 = reinterpret_cast<const invec_t*>(
&weights_base[i0 * OutputDimensions * ChunkSize]);
for (IndexType l = 0; l < NumAccums; ++l)
vec_add_dpbusd_32(acc[l], in0, col0[l]);
break;
}
const isize i1 = pop_lsb(bits);
if (!bits)
{
const invec_t in0 = vec_set_32(load_as<i32>(base_addr + i0 * sizeof(i32)));
const invec_t in1 = vec_set_32(load_as<i32>(base_addr + i1 * sizeof(i32)));
const auto col0 = reinterpret_cast<const invec_t*>(
&weights_base[i0 * OutputDimensions * ChunkSize]);
const auto col1 = reinterpret_cast<const invec_t*>(
&weights_base[i1 * OutputDimensions * ChunkSize]);
for (IndexType l = 0; l < NumAccums; ++l)
{
vec_add_dpbusd_32(acc[l], in0, col0[l]);
vec_add_dpbusd_32(acc[l + NumAccums], in1, col1[l]);
}
break;
}
const isize i2 = pop_lsb(bits);
const invec_t in0 = vec_set_32(load_as<i32>(base_addr + i0 * sizeof(i32)));
const invec_t in1 = vec_set_32(load_as<i32>(base_addr + i1 * sizeof(i32)));
const invec_t in2 = vec_set_32(load_as<i32>(base_addr + i2 * sizeof(i32)));
const auto col0 = reinterpret_cast<const invec_t*>(
&weights_base[i0 * OutputDimensions * ChunkSize]);
const auto col1 = reinterpret_cast<const invec_t*>(
&weights_base[i1 * OutputDimensions * ChunkSize]);
const auto col2 = reinterpret_cast<const invec_t*>(
&weights_base[i2 * OutputDimensions * ChunkSize]);
for (IndexType l = 0; l < NumAccums; ++l)
{
vec_add_dpbusd_32(acc[l], in0, col0[l]);
vec_add_dpbusd_32(acc[l + NumAccums], in1, col1[l]);
vec_add_dpbusd_32(acc[l + 2 * NumAccums], in2, col2[l]);
}
}
#else
while (bits)
{
isize i = pop_lsb(bits);
const auto* input_addr = base_addr + i * sizeof(i32);
auto col =
reinterpret_cast<const invec_t*>(&weights_base[i * OutputDimensions * ChunkSize]);
#ifdef FIX_GCC15_MISOPTIMIZATION
asm("" : "+r"(col), "+r"(input_addr));
#undef FIX_GCC15_MISOPTIMIZATION
#endif
const invec_t in = vec_set_32(load_as<i32>(input_addr));
for (IndexType l = 0; l < NumAccums; ++l)
vec_add_dpbusd_32(acc[l], in, col[l]);
}
#endif
}
#if defined(USE_AVXVNNI)
for (IndexType l = 0; l < NumAccums; ++l)
acc[l] = vec_add_32(acc[l], acc[l + NumAccums]);
#elif defined(USE_NEON_DOTPROD)
for (IndexType l = 0; l < NumAccums; ++l)
acc[l] = vaddq_s32(vaddq_s32(acc[l], acc[l + NumAccums]), acc[l + 2 * NumAccums]);
#endif
#endif
outvec_t* outptr = reinterpret_cast<outvec_t*>(output);
for (IndexType k = 0; k < NumAccums; ++k)
outptr[k] = acc[k];
@@ -355,6 +371,51 @@ class AffineTransformSparseInput {
#ifdef vec_add_32
#undef vec_add_32
#endif
#elif defined(USE_RVV)
static_assert(InputDimensions % 256 == 0);
const i8* weights_cp = weights;
#define RVV_SPARSE_PROPAGATE(LMUL) \
do \
{ \
const usize blk = __riscv_vsetvlmax_e32m##LMUL(); \
for (IndexType ob = 0; ob < OutputDimensions; ob += blk) \
{ \
const usize vl = __riscv_vsetvl_e32m##LMUL(OutputDimensions - ob); \
vint32m##LMUL##_t acc = __riscv_vle32_v_i32m##LMUL(biases + ob, vl); \
for (IndexType k = 0; k < InputDimensions / 256; ++k) \
{ \
u64 bits = load_as<u64>(nnzInfo.bitset + k * 8); \
isize base = k * 64; \
auto* base_addr = input + base * sizeof(i32); \
auto* weights_base = &weights_cp[base * OutputDimensions * ChunkSize]; \
while (bits) \
{ \
isize i = pop_lsb(bits); \
vuint8m##LMUL##_t a = __riscv_vreinterpret_v_u32m##LMUL##_u8m##LMUL( \
__riscv_vmv_v_x_u32m##LMUL(load_as<u32>(base_addr + i * sizeof(i32)), \
vl)); \
vint8m##LMUL##_t b = __riscv_vle8_v_i8m##LMUL( \
&weights_base[i * OutputDimensions * ChunkSize + ob * ChunkSize], \
vl * ChunkSize); \
acc = \
__riscv_vadd_vv_i32m##LMUL(acc, SIMD::rvv_dpbusd_m##LMUL(a, b, vl), vl); \
} \
} \
__riscv_vse32_v_i32m##LMUL(output + ob, acc, vl); \
} \
} while (0)
// Select LMUL
if (__riscv_vsetvlmax_e32m1() >= OutputDimensions)
RVV_SPARSE_PROPAGATE(1);
else if (__riscv_vsetvlmax_e32m2() >= OutputDimensions)
RVV_SPARSE_PROPAGATE(2);
else
RVV_SPARSE_PROPAGATE(4);
#undef RVV_SPARSE_PROPAGATE
#else
// Use dense implementation for the other architectures.
affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
@@ -364,7 +425,7 @@ class AffineTransformSparseInput {
private:
using BiasType = OutputType;
using WeightType = std::int8_t;
using WeightType = i8;
alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
+73 -61
View File
@@ -30,12 +30,12 @@
namespace Stockfish::Eval::NNUE::Layers {
// Clipped ReLU
template<IndexType InDims>
template<IndexType InDims, int WeightScaleBitsLocal = WeightScaleBits>
class ClippedReLU {
public:
// Input/output type
using InputType = std::int32_t;
using OutputType = std::uint8_t;
using InputType = i32;
using OutputType = u8;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = InDims;
@@ -46,9 +46,11 @@ class ClippedReLU {
using OutputBuffer = OutputType[PaddedOutputDimensions];
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
std::uint32_t hashValue = 0x538D24C7u;
static constexpr u32 get_hash_value(u32 prevHash) {
u32 hashValue = 0x538D24C7u;
hashValue += prevHash;
// TODO: consider including WeightScaleBitsLocal in the hash value.
// For now omitted on purpose because not written by trainer (yet)
return hashValue;
}
@@ -58,8 +60,8 @@ class ClippedReLU {
// Write network parameters
bool write_parameters(std::ostream&) const { return true; }
std::size_t get_content_hash() const {
std::size_t h = 0;
usize get_content_hash() const {
usize h = 0;
hash_combine(h, get_hash_value(0));
return h;
}
@@ -67,49 +69,9 @@ class ClippedReLU {
// Forward propagation
void propagate(const InputType* input, OutputType* output) const {
#if defined(USE_AVX2)
if constexpr (InputDimensions % SimdWidth == 0)
{
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
const auto in = reinterpret_cast<const __m256i*>(input);
const auto out = reinterpret_cast<__m256i*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m256i words0 =
_mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 0]),
_mm256_load_si256(&in[i * 4 + 1])),
WeightScaleBits);
const __m256i words1 =
_mm256_srli_epi16(_mm256_packus_epi32(_mm256_load_si256(&in[i * 4 + 2]),
_mm256_load_si256(&in[i * 4 + 3])),
WeightScaleBits);
_mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(
_mm256_packs_epi16(words0, words1), Offsets));
}
}
else
{
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m128i words0 = _mm_srli_epi16(
_mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
WeightScaleBits);
const __m128i words1 = _mm_srli_epi16(
_mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
WeightScaleBits);
_mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));
}
}
constexpr IndexType Start = InputDimensions % SimdWidth == 0
? InputDimensions / SimdWidth * SimdWidth
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
#elif defined(USE_SSE2)
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
#if defined(USE_SSE2)
constexpr IndexType NumChunks = InputDimensions / 16;
#ifndef USE_SSE41
const __m128i k0x80s = _mm_set1_epi8(-128);
@@ -122,45 +84,95 @@ class ClippedReLU {
#if defined(USE_SSE41)
const __m128i words0 = _mm_srli_epi16(
_mm_packus_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
WeightScaleBits);
WeightScaleBitsLocal);
const __m128i words1 = _mm_srli_epi16(
_mm_packus_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
WeightScaleBits);
WeightScaleBitsLocal);
_mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));
#else
const __m128i words0 = _mm_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
WeightScaleBits);
WeightScaleBitsLocal);
const __m128i words1 = _mm_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
WeightScaleBits);
WeightScaleBitsLocal);
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i], _mm_subs_epi8(_mm_adds_epi8(packedbytes, k0x80s), k0x80s));
#endif
}
constexpr IndexType Start = NumChunks * SimdWidth;
constexpr IndexType Start = NumChunks * 16;
#elif defined(USE_NEON)
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
const int8x8_t Zero = {0};
const auto in = reinterpret_cast<const int32x4_t*>(input);
const auto out = reinterpret_cast<int8x8_t*>(output);
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
const SIMD::vec_i8x8_t Zero = {0};
const auto in = reinterpret_cast<const SIMD::vec_i32x4_t*>(input);
const auto out = reinterpret_cast<SIMD::vec_i8x8_t*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
int16x8_t shifted;
const auto pack = reinterpret_cast<int16x4_t*>(&shifted);
pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBits);
pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBits);
pack[0] = vqshrn_n_s32(in[i * 2 + 0], WeightScaleBitsLocal);
pack[1] = vqshrn_n_s32(in[i * 2 + 1], WeightScaleBitsLocal);
out[i] = vmax_s8(vqmovn_s16(shifted), Zero);
}
constexpr IndexType Start = NumChunks * (SimdWidth / 2);
#elif defined(USE_LASX)
constexpr IndexType NumChunks = InputDimensions / 32;
const auto in = reinterpret_cast<const __m256i*>(input);
const auto out = reinterpret_cast<__m256i*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m256i packed0 = SIMD::lasx_packus_32(in[i * 4 + 0], in[i * 4 + 1]);
const __m256i packed1 = SIMD::lasx_packus_32(in[i * 4 + 2], in[i * 4 + 3]);
const __m256i words0 = __lasx_xvsrli_h(packed0, WeightScaleBitsLocal);
const __m256i words1 = __lasx_xvsrli_h(packed1, WeightScaleBitsLocal);
const __m256i packed = __lasx_xvssrani_b_h(words1, words0, 0);
const __m256i swaped = __lasx_xvpermi_d(packed, 0xD8);
__lasx_xvst(__lasx_xvshuf4i_w(swaped, 0xD8), out + i, 0);
}
constexpr IndexType Start = NumChunks * 32;
#elif defined(USE_LSX)
constexpr IndexType NumChunks = InputDimensions / 16;
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m128i packed0 = SIMD::lsx_packus_32(in[i * 4 + 0], in[i * 4 + 1]);
const __m128i packed1 = SIMD::lsx_packus_32(in[i * 4 + 2], in[i * 4 + 3]);
const __m128i words0 = __lsx_vsrli_h(packed0, WeightScaleBitsLocal);
const __m128i words1 = __lsx_vsrli_h(packed1, WeightScaleBitsLocal);
out[i] = __lsx_vssrani_b_h(words1, words0, 0);
}
constexpr IndexType Start = NumChunks * 16;
#elif defined(USE_RVV)
for (usize j = 0; j < InputDimensions;)
{
usize vl = __riscv_vsetvl_e32m4(InputDimensions - j);
vint32m4_t in = __riscv_vle32_v_i32m4(&input[j], vl);
in = __riscv_vmax_vx_i32m4(in, 0, vl);
vint16m2_t words =
__riscv_vnclip_wx_i16m2(in, WeightScaleBitsLocal, __RISCV_VXRM_RDN, vl);
vint8m1_t narrowed = __riscv_vnclip_wx_i8m1(words, 0, __RISCV_VXRM_RDN, vl);
__riscv_vse8_v_u8m1(&output[j], __riscv_vreinterpret_v_i8m1_u8m1(narrowed), vl);
j += vl;
}
constexpr IndexType Start = InputDimensions;
#else
constexpr IndexType Start = 0;
#endif
for (IndexType i = Start; i < InputDimensions; ++i)
{
output[i] = static_cast<OutputType>(std::clamp(input[i] >> WeightScaleBits, 0, 127));
output[i] =
static_cast<OutputType>(std::clamp(input[i] >> WeightScaleBitsLocal, 0, 127));
}
}
};
+146 -18
View File
@@ -26,16 +26,17 @@
#include <iosfwd>
#include "../nnue_common.h"
#include "../simd.h"
namespace Stockfish::Eval::NNUE::Layers {
// Clipped ReLU
template<IndexType InDims>
template<IndexType InDims, int WeightScaleBitsLocal = WeightScaleBits>
class SqrClippedReLU {
public:
// Input/output type
using InputType = std::int32_t;
using OutputType = std::uint8_t;
using InputType = i32;
using OutputType = u8;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = InDims;
@@ -46,9 +47,11 @@ class SqrClippedReLU {
using OutputBuffer = OutputType[PaddedOutputDimensions];
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value(std::uint32_t prevHash) {
std::uint32_t hashValue = 0x538D24C7u;
static constexpr u32 get_hash_value(u32 prevHash) {
u32 hashValue = 0x538D24C7u;
hashValue += prevHash;
// TODO: consider including WeightScaleBitsLocal in the hash value.
// For now omitted on purpose because not written by trainer (yet)
return hashValue;
}
@@ -58,21 +61,82 @@ class SqrClippedReLU {
// Write network parameters
bool write_parameters(std::ostream&) const { return true; }
std::size_t get_content_hash() const {
std::size_t h = 0;
usize get_content_hash() const {
usize h = 0;
hash_combine(h, get_hash_value(0));
return h;
}
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
// Produce the squared and linear clipped activations together, sharing the input loads and
// the initial signed 32-to-16-bit saturating packs.
void propagate_pair(const InputType* input, OutputType* squared, OutputType* clipped) const {
static_assert(WeightScaleBitsLocal >= 5 && WeightScaleBitsLocal <= 8,
"SqrClippedReLU only support WeightScaleBitsLocal between 5 and 8");
static_assert(InputDimensions % 32 == 0);
constexpr IndexType NumChunks = InputDimensions / 32;
constexpr int SimdShiftAmount = WeightScaleBitsLocal * 2 + 7 - 16;
const auto in = reinterpret_cast<const __m256i*>(input);
auto sqrOut = reinterpret_cast<__m256i*>(squared);
auto clipOut = reinterpret_cast<__m256i*>(clipped);
const __m256i zero = _mm256_setzero_si256();
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m256i words0 = _mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 0]),
_mm256_load_si256(&in[i * 4 + 1]));
const __m256i words1 = _mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 2]),
_mm256_load_si256(&in[i * 4 + 3]));
const __m256i sqr0 =
_mm256_srli_epi16(_mm256_mulhi_epi16(words0, words0), SimdShiftAmount);
const __m256i sqr1 =
_mm256_srli_epi16(_mm256_mulhi_epi16(words1, words1), SimdShiftAmount);
const __m256i sqrPacked = _mm256_packs_epi16(sqr0, sqr1);
_mm256_store_si256(&sqrOut[i], sqrPacked);
const __m256i clip0 =
_mm256_srli_epi16(_mm256_max_epi16(words0, zero), WeightScaleBitsLocal);
const __m256i clip1 =
_mm256_srli_epi16(_mm256_max_epi16(words1, zero), WeightScaleBitsLocal);
const __m256i clipPacked = _mm256_packs_epi16(clip0, clip1);
_mm256_store_si256(&clipOut[i], clipPacked);
}
}
#endif
// Forward propagation
void propagate(const InputType* input, OutputType* output) const {
static_assert(WeightScaleBitsLocal >= 5 && WeightScaleBitsLocal <= 8,
"SqrClippedReLU only support WeightScaleBitsLocal between 5 and 8");
// After squaring we need to shift by WeightScaleBitsLocal * 2 + 7
// MulHi strips the lower 16 bits (i.e. shift by 16) so we need to shift out the remaining.
[[maybe_unused]] constexpr int SimdShiftAmount = WeightScaleBitsLocal * 2 + 7 - 16;
#if defined(USE_SSE2)
#if defined(USE_AVX512)
static_assert(InputDimensions % 32 == 0);
constexpr IndexType NumChunks = InputDimensions / 32;
const auto in = reinterpret_cast<const __m512i*>(input);
const auto out = reinterpret_cast<__m256i*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m256i words0 = _mm512_cvtsepi32_epi16(_mm512_load_si512(&in[i * 2 + 0]));
const __m256i words1 = _mm512_cvtsepi32_epi16(_mm512_load_si512(&in[i * 2 + 1]));
__m512i words = _mm512_inserti64x4(_mm512_castsi256_si512(words0), words1, 1);
words = _mm512_srli_epi16(_mm512_mulhi_epi16(words, words), SimdShiftAmount);
_mm256_store_si256(&out[i], _mm512_cvtsepi16_epi8(words));
}
constexpr IndexType Start = NumChunks * 32;
#elif defined(USE_SSE2)
constexpr IndexType NumChunks = InputDimensions / 16;
static_assert(WeightScaleBits == 6);
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
__m128i words0 =
@@ -80,16 +144,79 @@ class SqrClippedReLU {
__m128i words1 =
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3]));
// We shift by WeightScaleBits * 2 = 12 and divide by 128
// which is an additional shift-right of 7, meaning 19 in total.
// MulHi strips the lower 16 bits so we need to shift out 3 more to match.
words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), 3);
words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), 3);
words0 = _mm_srli_epi16(_mm_mulhi_epi16(words0, words0), SimdShiftAmount);
words1 = _mm_srli_epi16(_mm_mulhi_epi16(words1, words1), SimdShiftAmount);
_mm_store_si128(&out[i], _mm_packs_epi16(words0, words1));
}
constexpr IndexType Start = NumChunks * 16;
#elif defined(USE_LASX)
constexpr IndexType NumChunks = InputDimensions / 32;
const auto in = reinterpret_cast<const __m256i*>(input);
const auto out = reinterpret_cast<__m256i*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m256i words0 = __lasx_xvssrani_h_w(in[i * 4 + 1], in[i * 4 + 0], 0);
const __m256i words1 = __lasx_xvssrani_h_w(in[i * 4 + 3], in[i * 4 + 2], 0);
const __m256i sqr0 = __lasx_xvmuh_h(words0, words0);
const __m256i sqr1 = __lasx_xvmuh_h(words1, words1);
__m256i packed;
packed = __lasx_xvssrlni_b_h(sqr1, sqr0, SimdShiftAmount);
const __m256i permed = __lasx_xvpermi_d(packed, 0xD8);
__lasx_xvst(__lasx_xvshuf4i_w(permed, 0xD8), out + i, 0);
}
constexpr IndexType Start = NumChunks * 32;
#elif defined(USE_LSX)
constexpr IndexType NumChunks = InputDimensions / 16;
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m128i words0 = __lsx_vssrani_h_w(in[i * 4 + 1], in[i * 4 + 0], 0);
const __m128i words1 = __lsx_vssrani_h_w(in[i * 4 + 3], in[i * 4 + 2], 0);
const __m128i sqr0 = __lsx_vmuh_h(words0, words0);
const __m128i sqr1 = __lsx_vmuh_h(words1, words1);
out[i] = __lsx_vssrlni_b_h(sqr1, sqr0, SimdShiftAmount);
}
constexpr IndexType Start = NumChunks * 16;
#elif defined(USE_NEON)
constexpr IndexType NumChunks = InputDimensions / 16;
const auto in = reinterpret_cast<const int32x4_t*>(input);
const auto out = reinterpret_cast<int8x16_t*>(output);
for (IndexType i = 0; i < NumChunks; ++i)
{
const int16x8_t words0 =
vcombine_s16(vqmovn_s32(in[i * 4 + 0]), vqmovn_s32(in[i * 4 + 1]));
const int16x8_t words1 =
vcombine_s16(vqmovn_s32(in[i * 4 + 2]), vqmovn_s32(in[i * 4 + 3]));
// Neon needs to shift by one more since the used simd instruction does
// `Saturating Doubling Multiply High` (doubling before shift by 16).
const int16x8_t r0 = vshrq_n_s16(vqdmulhq_s16(words0, words0), SimdShiftAmount + 1);
const int16x8_t r1 = vshrq_n_s16(vqdmulhq_s16(words1, words1), SimdShiftAmount + 1);
out[i] = vcombine_s8(vqmovn_s16(r0), vqmovn_s16(r1));
}
constexpr IndexType Start = NumChunks * 16;
#elif defined(USE_RVV)
for (usize j = 0; j < InputDimensions;)
{
usize vl = __riscv_vsetvl_e32m4(InputDimensions - j);
vint32m4_t in = __riscv_vle32_v_i32m4(&input[j], vl);
vint16m2_t words = __riscv_vnclip_wx_i16m2(in, 0, __RISCV_VXRM_RDN, vl);
vint16m2_t sqr = __riscv_vmulh_vv_i16m2(words, words, vl);
vint8m1_t narrowed = __riscv_vnclip_wx_i8m1(sqr, SimdShiftAmount, __RISCV_VXRM_RDN, vl);
__riscv_vse8_v_u8m1(&output[j], __riscv_vreinterpret_v_i8m1_u8m1(narrowed), vl);
j += vl;
}
constexpr IndexType Start = InputDimensions;
#else
constexpr IndexType Start = 0;
#endif
@@ -99,7 +226,8 @@ class SqrClippedReLU {
output[i] = static_cast<OutputType>(
// Really should be /127 but we need to make it fast so we right-shift
// by an extra 7 bits instead. Needs to be accounted for in the trainer.
std::min(127ll, ((long long) (input[i]) * input[i]) >> (2 * WeightScaleBits + 7)));
std::min(127ll,
((long long) (input[i]) * input[i]) >> (2 * WeightScaleBitsLocal + 7)));
}
}
};
+105 -168
View File
@@ -24,6 +24,7 @@
#include <optional>
#include <type_traits>
#include <vector>
#include <filesystem>
#define INCBIN_SILENCE_BITCODE_WARNING
#include "../incbin/incbin.h"
@@ -35,6 +36,7 @@
#include "nnue_architecture.h"
#include "nnue_common.h"
#include "nnue_misc.h"
#include "nnz_helper.h"
// Macro to embed the default efficiently updatable neural network (NNUE) file
// data in the engine binary (using incbin.h, by Dale Weiler).
@@ -43,46 +45,24 @@
// const unsigned char *const gEmbeddedNNUEEnd; // a marker to the end
// const unsigned int gEmbeddedNNUESize; // the size of the embedded file
// Note that this does not work in Microsoft Visual Studio.
#if !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
INCBIN(EmbeddedNNUEBig, EvalFileDefaultNameBig);
INCBIN(EmbeddedNNUESmall, EvalFileDefaultNameSmall);
#if !defined(UNIVERSAL_BINARY) && !defined(_MSC_VER) && !defined(NNUE_EMBEDDING_OFF)
INCBIN(EmbeddedNNUE, EvalFileDefaultName);
#elif defined(UNIVERSAL_BINARY_MACOS_X86_SLICE)
// Determined at runtime, see universal/nnue_embed.cpp
extern const unsigned char* const gEmbeddedNNUEData;
extern const unsigned int gEmbeddedNNUESize;
#elif defined(UNIVERSAL_BINARY)
extern const unsigned char gEmbeddedNNUEData[];
extern const unsigned int gEmbeddedNNUESize;
#else
const unsigned char gEmbeddedNNUEBigData[1] = {0x0};
const unsigned char* const gEmbeddedNNUEBigEnd = &gEmbeddedNNUEBigData[1];
const unsigned int gEmbeddedNNUEBigSize = 1;
const unsigned char gEmbeddedNNUESmallData[1] = {0x0};
const unsigned char* const gEmbeddedNNUESmallEnd = &gEmbeddedNNUESmallData[1];
const unsigned int gEmbeddedNNUESmallSize = 1;
const unsigned char gEmbeddedNNUEData[1] = {0x0};
const unsigned int gEmbeddedNNUESize = 1;
#endif
namespace {
struct EmbeddedNNUE {
EmbeddedNNUE(const unsigned char* embeddedData,
const unsigned char* embeddedEnd,
const unsigned int embeddedSize) :
data(embeddedData),
end(embeddedEnd),
size(embeddedSize) {}
const unsigned char* data;
const unsigned char* end;
const unsigned int size;
};
using namespace Stockfish::Eval::NNUE;
EmbeddedNNUE get_embedded(EmbeddedNNUEType type) {
if (type == EmbeddedNNUEType::BIG)
return EmbeddedNNUE(gEmbeddedNNUEBigData, gEmbeddedNNUEBigEnd, gEmbeddedNNUEBigSize);
else
return EmbeddedNNUE(gEmbeddedNNUESmallData, gEmbeddedNNUESmallEnd, gEmbeddedNNUESmallSize);
}
}
namespace Stockfish::Eval::NNUE {
namespace fs = std::filesystem;
namespace Detail {
@@ -90,8 +70,8 @@ namespace Detail {
template<typename T>
bool read_parameters(std::istream& stream, T& reference) {
std::uint32_t header;
header = read_little_endian<std::uint32_t>(stream);
u32 header;
header = read_little_endian<u32>(stream);
if (!stream || header != T::get_hash_value())
return false;
return reference.read_parameters(stream);
@@ -101,107 +81,96 @@ bool read_parameters(std::istream& stream, T& reference) {
template<typename T>
bool write_parameters(std::ostream& stream, const T& reference) {
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
write_little_endian<u32>(stream, T::get_hash_value());
return reference.write_parameters(stream);
}
} // namespace Detail
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::load(const std::string& rootDirectory, std::string evalfilePath) {
void Network::load(const fs::path& rootDirectory, fs::path evalfilePath, EvalFile& evalFile) {
#if defined(DEFAULT_NNUE_DIRECTORY)
std::vector<std::string> dirs = {"<internal>", "", rootDirectory,
stringify(DEFAULT_NNUE_DIRECTORY)};
std::vector<fs::path> dirs = {fs::path{}, rootDirectory,
fs::path(stringify(DEFAULT_NNUE_DIRECTORY))};
#else
std::vector<std::string> dirs = {"<internal>", "", rootDirectory};
std::vector<fs::path> dirs = {fs::path{}, rootDirectory};
#endif
if (evalfilePath.empty())
evalfilePath = evalFile.defaultName;
if (evalFile.current != evalfilePath && evalfilePath == evalFile.defaultName)
load_internal(evalFile);
for (const auto& directory : dirs)
{
if (std::string(evalFile.current) != evalfilePath)
{
if (directory != "<internal>")
{
load_user_net(directory, evalfilePath);
}
if (directory == "<internal>" && evalfilePath == std::string(evalFile.defaultName))
{
load_internal();
}
}
if (evalFile.current != evalfilePath)
load_external(directory, evalfilePath, evalFile);
}
}
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::save(const std::optional<std::string>& filename) const {
std::string actualFilename;
std::string msg;
if (filename.has_value())
actualFilename = filename.value();
else
bool Network::save(const EvalFile& evalFile, const std::optional<fs::path>& filename) const {
if (!evalFile.current.has_value())
{
if (std::string(evalFile.current) != std::string(evalFile.defaultName))
{
msg = "Failed to export a net. "
"A non-embedded net can only be saved if the filename is specified";
sync_cout << msg << sync_endl;
return false;
}
actualFilename = evalFile.defaultName;
sync_cout << "Failed to export a net. No network file is currently loaded. "
"Please load a network file first."
<< sync_endl;
return false;
}
if (!filename.has_value() && evalFile.current != evalFile.defaultName)
{
sync_cout << "Failed to export a net. A non-embedded net can only be "
"saved if the filename is specified"
<< sync_endl;
return false;
}
fs::path actualFilename = filename.value_or(evalFile.defaultName);
std::ofstream stream(actualFilename, std::ios_base::binary);
bool saved = save(stream, evalFile.current, evalFile.netDescription);
msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net";
bool saved = save(stream, evalFile.netDescription);
sync_cout << (saved ? "Network saved successfully to " + actualFilename.string()
: "Failed to export a net")
<< sync_endl;
sync_cout << msg << sync_endl;
return saved;
}
NetworkOutput Network::evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches& cache) const {
template<typename Arch, typename Transformer>
NetworkOutput
Network<Arch, Transformer>::evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const {
constexpr u64 alignment = CacheLineSize;
constexpr uint64_t alignment = CacheLineSize;
alignas(alignment)
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
ASSERT_ALIGNED(transformedFeatures, alignment);
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
const auto psqt =
featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
const auto positional = network[bucket].propagate(transformedFeatures);
NNZInfo<L1> nnzInfo;
const int bucket = (pos.count<ALL_PIECES>() - 1) / 4;
const auto psqt = featureTransformer.transform(pos, accumulatorStack, cache,
transformedFeatures, bucket, nnzInfo);
const auto positional = network[bucket].propagate(transformedFeatures, nnzInfo);
return {static_cast<Value>(psqt / OutputScale), static_cast<Value>(positional / OutputScale)};
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::verify(std::string evalfilePath,
const std::function<void(std::string_view)>& f) const {
void Network::verify(const std::function<void(std::string_view)>& f,
const EvalFile& evalFile,
fs::path evalfilePath) const {
if (evalfilePath.empty())
evalfilePath = evalFile.defaultName;
if (std::string(evalFile.current) != evalfilePath)
if (evalFile.current != evalfilePath)
{
if (f)
{
std::string msg1 =
"Network evaluation parameters compatible with the engine must be available.";
std::string msg2 = "The network file " + evalfilePath + " was not loaded successfully.";
std::string msg2 =
"The network file " + evalfilePath.string() + " was not loaded successfully.";
std::string msg3 = "The UCI option EvalFile might need to specify the full path, "
"including the directory name, to the network file.";
std::string msg4 = "The default net can be downloaded from: "
@@ -220,9 +189,10 @@ void Network<Arch, Transformer>::verify(std::string
if (f)
{
size_t size = sizeof(featureTransformer) + sizeof(Arch) * LayerStacks;
f("NNUE evaluation using " + evalfilePath + " (" + std::to_string(size / (1024 * 1024))
+ "MiB, (" + std::to_string(featureTransformer.TotalInputDimensions) + ", "
usize size = sizeof(featureTransformer) + sizeof(NetworkArchitecture) * LayerStacks;
f("NNUE evaluation using " + evalfilePath.string() + " ("
+ std::to_string(size / (1024 * 1024)) + "MiB, ("
+ std::to_string(featureTransformer.InputDimensions) + ", "
+ std::to_string(network[0].TransformedFeatureDimensions) + ", "
+ std::to_string(network[0].FC_0_OUTPUTS) + ", " + std::to_string(network[0].FC_1_OUTPUTS)
+ ", 1))");
@@ -230,16 +200,13 @@ void Network<Arch, Transformer>::verify(std::string
}
template<typename Arch, typename Transformer>
NnueEvalTrace
Network<Arch, Transformer>::trace_evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const {
NnueEvalTrace Network::trace_evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches& cache) const {
constexpr uint64_t alignment = CacheLineSize;
constexpr u64 alignment = CacheLineSize;
alignas(alignment)
TransformedFeatureType transformedFeatures[FeatureTransformer<FTDimensions>::BufferSize];
alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
ASSERT_ALIGNED(transformedFeatures, alignment);
@@ -247,9 +214,10 @@ Network<Arch, Transformer>::trace_evaluate(const Position&
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)
{
const auto materialist =
featureTransformer.transform(pos, accumulatorStack, cache, transformedFeatures, bucket);
const auto positional = network[bucket].propagate(transformedFeatures);
NNZInfo<L1> nnzInfo;
const auto materialist = featureTransformer.transform(pos, accumulatorStack, cache,
transformedFeatures, bucket, nnzInfo);
const auto positional = network[bucket].propagate(transformedFeatures, nnzInfo);
t.psqt[bucket] = static_cast<Value>(materialist / OutputScale);
t.positional[bucket] = static_cast<Value>(positional / OutputScale);
@@ -259,10 +227,8 @@ Network<Arch, Transformer>::trace_evaluate(const Position&
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::load_user_net(const std::string& dir,
const std::string& evalfilePath) {
std::ifstream stream(dir + evalfilePath, std::ios::binary);
void Network::load_external(const fs::path& dir, const fs::path& evalfilePath, EvalFile& evalFile) {
std::ifstream stream(dir / evalfilePath, std::ios::binary);
auto description = load(stream);
if (description.has_value())
@@ -273,21 +239,23 @@ void Network<Arch, Transformer>::load_user_net(const std::string& dir,
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::load_internal() {
void Network::load_internal(EvalFile& evalFile) {
// C++ way to prepare a buffer for a memory stream
class MemoryBuffer: public std::basic_streambuf<char> {
public:
MemoryBuffer(char* p, size_t n) {
MemoryBuffer(char* p, usize n) {
setg(p, p, p + n);
setp(p, p + n);
}
};
const auto embedded = get_embedded(embeddedType);
#ifdef UNIVERSAL_BINARY_MACOS_X86_SLICE
if (gEmbeddedNNUEData == nullptr) // failed embedded load
return;
#endif
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(embedded.data)),
size_t(embedded.size));
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
usize(gEmbeddedNNUESize));
std::istream stream(&buffer);
auto description = load(stream);
@@ -300,25 +268,15 @@ void Network<Arch, Transformer>::load_internal() {
}
template<typename Arch, typename Transformer>
void Network<Arch, Transformer>::initialize() {
initialized = true;
}
void Network::initialize() { initialized = true; }
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::save(std::ostream& stream,
const std::string& name,
const std::string& netDescription) const {
if (name.empty() || name == "None")
return false;
bool Network::save(std::ostream& stream, const std::string& netDescription) const {
return write_parameters(stream, netDescription);
}
template<typename Arch, typename Transformer>
std::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream) {
std::optional<std::string> Network::load(std::istream& stream) {
initialize();
std::string description;
@@ -326,30 +284,24 @@ std::optional<std::string> Network<Arch, Transformer>::load(std::istream& stream
}
template<typename Arch, typename Transformer>
std::size_t Network<Arch, Transformer>::get_content_hash() const {
usize Network::get_content_hash() const {
if (!initialized)
return 0;
std::size_t h = 0;
usize h = 0;
hash_combine(h, featureTransformer);
for (auto&& layerstack : network)
hash_combine(h, layerstack);
hash_combine(h, evalFile);
hash_combine(h, static_cast<int>(embeddedType));
return h;
}
// Read network header
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::read_header(std::istream& stream,
std::uint32_t* hashValue,
std::string* desc) const {
std::uint32_t version, size;
bool Network::read_header(std::istream& stream, u32* hashValue, std::string* desc) const {
u32 version, size;
version = read_little_endian<std::uint32_t>(stream);
*hashValue = read_little_endian<std::uint32_t>(stream);
size = read_little_endian<std::uint32_t>(stream);
version = read_little_endian<u32>(stream);
*hashValue = read_little_endian<u32>(stream);
size = read_little_endian<u32>(stream);
if (!stream || version != Version)
return false;
desc->resize(size);
@@ -359,29 +311,24 @@ bool Network<Arch, Transformer>::read_header(std::istream& stream,
// Write network header
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::write_header(std::ostream& stream,
std::uint32_t hashValue,
const std::string& desc) const {
write_little_endian<std::uint32_t>(stream, Version);
write_little_endian<std::uint32_t>(stream, hashValue);
write_little_endian<std::uint32_t>(stream, std::uint32_t(desc.size()));
bool Network::write_header(std::ostream& stream, u32 hashValue, const std::string& desc) const {
write_little_endian<u32>(stream, Version);
write_little_endian<u32>(stream, hashValue);
write_little_endian<u32>(stream, u32(desc.size()));
stream.write(&desc[0], desc.size());
return !stream.fail();
}
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::read_parameters(std::istream& stream,
std::string& netDescription) {
std::uint32_t hashValue;
bool Network::read_parameters(std::istream& stream, std::string& netDescription) {
u32 hashValue;
if (!read_header(stream, &hashValue, &netDescription))
return false;
if (hashValue != Network::hash)
return false;
if (!Detail::read_parameters(stream, featureTransformer))
return false;
for (std::size_t i = 0; i < LayerStacks; ++i)
for (usize i = 0; i < LayerStacks; ++i)
{
if (!Detail::read_parameters(stream, network[i]))
return false;
@@ -390,14 +337,12 @@ bool Network<Arch, Transformer>::read_parameters(std::istream& stream,
}
template<typename Arch, typename Transformer>
bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
const std::string& netDescription) const {
bool Network::write_parameters(std::ostream& stream, const std::string& netDescription) const {
if (!write_header(stream, Network::hash, netDescription))
return false;
if (!Detail::write_parameters(stream, featureTransformer))
return false;
for (std::size_t i = 0; i < LayerStacks; ++i)
for (usize i = 0; i < LayerStacks; ++i)
{
if (!Detail::write_parameters(stream, network[i]))
return false;
@@ -405,12 +350,4 @@ bool Network<Arch, Transformer>::write_parameters(std::ostream& stream,
return bool(stream);
}
// Explicit template instantiations
template class Network<NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>,
FeatureTransformer<TransformedFeatureDimensionsBig>>;
template class Network<NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>,
FeatureTransformer<TransformedFeatureDimensionsSmall>>;
} // namespace Stockfish::Eval::NNUE
+35 -75
View File
@@ -19,8 +19,6 @@
#ifndef NETWORK_H_INCLUDED
#define NETWORK_H_INCLUDED
#include <cstddef>
#include <cstdint>
#include <functional>
#include <iostream>
#include <memory>
@@ -28,12 +26,11 @@
#include <string>
#include <string_view>
#include <tuple>
#include <filesystem>
#include "../misc.h"
#include "../types.h"
#include "nnue_accumulator.h"
#include "../misc.h"
#include "nnue_architecture.h"
#include "nnue_common.h"
#include "nnue_feature_transformer.h"
#include "nnue_misc.h"
@@ -43,24 +40,17 @@ class Position;
namespace Stockfish::Eval::NNUE {
enum class EmbeddedNNUEType {
BIG,
SMALL,
};
class AccumulatorStack;
struct AccumulatorCaches;
using NetworkOutput = std::tuple<Value, Value>;
// The network must be a trivial type, i.e. the memory must be in-line.
// This is required to allow sharing the network via shared memory, as
// there is no way to run destructors.
template<typename Arch, typename Transformer>
class Network {
static constexpr IndexType FTDimensions = Arch::TransformedFeatureDimensions;
public:
Network(EvalFile file, EmbeddedNNUEType type) :
evalFile(file),
embeddedType(type) {}
Network() = default;
Network(const Network& other) = default;
Network(Network&& other) = default;
@@ -68,94 +58,64 @@ class Network {
Network& operator=(const Network& other) = default;
Network& operator=(Network&& other) = default;
void load(const std::string& rootDirectory, std::string evalfilePath);
bool save(const std::optional<std::string>& filename) const;
void load(const std::filesystem::path& rootDirectory,
std::filesystem::path evalfilePath,
EvalFile& evalFile);
bool save(const EvalFile& evalFile, const std::optional<std::filesystem::path>& filename) const;
std::size_t get_content_hash() const;
usize get_content_hash() const;
NetworkOutput evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const;
NetworkOutput evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches& cache) const;
void verify(std::string evalfilePath, const std::function<void(std::string_view)>&) const;
NnueEvalTrace trace_evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<FTDimensions>& cache) const;
void verify(const std::function<void(std::string_view)>& f,
const EvalFile& evalFile,
std::filesystem::path evalfilePath) const;
NnueEvalTrace trace_evaluate(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches& cache) const;
void load_external(const std::filesystem::path&, const std::filesystem::path&, EvalFile&);
void load_internal(EvalFile&);
private:
void load_user_net(const std::string&, const std::string&);
void load_internal();
void initialize();
bool save(std::ostream&, const std::string&, const std::string&) const;
bool save(std::ostream&, const std::string&) const;
std::optional<std::string> load(std::istream&);
bool read_header(std::istream&, std::uint32_t*, std::string*) const;
bool write_header(std::ostream&, std::uint32_t, const std::string&) const;
bool read_header(std::istream&, u32*, std::string*) const;
bool write_header(std::ostream&, u32, const std::string&) const;
bool read_parameters(std::istream&, std::string&);
bool write_parameters(std::ostream&, const std::string&) const;
// Input feature converter
Transformer featureTransformer;
FeatureTransformer featureTransformer;
// Evaluation function
Arch network[LayerStacks];
EvalFile evalFile;
EmbeddedNNUEType embeddedType;
NetworkArchitecture network[LayerStacks];
bool initialized = false;
// Hash value of evaluation function structure
static constexpr std::uint32_t hash = Transformer::get_hash_value() ^ Arch::get_hash_value();
static constexpr u32 hash =
FeatureTransformer::get_hash_value() ^ NetworkArchitecture::get_hash_value();
template<IndexType Size>
friend struct AccumulatorCaches::Cache;
};
// Definitions of the network types
using SmallFeatureTransformer = FeatureTransformer<TransformedFeatureDimensionsSmall>;
using SmallNetworkArchitecture =
NetworkArchitecture<TransformedFeatureDimensionsSmall, L2Small, L3Small>;
using BigFeatureTransformer = FeatureTransformer<TransformedFeatureDimensionsBig>;
using BigNetworkArchitecture = NetworkArchitecture<TransformedFeatureDimensionsBig, L2Big, L3Big>;
using NetworkBig = Network<BigNetworkArchitecture, BigFeatureTransformer>;
using NetworkSmall = Network<SmallNetworkArchitecture, SmallFeatureTransformer>;
struct Networks {
Networks(EvalFile bigFile, EvalFile smallFile) :
big(bigFile, EmbeddedNNUEType::BIG),
small(smallFile, EmbeddedNNUEType::SMALL) {}
NetworkBig big;
NetworkSmall small;
friend struct AccumulatorCaches;
};
} // namespace Stockfish
} // namespace Stockfish::Eval::NNUE
template<typename ArchT, typename FeatureTransformerT>
struct std::hash<Stockfish::Eval::NNUE::Network<ArchT, FeatureTransformerT>> {
std::size_t operator()(
const Stockfish::Eval::NNUE::Network<ArchT, FeatureTransformerT>& network) const noexcept {
template<>
struct std::hash<Stockfish::Eval::NNUE::Network> {
Stockfish::usize operator()(const Stockfish::Eval::NNUE::Network& network) const noexcept {
return network.get_content_hash();
}
};
template<>
struct std::hash<Stockfish::Eval::NNUE::Networks> {
std::size_t operator()(const Stockfish::Eval::NNUE::Networks& networks) const noexcept {
std::size_t h = 0;
Stockfish::hash_combine(h, networks.big);
Stockfish::hash_combine(h, networks.small);
return h;
}
};
#endif
File diff suppressed because it is too large Load Diff
+55 -125
View File
@@ -23,11 +23,10 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <utility>
#include "../types.h"
#include "../misc.h"
#include "nnue_architecture.h"
#include "nnue_common.h"
@@ -37,18 +36,16 @@ class Position;
namespace Stockfish::Eval::NNUE {
template<IndexType Size>
struct alignas(CacheLineSize) Accumulator;
template<IndexType TransformedFeatureDimensions>
class FeatureTransformer;
// Class that holds the result of affine transformation of input features
template<IndexType Size>
// Class that holds the result of affine transformation of input features,
// combined HalfKA + Threats
struct alignas(CacheLineSize) Accumulator {
std::array<std::array<std::int16_t, Size>, COLOR_NB> accumulation;
std::array<std::array<std::int32_t, PSQTBuckets>, COLOR_NB> psqtAccumulation;
std::array<bool, COLOR_NB> computed = {};
std::array<std::array<i16, L1>, COLOR_NB> accumulation;
std::array<std::array<i32, PSQTBuckets>, COLOR_NB> psqtAccumulation;
std::array<bool, COLOR_NB> computed = {};
};
@@ -59,146 +56,79 @@ struct alignas(CacheLineSize) Accumulator {
// This idea, was first described by Luecx (author of Koivisto) and
// is commonly referred to as "Finny Tables".
struct AccumulatorCaches {
template<typename Networks>
AccumulatorCaches(const Networks& networks) {
clear(networks);
template<typename Network>
AccumulatorCaches(const Network& network) {
clear(network);
}
template<IndexType Size>
struct alignas(CacheLineSize) Cache {
struct alignas(CacheLineSize) Entry {
std::array<BiasType, L1> accumulation;
std::array<PSQTWeightType, PSQTBuckets> psqtAccumulation;
std::array<Piece, SQUARE_NB> pieces;
Bitboard pieceBB;
struct alignas(CacheLineSize) Entry {
std::array<BiasType, Size> accumulation;
std::array<PSQTWeightType, PSQTBuckets> psqtAccumulation;
std::array<Piece, SQUARE_NB> pieces;
Bitboard pieceBB;
// To initialize a refresh entry, we set all its bitboards empty,
// so we put the biases in the accumulation, without any weights on top
void clear(const std::array<BiasType, Size>& biases) {
accumulation = biases;
std::memset(reinterpret_cast<std::byte*>(this) + offsetof(Entry, psqtAccumulation),
0, sizeof(Entry) - offsetof(Entry, psqtAccumulation));
}
};
template<typename Network>
void clear(const Network& network) {
for (auto& entries1D : entries)
for (auto& entry : entries1D)
entry.clear(network.featureTransformer.biases);
// To initialize a refresh entry, we set all its bitboards empty,
// so we put the biases in the accumulation, without any weights on top
void clear(const std::array<BiasType, L1>& biases) {
accumulation = biases;
std::memset(reinterpret_cast<std::byte*>(this) + offsetof(Entry, psqtAccumulation), 0,
sizeof(Entry) - offsetof(Entry, psqtAccumulation));
}
std::array<Entry, COLOR_NB>& operator[](Square sq) { return entries[sq]; }
std::array<std::array<Entry, COLOR_NB>, SQUARE_NB> entries;
};
template<typename Networks>
void clear(const Networks& networks) {
big.clear(networks.big);
small.clear(networks.small);
template<typename Network>
void clear(const Network& network) {
for (auto& entries1D : entries)
for (auto& entry : entries1D)
entry.clear(network.featureTransformer.biases);
}
Cache<TransformedFeatureDimensionsBig> big;
Cache<TransformedFeatureDimensionsSmall> small;
std::array<Entry, COLOR_NB>& operator[](Square sq) { return entries[sq]; }
std::array<std::array<Entry, COLOR_NB>, SQUARE_NB> entries;
};
template<typename FeatureSet>
struct AccumulatorState {
Accumulator<TransformedFeatureDimensionsBig> accumulatorBig;
Accumulator<TransformedFeatureDimensionsSmall> accumulatorSmall;
typename FeatureSet::DiffType diff;
template<IndexType Size>
auto& acc() noexcept {
static_assert(Size == TransformedFeatureDimensionsBig
|| Size == TransformedFeatureDimensionsSmall,
"Invalid size for accumulator");
if constexpr (Size == TransformedFeatureDimensionsBig)
return accumulatorBig;
else if constexpr (Size == TransformedFeatureDimensionsSmall)
return accumulatorSmall;
}
template<IndexType Size>
const auto& acc() const noexcept {
static_assert(Size == TransformedFeatureDimensionsBig
|| Size == TransformedFeatureDimensionsSmall,
"Invalid size for accumulator");
if constexpr (Size == TransformedFeatureDimensionsBig)
return accumulatorBig;
else if constexpr (Size == TransformedFeatureDimensionsSmall)
return accumulatorSmall;
}
void reset(const typename FeatureSet::DiffType& dp) noexcept {
diff = dp;
accumulatorBig.computed.fill(false);
accumulatorSmall.computed.fill(false);
}
typename FeatureSet::DiffType& reset() noexcept {
accumulatorBig.computed.fill(false);
accumulatorSmall.computed.fill(false);
return diff;
}
};
struct AccumulatorState: public Accumulator, Dirties {};
class AccumulatorStack {
public:
static constexpr std::size_t MaxSize = MAX_PLY + 1;
static constexpr usize MaxSize = MAX_PLY + 1;
template<typename T>
[[nodiscard]] const AccumulatorState<T>& latest() const noexcept;
[[nodiscard]] const AccumulatorState& latest() const noexcept;
void reset() noexcept;
std::pair<DirtyPiece&, DirtyThreats&> push() noexcept;
void pop() noexcept;
void reset() noexcept;
Dirties& push() noexcept;
void pop() noexcept;
template<IndexType Dimensions>
void evaluate(const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer,
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
void evaluate(const Position& pos,
const FeatureTransformer& featureTransformer,
// Silence spurious warning on GCC 10
[[maybe_unused]] AccumulatorCaches& cache) noexcept;
private:
template<typename T>
[[nodiscard]] AccumulatorState<T>& mut_latest() noexcept;
[[nodiscard]] AccumulatorState& mut_latest() noexcept;
template<typename T>
[[nodiscard]] const std::array<AccumulatorState<T>, MaxSize>& accumulators() const noexcept;
void evaluate_side(Color perspective,
const Position& pos,
const FeatureTransformer& featureTransformer,
// Silence spurious warning on GCC 10
[[maybe_unused]] AccumulatorCaches& cache) noexcept;
template<typename T>
[[nodiscard]] std::array<AccumulatorState<T>, MaxSize>& mut_accumulators() noexcept;
[[nodiscard]] usize find_last_usable_accumulator(Color perspective) const noexcept;
template<typename FeatureSet, IndexType Dimensions>
void evaluate_side(Color perspective,
const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer,
AccumulatorCaches::Cache<Dimensions>& cache) noexcept;
void forward_update_incremental(Color perspective,
const Position& pos,
const FeatureTransformer& featureTransformer,
const usize begin) noexcept;
template<typename FeatureSet, IndexType Dimensions>
[[nodiscard]] std::size_t find_last_usable_accumulator(Color perspective) const noexcept;
void backward_update_incremental(Color perspective,
const Position& pos,
const FeatureTransformer& featureTransformer,
const usize end) noexcept;
template<typename FeatureSet, IndexType Dimensions>
void forward_update_incremental(Color perspective,
const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer,
const std::size_t begin) noexcept;
template<typename FeatureSet, IndexType Dimensions>
void backward_update_incremental(Color perspective,
const Position& pos,
const FeatureTransformer<Dimensions>& featureTransformer,
const std::size_t end) noexcept;
std::array<AccumulatorState<PSQFeatureSet>, MaxSize> psq_accumulators;
std::array<AccumulatorState<ThreatFeatureSet>, MaxSize> threat_accumulators;
std::size_t size = 1;
std::array<AccumulatorState, MaxSize> accumulators;
usize size = 1;
};
} // namespace Stockfish::Eval::NNUE
+61 -47
View File
@@ -22,31 +22,29 @@
#define NNUE_ARCHITECTURE_H_INCLUDED
#include <cstdint>
#include <cstring>
#include <iosfwd>
#include "features/half_ka_v2_hm.h"
#include "features/full_threats.h"
#include "features/pp_3wide.h"
#include "layers/affine_transform.h"
#include "layers/affine_transform_sparse_input.h"
#include "layers/clipped_relu.h"
#include "layers/sqr_clipped_relu.h"
#include "nnue_common.h"
#include "nnz_helper.h"
namespace Stockfish::Eval::NNUE {
// Input features used in evaluation function
using ThreatFeatureSet = Features::FullThreats;
using PairFeatureSet = Features::PP_3Wide;
using PSQFeatureSet = Features::HalfKAv2_hm;
// Number of input feature dimensions after conversion
constexpr IndexType TransformedFeatureDimensionsBig = 1024;
constexpr int L2Big = 15;
constexpr int L3Big = 32;
constexpr IndexType TransformedFeatureDimensionsSmall = 128;
constexpr int L2Small = 15;
constexpr int L3Small = 32;
constexpr IndexType L1 = 1024;
constexpr int L2 = 32;
constexpr int L3 = 32;
constexpr IndexType PSQTBuckets = 8;
constexpr IndexType LayerStacks = 8;
@@ -57,26 +55,33 @@ constexpr IndexType LayerStacks = 8;
static_assert(PSQTBuckets % 8 == 0,
"Per feature PSQT values cannot be processed at granularity lower than 8 at a time.");
template<IndexType L1, int L2, int L3>
struct NetworkArchitecture {
static constexpr IndexType TransformedFeatureDimensions = L1;
static constexpr int FC_0_OUTPUTS = L2;
static constexpr int FC_1_OUTPUTS = L3;
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
static constexpr bool UsePairedActivations = true;
#else
static constexpr bool UsePairedActivations = false;
#endif
Layers::AffineTransformSparseInput<TransformedFeatureDimensions, FC_0_OUTPUTS + 1> fc_0;
Layers::SqrClippedReLU<FC_0_OUTPUTS + 1> ac_sqr_0;
Layers::ClippedReLU<FC_0_OUTPUTS + 1> ac_0;
Layers::AffineTransform<FC_0_OUTPUTS * 2, FC_1_OUTPUTS> fc_1;
Layers::ClippedReLU<FC_1_OUTPUTS> ac_1;
Layers::AffineTransform<FC_1_OUTPUTS, 1> fc_2;
Layers::AffineTransformSparseInput<TransformedFeatureDimensions, FC_0_OUTPUTS> fc_0;
Layers::SqrClippedReLU<FC_0_OUTPUTS, WeightScaleBits + 1> ac_sqr_0;
Layers::ClippedReLU<FC_0_OUTPUTS, WeightScaleBits + 1> ac_0;
Layers::AffineTransform<FC_0_OUTPUTS * 2, FC_1_OUTPUTS, UsePairedActivations> fc_1;
Layers::SqrClippedReLU<FC_1_OUTPUTS, WeightScaleBits> ac_sqr_1;
Layers::ClippedReLU<FC_1_OUTPUTS, WeightScaleBits> ac_1;
Layers::AffineTransform<FC_0_OUTPUTS * 2 + FC_1_OUTPUTS * 2, 1, UsePairedActivations> fc_2;
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value() {
static constexpr u32 get_hash_value() {
// input slice hash
std::uint32_t hashValue = 0xEC42E90Du;
u32 hashValue = 0xEC42E90Du;
hashValue ^= TransformedFeatureDimensions * 2;
hashValue = decltype(fc_0)::get_hash_value(hashValue);
// TODO: consider including hash value of ac_sqr_0 in the overall hash value.
// For now omitted on purpose because hash value is not written by trainer (yet)
hashValue = decltype(ac_0)::get_hash_value(hashValue);
hashValue = decltype(fc_1)::get_hash_value(hashValue);
hashValue = decltype(ac_1)::get_hash_value(hashValue);
@@ -99,52 +104,61 @@ struct NetworkArchitecture {
&& fc_2.write_parameters(stream);
}
std::int32_t propagate(const TransformedFeatureType* transformedFeatures) const {
i32 propagate(const TransformedFeatureType* transformedFeatures,
const NNZInfo<L1>& nnzInfo) const {
struct alignas(CacheLineSize) Buffer {
alignas(CacheLineSize) typename decltype(fc_0)::OutputBuffer fc_0_out;
alignas(CacheLineSize) typename decltype(ac_sqr_0)::OutputType
ac_sqr_0_out[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2, 32)];
alignas(CacheLineSize) typename decltype(ac_0)::OutputBuffer ac_0_out;
concat_buffer[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2 + FC_1_OUTPUTS * 2, 32)];
alignas(CacheLineSize) typename decltype(fc_1)::OutputBuffer fc_1_out;
alignas(CacheLineSize) typename decltype(ac_1)::OutputBuffer ac_1_out;
alignas(CacheLineSize) typename decltype(fc_2)::OutputBuffer fc_2_out;
Buffer() { std::memset(this, 0, sizeof(*this)); }
};
#if defined(__clang__) && (__APPLE__)
// workaround for a bug reported with xcode 12
static thread_local auto tlsBuffer = std::make_unique<Buffer>();
// Access TLS only once, cache result.
Buffer& buffer = *tlsBuffer;
Buffer buffer;
fc_0.propagate(transformedFeatures, buffer.fc_0_out, nnzInfo);
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
ac_sqr_0.propagate_pair(buffer.fc_0_out, buffer.concat_buffer,
buffer.concat_buffer + FC_0_OUTPUTS);
#else
alignas(CacheLineSize) static thread_local Buffer buffer;
ac_sqr_0.propagate(buffer.fc_0_out, buffer.concat_buffer);
ac_0.propagate(buffer.fc_0_out, buffer.concat_buffer + FC_0_OUTPUTS);
#endif
fc_0.propagate(transformedFeatures, buffer.fc_0_out);
ac_sqr_0.propagate(buffer.fc_0_out, buffer.ac_sqr_0_out);
ac_0.propagate(buffer.fc_0_out, buffer.ac_0_out);
std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out,
FC_0_OUTPUTS * sizeof(typename decltype(ac_0)::OutputType));
fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out);
ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out);
fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out);
fc_1.propagate(buffer.concat_buffer, buffer.fc_1_out);
#if defined(USE_AVX2_PAIR_ACTIVATIONS)
ac_sqr_1.propagate_pair(buffer.fc_1_out, buffer.concat_buffer + FC_0_OUTPUTS * 2,
buffer.concat_buffer + FC_0_OUTPUTS * 2 + FC_1_OUTPUTS);
#else
ac_sqr_1.propagate(buffer.fc_1_out, buffer.concat_buffer + FC_0_OUTPUTS * 2);
ac_1.propagate(buffer.fc_1_out, buffer.concat_buffer + FC_0_OUTPUTS * 2 + FC_1_OUTPUTS);
#endif
// buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in
fc_2.propagate(buffer.concat_buffer, buffer.fc_2_out);
static_assert(FC_0_OUTPUTS >= 2);
i32 fwdOut = buffer.fc_2_out[0];
i32 skip_0 = buffer.fc_0_out[FC_0_OUTPUTS - 2] - buffer.fc_0_out[FC_0_OUTPUTS - 1];
fwdOut += skip_0;
// fwdOut is such that 1.0 is equal to HiddenOneVal*(1<<WeightScaleBits)*2 in
// quantized form, but we want 1.0 to be equal to 600*OutputScale
std::int32_t fwdOut =
(buffer.fc_0_out[FC_0_OUTPUTS]) * (600 * OutputScale) / (127 * (1 << WeightScaleBits));
std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
// to make overflow impossible we cast to int64_t
constexpr i64 multiplier = 600 * OutputScale;
constexpr i64 denominator =
static_cast<i64>(HiddenOneVal) * static_cast<i64>(1U << WeightScaleBits) * 2;
i32 outputValue = static_cast<i32>((static_cast<i64>(fwdOut) * multiplier) / denominator);
return outputValue;
}
std::size_t get_content_hash() const {
std::size_t h = 0;
usize get_content_hash() const {
usize h = 0;
hash_combine(h, fc_0.get_content_hash());
hash_combine(h, ac_sqr_0.get_content_hash());
hash_combine(h, ac_0.get_content_hash());
hash_combine(h, fc_1.get_content_hash());
// hash_combine(h, ac_sqr_1.get_content_hash()); TODO
hash_combine(h, ac_1.get_content_hash());
hash_combine(h, fc_2.get_content_hash());
hash_combine(h, get_hash_value());
@@ -154,10 +168,10 @@ struct NetworkArchitecture {
} // namespace Stockfish::Eval::NNUE
template<Stockfish::Eval::NNUE::IndexType L1, int L2, int L3>
struct std::hash<Stockfish::Eval::NNUE::NetworkArchitecture<L1, L2, L3>> {
std::size_t
operator()(const Stockfish::Eval::NNUE::NetworkArchitecture<L1, L2, L3>& arch) const noexcept {
template<>
struct std::hash<Stockfish::Eval::NNUE::NetworkArchitecture> {
Stockfish::usize
operator()(const Stockfish::Eval::NNUE::NetworkArchitecture& arch) const noexcept {
return arch.get_content_hash();
}
};
+83 -49
View File
@@ -42,46 +42,61 @@
#elif defined(USE_SSE2)
#include <emmintrin.h>
#elif defined(USE_LASX)
#include <lasxintrin.h>
#include <lsxintrin.h>
#elif defined(USE_LSX)
#include <lsxintrin.h>
#elif defined(USE_NEON)
#include <arm_neon.h>
#endif
namespace Stockfish::Eval::NNUE {
using BiasType = std::int16_t;
using ThreatWeightType = std::int8_t;
using WeightType = std::int16_t;
using PSQTWeightType = std::int32_t;
using IndexType = std::uint32_t;
using BiasType = i16;
using ThreatWeightType = i8;
using WeightType = i16;
using PSQTWeightType = i32;
using IndexType = u32;
// Version of the evaluation file
constexpr std::uint32_t Version = 0x7AF32F20u;
constexpr u32 Version = 0x6A448AFAu;
// Constant used in evaluation value calculation
constexpr int OutputScale = 16;
constexpr int WeightScaleBits = 6;
constexpr int FtMaxVal = 255;
constexpr int HiddenOneVal = 128;
// Size of cache line (in bytes)
constexpr std::size_t CacheLineSize = 64;
constexpr usize CacheLineSize = 64;
constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128";
constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;
constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128";
constexpr const usize Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;
// SIMD width (in bytes)
#if defined(USE_AVX2)
constexpr std::size_t SimdWidth = 32;
constexpr usize SimdWidth = 32;
#elif defined(USE_LASX)
constexpr usize SimdWidth = 32;
#elif defined(USE_SSE2)
constexpr std::size_t SimdWidth = 16;
constexpr usize SimdWidth = 16;
#elif defined(USE_NEON)
constexpr std::size_t SimdWidth = 16;
constexpr usize SimdWidth = 16;
#elif defined(USE_LSX)
constexpr usize SimdWidth = 16;
#endif
constexpr std::size_t MaxSimdWidth = 32;
constexpr usize MaxSimdWidth = 32;
// Type of input feature after conversion
using TransformedFeatureType = std::uint8_t;
using TransformedFeatureType = u8;
// Round n up to be a multiple of base
template<typename IntType>
@@ -101,11 +116,11 @@ inline IntType read_little_endian(std::istream& stream) {
stream.read(reinterpret_cast<char*>(&result), sizeof(IntType));
else
{
std::uint8_t u[sizeof(IntType)];
u8 u[sizeof(IntType)];
std::make_unsigned_t<IntType> v = 0;
stream.read(reinterpret_cast<char*>(u), sizeof(IntType));
for (std::size_t i = 0; i < sizeof(IntType); ++i)
for (usize i = 0; i < sizeof(IntType); ++i)
v = (v << 8) | u[sizeof(IntType) - i - 1];
std::memcpy(&result, &v, sizeof(IntType));
@@ -126,20 +141,20 @@ inline void write_little_endian(std::ostream& stream, IntType value) {
stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
else
{
std::uint8_t u[sizeof(IntType)];
u8 u[sizeof(IntType)];
std::make_unsigned_t<IntType> v = value;
std::size_t i = 0;
usize i = 0;
// if constexpr to silence the warning about shift by 8
if constexpr (sizeof(IntType) > 1)
{
for (; i + 1 < sizeof(IntType); ++i)
{
u[i] = std::uint8_t(v);
u[i] = u8(v);
v >>= 8;
}
}
u[i] = std::uint8_t(v);
u[i] = u8(v);
stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
}
@@ -149,11 +164,11 @@ inline void write_little_endian(std::ostream& stream, IntType value) {
// Read integers in bulk from a little-endian stream.
// This reads N integers from stream s and puts them in array out.
template<typename IntType>
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
inline void read_little_endian(std::istream& stream, IntType* out, usize count) {
if (IsLittleEndian)
stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
else
for (std::size_t i = 0; i < count; ++i)
for (usize i = 0; i < count; ++i)
out[i] = read_little_endian<IntType>(stream);
}
@@ -161,39 +176,36 @@ inline void read_little_endian(std::istream& stream, IntType* out, std::size_t c
// Write integers in bulk to a little-endian stream.
// This takes N integers from array values and writes them on stream s.
template<typename IntType>
inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
inline void write_little_endian(std::ostream& stream, const IntType* values, usize count) {
if (IsLittleEndian)
stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
else
for (std::size_t i = 0; i < count; ++i)
for (usize i = 0; i < count; ++i)
write_little_endian<IntType>(stream, values[i]);
}
// Read N signed integers from the stream s, putting them in the array out.
// The stream is assumed to be compressed using the signed LEB128 format.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template<typename BufType, typename IntType, std::size_t Count>
inline void read_leb_128_detail(std::istream& stream,
std::array<IntType, Count>& out,
std::uint32_t& bytes_left,
BufType& buf,
std::uint32_t& buf_pos) {
template<typename BufType, typename IntType>
inline void read_leb_128_detail(
std::istream& stream, IntType* out, usize Count, u32& bytes_left, BufType& buf, u32& buf_pos) {
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
static_assert(sizeof(IntType) <= 4, "Not implemented for types larger than 32 bit");
IntType result = 0;
size_t shift = 0, i = 0;
usize shift = 0, i = 0;
while (i < Count)
{
if (buf_pos == buf.size())
{
stream.read(reinterpret_cast<char*>(buf.data()),
std::min(std::size_t(bytes_left), buf.size()));
std::min(usize(bytes_left), buf.size()));
buf_pos = 0;
}
std::uint8_t byte = buf[buf_pos++];
u8 byte = buf[buf_pos++];
--bytes_left;
result |= (byte & 0x7f) << (shift % 32);
shift += 7;
@@ -214,11 +226,28 @@ inline void read_leb_128(std::istream& stream, Arrays&... outs) {
stream.read(leb128MagicString, Leb128MagicStringSize);
assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);
auto bytes_left = read_little_endian<std::uint32_t>(stream);
std::array<std::uint8_t, 8192> buf;
std::uint32_t buf_pos = buf.size();
auto bytes_left = read_little_endian<u32>(stream);
std::array<u8, 8192> buf;
u32 buf_pos = u32(buf.size());
(read_leb_128_detail(stream, outs, bytes_left, buf, buf_pos), ...);
(read_leb_128_detail(stream, outs.data(), outs.size(), bytes_left, buf, buf_pos), ...);
assert(bytes_left == 0);
}
template<typename IntType>
inline void read_leb_128(std::istream& stream, IntType* out, usize expected) {
// Check the presence of our LEB128 magic string
char leb128MagicString[Leb128MagicStringSize];
stream.read(leb128MagicString, Leb128MagicStringSize);
assert(strncmp(Leb128MagicString, leb128MagicString, Leb128MagicStringSize) == 0);
auto bytes_left = read_little_endian<u32>(stream);
std::array<u8, 8192> buf;
u32 buf_pos = u32(buf.size());
read_leb_128_detail(stream, out, expected, bytes_left, buf, buf_pos);
assert(bytes_left == 0);
}
@@ -228,19 +257,19 @@ inline void read_leb_128(std::istream& stream, Arrays&... outs) {
// This takes N integers from array values, compresses them with
// the LEB128 algorithm and writes the result on the stream s.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template<typename IntType, std::size_t Count>
inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>& values) {
template<typename IntType>
inline void write_leb_128(std::ostream& stream, const IntType* values, usize Count) {
// Write our LEB128 magic string
stream.write(Leb128MagicString, Leb128MagicStringSize);
static_assert(std::is_signed_v<IntType>, "Not implemented for unsigned types");
std::uint32_t byte_count = 0;
for (std::size_t i = 0; i < Count; ++i)
u32 byte_count = 0;
for (usize i = 0; i < Count; ++i)
{
IntType value = values[i];
std::uint8_t byte;
IntType value = values[i];
u8 byte;
do
{
byte = value & 0x7f;
@@ -251,9 +280,9 @@ inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>
write_little_endian(stream, byte_count);
const std::uint32_t BUF_SIZE = 4096;
std::uint8_t buf[BUF_SIZE];
std::uint32_t buf_pos = 0;
const u32 BUF_SIZE = 4096;
u8 buf[BUF_SIZE];
u32 buf_pos = 0;
auto flush = [&]() {
if (buf_pos > 0)
@@ -263,18 +292,18 @@ inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>
}
};
auto write = [&](std::uint8_t b) {
auto write = [&](u8 b) {
buf[buf_pos++] = b;
if (buf_pos == BUF_SIZE)
flush();
};
for (std::size_t i = 0; i < Count; ++i)
for (usize i = 0; i < Count; ++i)
{
IntType value = values[i];
while (true)
{
std::uint8_t byte = value & 0x7f;
u8 byte = value & 0x7f;
value >>= 7;
if ((byte & 0x40) == 0 ? value == 0 : value == -1)
{
@@ -288,6 +317,11 @@ inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>
flush();
}
template<typename IntType, usize Count>
inline void write_leb_128(std::ostream& stream, const std::array<IntType, Count>& values) {
write_leb_128(stream, values.data(), Count);
}
} // namespace Stockfish::Eval::NNUE
#endif // #ifndef NNUE_COMMON_H_INCLUDED
+194 -173
View File
@@ -37,35 +37,34 @@
namespace Stockfish::Eval::NNUE {
// Returns the inverse of a permutation
template<std::size_t Len>
constexpr std::array<std::size_t, Len>
invert_permutation(const std::array<std::size_t, Len>& order) {
std::array<std::size_t, Len> inverse{};
for (std::size_t i = 0; i < order.size(); i++)
template<usize Len>
constexpr std::array<usize, Len> invert_permutation(const std::array<usize, Len>& order) {
std::array<usize, Len> inverse{};
for (usize i = 0; i < order.size(); i++)
inverse[order[i]] = i;
return inverse;
}
// Divide a byte region of size TotalSize to chunks of size
// BlockSize, and permute the blocks by a given order
template<std::size_t BlockSize, typename T, std::size_t N, std::size_t OrderSize>
void permute(std::array<T, N>& data, const std::array<std::size_t, OrderSize>& order) {
constexpr std::size_t TotalSize = N * sizeof(T);
template<usize BlockSize, typename T, usize N, usize OrderSize>
void permute(std::array<T, N>& data, const std::array<usize, OrderSize>& order) {
constexpr usize TotalSize = N * sizeof(T);
static_assert(TotalSize % (BlockSize * OrderSize) == 0,
"ChunkSize * OrderSize must perfectly divide TotalSize");
constexpr std::size_t ProcessChunkSize = BlockSize * OrderSize;
constexpr usize ProcessChunkSize = BlockSize * OrderSize;
std::array<std::byte, ProcessChunkSize> buffer{};
std::byte* const bytes = reinterpret_cast<std::byte*>(data.data());
for (std::size_t i = 0; i < TotalSize; i += ProcessChunkSize)
for (usize i = 0; i < TotalSize; i += ProcessChunkSize)
{
std::byte* const values = &bytes[i];
for (std::size_t j = 0; j < OrderSize; j++)
for (usize j = 0; j < OrderSize; j++)
{
auto* const buffer_chunk = &buffer[j * BlockSize];
auto* const value_chunk = &values[order[j] * BlockSize];
@@ -78,38 +77,35 @@ void permute(std::array<T, N>& data, const std::array<std::size_t, OrderSize>& o
}
// Input feature converter
template<IndexType TransformedFeatureDimensions>
class FeatureTransformer {
static constexpr bool UseThreats =
(TransformedFeatureDimensions == TransformedFeatureDimensionsBig);
// Number of output dimensions for one side
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
static constexpr IndexType HalfDimensions = L1;
public:
// Output type
using OutputType = TransformedFeatureType;
// Number of input/output dimensions
static constexpr IndexType InputDimensions = PSQFeatureSet::Dimensions;
static constexpr IndexType ThreatInputDimensions = ThreatFeatureSet::Dimensions;
static constexpr IndexType TotalInputDimensions =
InputDimensions + (UseThreats ? ThreatInputDimensions : 0);
static constexpr IndexType PairInputDimensions = PairFeatureSet::Dimensions;
static constexpr IndexType InputDimensions =
PSQFeatureSet::Dimensions + ThreatInputDimensions + PairInputDimensions;
static constexpr IndexType OutputDimensions = HalfDimensions;
// Size of forward propagation buffer
static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType);
static constexpr usize BufferSize = OutputDimensions * sizeof(OutputType);
// Store the order by which 128-bit blocks of a 1024-bit data must
// be permuted so that calling packus on adjacent vectors of 16-bit
// integers loaded from the data results in the pre-permutation order
static constexpr auto PackusEpi16Order = []() -> std::array<std::size_t, 8> {
static constexpr auto PackusEpi16Order = []() -> std::array<usize, 8> {
#if defined(USE_AVX512)
// _mm512_packus_epi16 after permutation:
// | 0 | 2 | 4 | 6 | // Vector 0
// | 1 | 3 | 5 | 7 | // Vector 1
// | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | // Packed Result
return {0, 2, 4, 6, 1, 3, 5, 7};
#elif defined(USE_AVX2)
#elif defined(USE_AVX2) || defined(USE_LASX)
// _mm256_packus_epi16 after permutation:
// | 0 | 2 | | 4 | 6 | // Vector 0, 2
// | 1 | 3 | | 5 | 7 | // Vector 1, 3
@@ -122,9 +118,20 @@ class FeatureTransformer {
static constexpr auto InversePackusEpi16Order = invert_permutation(PackusEpi16Order);
static constexpr u32 combine_hash(std::initializer_list<u32> hashes) {
u32 hash = 0;
for (const auto component_hash : hashes)
{
hash = (hash << 1) | (hash >> 31);
hash ^= component_hash;
}
return hash;
}
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value() {
return (UseThreats ? ThreatFeatureSet::HashValue : PSQFeatureSet::HashValue)
static constexpr u32 get_hash_value() {
return combine_hash(
{ThreatFeatureSet::HashValue, PairFeatureSet::HashValue, PSQFeatureSet::HashValue})
^ (OutputDimensions * 2);
}
@@ -132,48 +139,48 @@ class FeatureTransformer {
permute<16>(biases, PackusEpi16Order);
permute<16>(weights, PackusEpi16Order);
if constexpr (UseThreats)
permute<8>(threatWeights, PackusEpi16Order);
permute<8>(threatAndPpWeights, PackusEpi16Order);
}
void unpermute_weights() {
permute<16>(biases, InversePackusEpi16Order);
permute<16>(weights, InversePackusEpi16Order);
if constexpr (UseThreats)
permute<8>(threatWeights, InversePackusEpi16Order);
permute<8>(threatAndPpWeights, InversePackusEpi16Order);
}
inline void scale_weights(bool read) {
for (auto& w : weights)
w = read ? w * 2 : w / 2;
for (auto& b : biases)
b = read ? b * 2 : b / 2;
auto threatWeights() { return threatAndPpWeights.data(); }
auto threatWeights() const { return threatAndPpWeights.data(); }
auto ppWeights() { return &threatAndPpWeights[ThreatFeatureSet::Dimensions * HalfDimensions]; }
auto ppWeights() const {
return &threatAndPpWeights[ThreatFeatureSet::Dimensions * HalfDimensions];
}
auto threatPsqtWeights() { return threatAndPpPsqtWeights.data(); }
auto threatPsqtWeights() const { return threatAndPpPsqtWeights.data(); }
auto ppPsqtWeights() {
return &threatAndPpPsqtWeights[ThreatFeatureSet::Dimensions * PSQTBuckets];
}
auto ppPsqtWeights() const {
return &threatAndPpPsqtWeights[ThreatFeatureSet::Dimensions * PSQTBuckets];
}
// Read network parameters
bool read_parameters(std::istream& stream) {
read_leb_128(stream, biases);
if constexpr (UseThreats)
{
read_little_endian<ThreatWeightType>(stream, threatWeights.data(),
ThreatInputDimensions * HalfDimensions);
read_leb_128(stream, weights);
read_little_endian<ThreatWeightType>(stream, threatWeights(),
ThreatInputDimensions * HalfDimensions);
read_leb_128(stream, threatPsqtWeights(), ThreatFeatureSet::Dimensions * PSQTBuckets);
read_little_endian<ThreatWeightType>(stream, ppWeights(),
PairInputDimensions * HalfDimensions);
read_leb_128(stream, ppPsqtWeights(), PairFeatureSet::Dimensions * PSQTBuckets);
read_leb_128(stream, threatPsqtWeights, psqtWeights);
}
else
{
read_leb_128(stream, weights);
read_leb_128(stream, psqtWeights);
}
read_leb_128(stream, weights);
read_leb_128(stream, psqtWeights);
permute_weights();
if constexpr (!UseThreats)
scale_weights(true);
return !stream.fail();
}
@@ -183,92 +190,74 @@ class FeatureTransformer {
copy->unpermute_weights();
if constexpr (!UseThreats)
copy->scale_weights(false);
write_leb_128<BiasType>(stream, copy->biases);
if constexpr (UseThreats)
{
write_little_endian<ThreatWeightType>(stream, copy->threatWeights.data(),
ThreatInputDimensions * HalfDimensions);
write_leb_128<WeightType>(stream, copy->weights);
auto combinedPsqtWeights =
std::make_unique<std::array<PSQTWeightType, TotalInputDimensions * PSQTBuckets>>();
write_little_endian<ThreatWeightType>(stream, copy->threatWeights(),
ThreatInputDimensions * HalfDimensions);
write_leb_128<PSQTWeightType>(stream, copy->threatPsqtWeights(),
ThreatFeatureSet::Dimensions * PSQTBuckets);
write_little_endian<ThreatWeightType>(stream, copy->ppWeights(),
PairInputDimensions * HalfDimensions);
write_leb_128<PSQTWeightType>(stream, copy->ppPsqtWeights(),
PairFeatureSet::Dimensions * PSQTBuckets);
std::copy(std::begin(copy->threatPsqtWeights),
std::begin(copy->threatPsqtWeights) + ThreatInputDimensions * PSQTBuckets,
combinedPsqtWeights->begin());
std::copy(std::begin(copy->psqtWeights),
std::begin(copy->psqtWeights) + InputDimensions * PSQTBuckets,
combinedPsqtWeights->begin() + ThreatInputDimensions * PSQTBuckets);
write_leb_128<PSQTWeightType>(stream, *combinedPsqtWeights);
}
else
{
write_leb_128<WeightType>(stream, copy->weights);
write_leb_128<PSQTWeightType>(stream, copy->psqtWeights);
}
write_leb_128<WeightType>(stream, copy->weights);
write_leb_128<PSQTWeightType>(stream, copy->psqtWeights);
return !stream.fail();
}
std::size_t get_content_hash() const {
std::size_t h = 0;
usize get_content_hash() const {
usize h = 0;
hash_combine(h, get_raw_data_hash(biases));
hash_combine(h, get_raw_data_hash(weights));
hash_combine(h, get_raw_data_hash(psqtWeights));
hash_combine(h, get_raw_data_hash(threatAndPpWeights));
hash_combine(h, get_raw_data_hash(threatAndPpPsqtWeights));
hash_combine(h, get_hash_value());
return h;
}
// Convert input features
std::int32_t transform(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches::Cache<HalfDimensions>& cache,
OutputType* output,
int bucket) const {
i32 transform(const Position& pos,
AccumulatorStack& accumulatorStack,
AccumulatorCaches& cache,
OutputType* output,
int bucket,
NNZInfo<OutputDimensions>& nnzInfo) const {
using namespace SIMD;
accumulatorStack.evaluate(pos, *this, cache);
const auto& accumulatorState = accumulatorStack.latest<PSQFeatureSet>();
const auto& threatAccumulatorState = accumulatorStack.latest<ThreatFeatureSet>();
const auto& accumulatorState = accumulatorStack.latest();
const Color perspectives[2] = {pos.side_to_move(), ~pos.side_to_move()};
const auto& psqtAccumulation = (accumulatorState.acc<HalfDimensions>()).psqtAccumulation;
auto psqt =
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket]);
const auto& psqtAccumulation = accumulatorState.psqtAccumulation;
const auto psqt =
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket])
/ 2;
if constexpr (UseThreats)
{
const auto& threatPsqtAccumulation =
(threatAccumulatorState.acc<HalfDimensions>()).psqtAccumulation;
psqt = (psqt + threatPsqtAccumulation[perspectives[0]][bucket]
- threatPsqtAccumulation[perspectives[1]][bucket])
/ 2;
}
else
psqt /= 2;
const auto& accumulation = (accumulatorState.acc<HalfDimensions>()).accumulation;
const auto& threatAccumulation =
(threatAccumulatorState.acc<HalfDimensions>()).accumulation;
const auto& accumulation = accumulatorState.accumulation;
for (IndexType p = 0; p < 2; ++p)
{
const IndexType offset = (HalfDimensions / 2) * p;
[[maybe_unused]] auto cursor = nnzInfo.make_cursor(p);
#if defined(VECTOR)
constexpr IndexType OutputChunkSize = MaxChunkSize;
static_assert((HalfDimensions / 2) % OutputChunkSize == 0);
constexpr IndexType NumOutputChunks = HalfDimensions / 2 / OutputChunkSize;
const vec_t Zero = vec_zero();
const vec_t One = vec_set_16(UseThreats ? 255 : 127 * 2);
[[maybe_unused]] const vec_t Zero = vec_zero();
[[maybe_unused]] const vec_t FtMax = vec_set_16(FtMaxVal);
[[maybe_unused]] constexpr int shift = 7;
const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
const vec_t* in1 =
@@ -291,7 +280,7 @@ class FeatureTransformer {
// means our pairwise product is zero. If we perform packus and
// remove the lower-side clip for the second element, then our
// product before packus will be negative, and is zeroed on pack.
// The two operation produce equivalent results, but the second
// The two operations produce equivalent results, but the second
// one (using packus) saves one max operation per pair.
// But here we run into a problem: mullo does not preserve the
@@ -322,60 +311,97 @@ class FeatureTransformer {
// 8 bits. Shifting it by 7 bits left will no longer occupy the
// signed bit, so we are safe.
// Note that on NEON processors, we shift left by 6 instead
// because the instruction "vqdmulhq_s16" also doubles the
// return value after the multiplication, adding an extra shift
// to the left by 1, so we compensate by shifting less before
// the multiplication.
for (IndexType j = 0; j < NumOutputChunks; j += 2)
{
vec_t packed[2];
for (IndexType k = 0; k < 2; ++k)
{
const IndexType i = (j + k) * 2;
constexpr int shift =
#if defined(USE_SSE2)
7;
vec_t acc0a = in0[i + 0];
vec_t acc0b = in0[i + 1];
vec_t acc1a = in1[i + 0];
vec_t acc1b = in1[i + 1];
static_assert(FtMaxVal == 255);
#if defined(USE_NEON)
uint16x8_t mul0 = vmull_u8(vqmovun_s16(acc0a), vqmovun_s16(acc1a));
uint16x8_t mul1 = vmull_u8(vqmovun_s16(acc0b), vqmovun_s16(acc1b));
uint8x16x2_t uzp =
vuzpq_u8(vreinterpretq_u8_u16(mul0), vreinterpretq_u8_u16(mul1));
uint8x16_t pab = vshrq_n_u8(uzp.val[1], 1);
vec_t result = reinterpret_cast<vec_t>(pab);
#elif defined(USE_LSX) || defined(USE_LASX)
vec_t pa = vec_packus_16(acc0a, acc0b);
vec_t pb = vec_packus_16(acc1a, acc1b);
vec_t hi = vec_mulhi_8(pa, pb);
vec_t result = vec_srli_8(hi, 1);
#elif defined(__wasm__)
// _mm_mulhi_epi16 is lowered to 32-bit multiplies, so we take
// a similar approach as the NEON path.
vec_t mul0 = vec_packus_16(acc0a, acc0b);
vec_t mul1 = vec_packus_16(acc1a, acc1b);
vec_t low = wasm_u16x8_extmul_low_u8x16(mul0, mul1);
vec_t hi = wasm_u16x8_extmul_high_u8x16(mul0, mul1);
// equivalent to vuzp2_u8
vec_t merged = wasm_i8x16_shuffle(low, hi, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19,
21, 23, 25, 27, 29, 31);
vec_t result = wasm_u8x16_shr(merged, 1);
#else
6;
vec_t sum0a = vec_slli_16(vec_max_16(vec_min_16(acc0a, FtMax), Zero), shift);
vec_t sum0b = vec_slli_16(vec_max_16(vec_min_16(acc0b, FtMax), Zero), shift);
vec_t sum1a = vec_min_16(acc1a, FtMax);
vec_t sum1b = vec_min_16(acc1b, FtMax);
vec_t pa = vec_mulhi_16(sum0a, sum1a);
vec_t pb = vec_mulhi_16(sum0b, sum1b);
vec_t result = vec_packus_16(pa, pb);
#endif
if constexpr (UseThreats)
{
const vec_t* tin0 =
reinterpret_cast<const vec_t*>(&(threatAccumulation[perspectives[p]][0]));
const vec_t* tin1 = reinterpret_cast<const vec_t*>(
&(threatAccumulation[perspectives[p]][HalfDimensions / 2]));
for (IndexType j = 0; j < NumOutputChunks; ++j)
{
const vec_t acc0a = vec_add_16(in0[j * 2 + 0], tin0[j * 2 + 0]);
const vec_t acc0b = vec_add_16(in0[j * 2 + 1], tin0[j * 2 + 1]);
const vec_t acc1a = vec_add_16(in1[j * 2 + 0], tin1[j * 2 + 0]);
const vec_t acc1b = vec_add_16(in1[j * 2 + 1], tin1[j * 2 + 1]);
const vec_t sum0a =
vec_slli_16(vec_max_16(vec_min_16(acc0a, One), Zero), shift);
const vec_t sum0b =
vec_slli_16(vec_max_16(vec_min_16(acc0b, One), Zero), shift);
const vec_t sum1a = vec_min_16(acc1a, One);
const vec_t sum1b = vec_min_16(acc1b, One);
const vec_t pa = vec_mulhi_16(sum0a, sum1a);
const vec_t pb = vec_mulhi_16(sum0b, sum1b);
out[j] = vec_packus_16(pa, pb);
packed[k] = out[j + k] = result;
}
cursor.record2(packed[0], packed[1]);
}
else
#elif defined(USE_RVV)
usize j = 0;
while (j < HalfDimensions / 2)
{
for (IndexType j = 0; j < NumOutputChunks; ++j)
{
const vec_t sum0a =
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 0], One), Zero), shift);
const vec_t sum0b =
vec_slli_16(vec_max_16(vec_min_16(in0[j * 2 + 1], One), Zero), shift);
const vec_t sum1a = vec_min_16(in1[j * 2 + 0], One);
const vec_t sum1b = vec_min_16(in1[j * 2 + 1], One);
usize vl = __riscv_vsetvl_e16m8(HalfDimensions / 2 - j);
const vec_t pa = vec_mulhi_16(sum0a, sum1a);
const vec_t pb = vec_mulhi_16(sum0b, sum1b);
vint16m8_t acc0 = __riscv_vle16_v_i16m8(&accumulation[perspectives[p]][j], vl);
vint16m8_t acc1 =
__riscv_vle16_v_i16m8(&accumulation[perspectives[p]][j + HalfDimensions / 2], vl);
out[j] = vec_packus_16(pa, pb);
}
acc0 = __riscv_vmax_vx_i16m8(acc0, 0, vl);
acc1 = __riscv_vmax_vx_i16m8(acc1, 0, vl);
vuint8m4_t pa = __riscv_vnclipu_wx_u8m4(__riscv_vreinterpret_v_i16m8_u16m8(acc0), 0,
__RISCV_VXRM_RDN, vl);
vuint8m4_t pb = __riscv_vnclipu_wx_u8m4(__riscv_vreinterpret_v_i16m8_u16m8(acc1), 0,
__RISCV_VXRM_RDN, vl);
vuint8m4_t hi = __riscv_vmulhu_vv_u8m4(pa, pb, vl);
vuint8m4_t result = __riscv_vsrl_vx_u8m4(hi, 1, vl);
__riscv_vse8_v_u8m4(&output[offset + j], result, vl);
j += vl;
// Record NNZ
usize vl32 = vl / 4;
vbool8_t nnzMask =
__riscv_vmsne_vx_u32m4_b8(__riscv_vreinterpret_v_u8m4_u32m4(result), 0, vl32);
__riscv_vsm_v_b8(cursor.out, nnzMask, vl32);
cursor.out += vl32 / 8;
}
#else
@@ -386,19 +412,8 @@ class FeatureTransformer {
BiasType sum1 =
accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
if constexpr (UseThreats)
{
BiasType sum0t = threatAccumulation[static_cast<int>(perspectives[p])][j + 0];
BiasType sum1t =
threatAccumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
sum0 = std::clamp<BiasType>(sum0 + sum0t, 0, 255);
sum1 = std::clamp<BiasType>(sum1 + sum1t, 0, 255);
}
else
{
sum0 = std::clamp<BiasType>(sum0, 0, 127 * 2);
sum1 = std::clamp<BiasType>(sum1, 0, 127 * 2);
}
sum0 = std::clamp<BiasType>(sum0, 0, FtMaxVal);
sum1 = std::clamp<BiasType>(sum1, 0, FtMaxVal);
output[offset + j] = static_cast<OutputType>(unsigned(sum0 * sum1) / 512);
}
@@ -410,24 +425,30 @@ class FeatureTransformer {
} // end of function transform()
alignas(CacheLineSize) std::array<BiasType, HalfDimensions> biases;
alignas(CacheLineSize) std::array<WeightType, HalfDimensions * InputDimensions> weights;
alignas(
CacheLineSize) std::array<WeightType, HalfDimensions * PSQFeatureSet::Dimensions> weights;
// Threats and pawn-pair features are concatenated into one array to allow for a single index to address either.
// The first pawn-pair feature is at index ThreatFeatureSet::Dimensions.
static_assert(PairFeatureSet::IndexBase == ThreatFeatureSet::Dimensions);
alignas(CacheLineSize) std::array<ThreatWeightType,
(ThreatFeatureSet::Dimensions + PairFeatureSet::Dimensions)
* HalfDimensions> threatAndPpWeights;
alignas(CacheLineSize)
std::array<ThreatWeightType,
UseThreats ? HalfDimensions * ThreatInputDimensions : 0> threatWeights;
alignas(CacheLineSize) std::array<PSQTWeightType, InputDimensions * PSQTBuckets> psqtWeights;
alignas(CacheLineSize)
std::array<PSQTWeightType,
UseThreats ? ThreatInputDimensions * PSQTBuckets : 0> threatPsqtWeights;
std::array<PSQTWeightType, PSQTBuckets * PSQFeatureSet::Dimensions> psqtWeights;
// As above
alignas(CacheLineSize) std::array<PSQTWeightType,
(ThreatFeatureSet::Dimensions + PairFeatureSet::Dimensions)
* PSQTBuckets> threatAndPpPsqtWeights;
};
} // namespace Stockfish::Eval::NNUE
template<Stockfish::Eval::NNUE::IndexType TransformedFeatureDimensions>
struct std::hash<Stockfish::Eval::NNUE::FeatureTransformer<TransformedFeatureDimensions>> {
std::size_t
operator()(const Stockfish::Eval::NNUE::FeatureTransformer<TransformedFeatureDimensions>& ft)
const noexcept {
template<>
struct std::hash<Stockfish::Eval::NNUE::FeatureTransformer> {
Stockfish::usize
operator()(const Stockfish::Eval::NNUE::FeatureTransformer& ft) const noexcept {
return ft.get_content_hash();
}
};
+8 -101
View File
@@ -22,15 +22,14 @@
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iosfwd>
#include <iostream>
#include <memory>
#include <sstream>
#include <string_view>
#include <tuple>
#include "../position.h"
#include "../misc.h"
#include "../types.h"
#include "../uci.h"
#include "network.h"
@@ -39,45 +38,7 @@
namespace Stockfish::Eval::NNUE {
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
namespace {
// Converts a Value into (centi)pawns and writes it in a buffer.
// The buffer must have capacity for at least 5 chars.
void format_cp_compact(Value v, char* buffer, const Position& pos) {
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
int cp = std::abs(UCIEngine::to_cp(v, pos));
if (cp >= 10000)
{
buffer[1] = '0' + cp / 10000;
cp %= 10000;
buffer[2] = '0' + cp / 1000;
cp %= 1000;
buffer[3] = '0' + cp / 100;
buffer[4] = ' ';
}
else if (cp >= 1000)
{
buffer[1] = '0' + cp / 1000;
cp %= 1000;
buffer[2] = '0' + cp / 100;
cp %= 100;
buffer[3] = '.';
buffer[4] = '0' + cp / 10;
}
else
{
buffer[1] = '0' + cp / 100;
cp %= 100;
buffer[2] = '.';
buffer[3] = '0' + cp / 10;
cp %= 10;
buffer[4] = '0' + cp / 1;
}
}
// Converts a Value into pawns, always keeping two decimals
@@ -96,77 +57,23 @@ void format_cp_aligned_dot(Value v, std::stringstream& stream, const Position& p
// Returns a string with the value of each piece on a board,
// and a table for (PSQT, Layers) values bucket by bucket.
std::string
trace(Position& pos, const Eval::NNUE::Networks& networks, Eval::NNUE::AccumulatorCaches& caches) {
trace(Position& pos, const Eval::NNUE::Network& network, Eval::NNUE::AccumulatorCaches& caches) {
std::stringstream ss;
char board[3 * 8 + 1][8 * 8 + 2];
std::memset(board, ' ', sizeof(board));
for (int row = 0; row < 3 * 8 + 1; ++row)
board[row][8 * 8 + 1] = '\0';
// A lambda to output one box of the board
auto writeSquare = [&board, &pos](File file, Rank rank, Piece pc, Value value) {
const int x = int(file) * 8;
const int y = (7 - int(rank)) * 3;
for (int i = 1; i < 8; ++i)
board[y][x + i] = board[y + 3][x + i] = '-';
for (int i = 1; i < 3; ++i)
board[y + i][x] = board[y + i][x + 8] = '|';
board[y][x] = board[y][x + 8] = board[y + 3][x + 8] = board[y + 3][x] = '+';
if (pc != NO_PIECE)
board[y + 1][x + 4] = PieceToChar[pc];
if (is_valid(value))
format_cp_compact(value, &board[y + 2][x + 2], pos);
};
auto accumulators = std::make_unique<AccumulatorStack>();
// We estimate the value of each piece by doing a differential evaluation from
// the current base eval, simulating the removal of the piece from its square.
auto [psqt, positional] = networks.big.evaluate(pos, *accumulators, caches.big);
Value base = psqt + positional;
base = pos.side_to_move() == WHITE ? base : -base;
for (File f = FILE_A; f <= FILE_H; ++f)
for (Rank r = RANK_1; r <= RANK_8; ++r)
{
Square sq = make_square(f, r);
Piece pc = pos.piece_on(sq);
Value v = VALUE_NONE;
if (pc != NO_PIECE && type_of(pc) != KING)
{
pos.remove_piece(sq);
accumulators->reset();
std::tie(psqt, positional) = networks.big.evaluate(pos, *accumulators, caches.big);
Value eval = psqt + positional;
eval = pos.side_to_move() == WHITE ? eval : -eval;
v = base - eval;
pos.put_piece(pc, sq);
}
writeSquare(f, r, pc, v);
}
ss << " NNUE derived piece values:\n";
for (int row = 0; row < 3 * 8 + 1; ++row)
ss << board[row] << '\n';
ss << '\n';
accumulators->reset();
auto t = networks.big.trace_evaluate(pos, *accumulators, caches.big);
ss << " NNUE network contributions "
<< (pos.side_to_move() == WHITE ? "(White to move)" : "(Black to move)") << std::endl
auto t = network.trace_evaluate(pos, *accumulators, caches);
ss << "NNUE network contributions (Normalized, "
<< (pos.side_to_move() == WHITE ? "White to move)" : "Black to move)") << std::endl
<< "+------------+------------+------------+------------+\n"
<< "| Bucket | Material | Positional | Total |\n"
<< "| | (PSQT) | (Layers) | |\n"
<< "+------------+------------+------------+------------+\n";
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket)
for (usize bucket = 0; bucket < LayerStacks; ++bucket)
{
ss << "| " << bucket << " " //
<< " | ";
+14 -22
View File
@@ -19,13 +19,15 @@
#ifndef NNUE_MISC_H_INCLUDED
#define NNUE_MISC_H_INCLUDED
#include <cstddef>
#include <memory>
#include <string>
#include <string_view>
#include <optional>
#include <filesystem>
#include "../misc.h"
#include "../types.h"
#include "nnue_architecture.h"
#include "../evaluate.h"
namespace Stockfish {
@@ -33,42 +35,32 @@ class Position;
namespace Eval::NNUE {
// EvalFile uses fixed string types because it's part of the network structure which must be trivial.
// NNUE file metadata uses fixed string types so it stays trivially copyable and cheap to move
// around between the engine and the network loader.
struct EvalFile {
// Default net name, will use one of the EvalFileDefaultName* macros defined
// in evaluate.h
FixedString<256> defaultName;
constexpr static std::string_view defaultName = EvalFileDefaultName;
// Selected net name, either via uci option or default
FixedString<256> current;
std::optional<std::filesystem::path> current;
// Net description extracted from the net file
FixedString<256> netDescription;
std::string netDescription;
};
struct NnueEvalTrace {
static_assert(LayerStacks == PSQTBuckets);
Value psqt[LayerStacks];
Value positional[LayerStacks];
std::size_t correctBucket;
Value psqt[LayerStacks];
Value positional[LayerStacks];
usize correctBucket;
};
struct Networks;
class Network;
struct AccumulatorCaches;
std::string trace(Position& pos, const Networks& networks, AccumulatorCaches& caches);
std::string trace(Position& pos, const Network& network, AccumulatorCaches& caches);
} // namespace Stockfish::Eval::NNUE
} // namespace Stockfish
template<>
struct std::hash<Stockfish::Eval::NNUE::EvalFile> {
std::size_t operator()(const Stockfish::Eval::NNUE::EvalFile& evalFile) const noexcept {
std::size_t h = 0;
Stockfish::hash_combine(h, evalFile.defaultName);
Stockfish::hash_combine(h, evalFile.current);
Stockfish::hash_combine(h, evalFile.netDescription);
return h;
}
};
#endif // #ifndef NNUE_MISC_H_INCLUDED
+168
View File
@@ -0,0 +1,168 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NNZ_HELPER_H_INCLUDED
#define NNZ_HELPER_H_INCLUDED
#include <utility>
#include "nnue_common.h"
#include "simd.h"
namespace Stockfish::Eval::NNUE {
template<usize Dimensions>
struct NNZInfo {
#if defined(USE_AVX512)
unsigned count = 0;
// indices of non-zero chunks
u16 nnz[Dimensions / 4];
#ifdef USE_AVX512ICL
alignas(64) static constexpr auto Indices = []() {
std::array<std::array<u16, 32>, 2> indices{};
for (int i = 0; i < 2; ++i)
{
indices[i] = {0, 1, 2, 3, 16, 17, 18, 19, 4, 5, 6, 7, 20, 21, 22, 23,
8, 9, 10, 11, 24, 25, 26, 27, 12, 13, 14, 15, 28, 29, 30, 31};
for (u16& m : indices[i])
m += u16(i * Dimensions / 8);
}
return indices;
}();
#else
alignas(64) static constexpr auto Indices = []() {
std::array<std::array<u32, 16>, 2> indices{};
for (int i = 0; i < 2; ++i)
{
indices[i] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
for (u32& m : indices[i])
m += i * Dimensions / 8;
}
return indices;
}();
#endif
struct NNZCursor {
NNZInfo& info;
__m512i indices;
unsigned count;
NNZCursor(NNZInfo& info_, bool perspective, unsigned count_) :
info(info_),
count(count_) {
indices = _mm512_load_si512(&Indices[perspective]);
}
void record2(SIMD::vec_t neurons1, SIMD::vec_t neurons2) {
#if defined(USE_AVX512ICL)
const __m512i increment = _mm512_set1_epi16(32);
// Get a bitmask and gather non zero indices
const __m512i inputV01 = _mm512_packs_epi32(neurons1, neurons2);
const __mmask32 nnzMask = _mm512_test_epi16_mask(inputV01, inputV01);
// Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4
__m512i nnzIndices = _mm512_maskz_compress_epi16(nnzMask, indices);
_mm512_storeu_si512(info.nnz + count, nnzIndices);
count += popcount(nnzMask);
indices = _mm512_add_epi16(indices, increment);
#else
const __m512i increment = _mm512_set1_epi32(16);
for (auto neurons : {neurons1, neurons2})
{
// Get a bitmask and gather non zero indices
const __mmask16 nnzMask = _mm512_test_epi32_mask(neurons, neurons);
const __m512i nnzV = _mm512_maskz_compress_epi32(nnzMask, indices);
_mm512_mask_cvtepi32_storeu_epi16(info.nnz + count, 0xFFFF, nnzV);
count += popcount(nnzMask);
indices = _mm512_add_epi32(indices, increment);
}
#endif
}
~NNZCursor() { info.count = count; }
};
NNZCursor make_cursor(bool perspective) { return {*this, perspective, count}; }
#else
// Each 8-bit chunk
alignas(8) u8 bitset[(Dimensions + 31) / 32];
struct NNZCursor {
u8* out;
NNZCursor(NNZInfo& info, bool perspective) {
out = info.bitset + perspective * Dimensions / 64;
}
#if (defined(USE_SSSE3) || defined(USE_LSX) || defined(USE_LASX) || (USE_NEON >= 8))
void record2(SIMD::vec_t neurons1, SIMD::vec_t neurons2) {
using namespace SIMD;
#ifdef USE_NEON
alignas(16) static constexpr u16 Mask8[8] = {1, 16, 2, 32, 4, 64, 8, 128};
uint32x4_t n1 = vreinterpretq_u32_s16(neurons1);
uint32x4_t n2 = vreinterpretq_u32_s16(neurons2);
const uint32x4_t t1 = vtstq_u32(n1, n1);
const uint32x4_t t2 = vtstq_u32(n2, n2);
const uint16x8_t packed =
vtrn1q_u16(vreinterpretq_u16_u32(t1), vreinterpretq_u16_u32(t2));
const uint16x8_t bits = vandq_u16(packed, vld1q_u16(Mask8));
*out++ = vaddvq_u16(bits);
#elif defined(__wasm__)
__m128i packed = _mm_packus_epi32(neurons1, neurons2);
packed = _mm_packs_epi16(packed, packed);
*out++ = ~_mm_movemask_epi8(_mm_cmpeq_epi8(packed, _mm_setzero_si128()));
#else
auto m1 = vec_nnz(neurons1);
auto m2 = vec_nnz(neurons2);
if (sizeof(neurons1) == 16)
{
*out++ = m1 + (m2 << 4);
}
else
{
usize bytes = sizeof(neurons1) / 32;
memcpy(out, &m1, bytes);
out += bytes;
memcpy(out, &m2, bytes);
out += bytes;
}
#endif
}
#elif defined(VECTOR)
void record2(SIMD::vec_t, SIMD::vec_t) {}
#endif
};
NNZCursor make_cursor(bool perspective) { return {*this, perspective}; }
#endif
};
} // namespace Stockfish::Eval::NNUE
#endif
+242 -116
View File
@@ -33,6 +33,17 @@
#elif defined(USE_NEON)
#include <arm_neon.h>
#elif defined(USE_LASX)
#include <lasxintrin.h>
#include <lsxintrin.h>
#elif defined(USE_LSX)
#include <lsxintrin.h>
#elif defined(USE_RVV)
#include <riscv_vector.h>
#endif
#include "../types.h"
@@ -40,6 +51,10 @@
namespace Stockfish::Eval::NNUE::SIMD {
#if defined(USE_AVX2) && !defined(USE_VNNI) && !defined(USE_AVX512)
#define USE_AVX2_PAIR_ACTIVATIONS
#endif
// If vector instructions are enabled, we update and refresh the
// accumulator tile by tile such that each tile fits in the CPU's
// vector registers.
@@ -48,9 +63,7 @@ namespace Stockfish::Eval::NNUE::SIMD {
#ifdef USE_AVX512
using vec_t = __m512i;
using vec_i8_t = __m256i;
using vec128_t = __m128i;
using psqt_vec_t = __m256i;
using vec_uint_t = __m512i;
#define vec_load(a) _mm512_load_si512(a)
#define vec_store(a, b) _mm512_store_si512(a, b)
#define vec_convert_8_16(a) _mm512_cvtepi8_epi16(a)
@@ -70,24 +83,15 @@ using vec_uint_t = __m512i;
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
#define vec_zero_psqt() _mm256_setzero_si256()
#ifdef USE_SSSE3
#define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
#endif
#define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
#define vec128_zero _mm_setzero_si128()
#define vec128_set_16(a) _mm_set1_epi16(a)
#define vec128_load(a) _mm_load_si128(a)
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
#define vec128_add(a, b) _mm_add_epi16(a, b)
#define NumRegistersSIMD 16
#define MaxChunkSize 64
#elif USE_AVX2
using vec_t = __m256i;
using vec_i8_t = __m128i;
using vec128_t = __m128i;
using psqt_vec_t = __m256i;
using vec_uint_t = __m256i;
#define vec_load(a) _mm256_load_si256(a)
#define vec_store(a, b) _mm256_store_si256(a, b)
#define vec_convert_8_16(a) _mm256_cvtepi8_epi16(a)
@@ -107,31 +111,16 @@ using vec_uint_t = __m256i;
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
#define vec_zero_psqt() _mm256_setzero_si256()
#ifdef USE_SSSE3
#if defined(USE_VNNI) && !defined(USE_AVXVNNI)
#define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256())
#else
#define vec_nnz(a) \
_mm256_movemask_ps( \
_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
#endif
#endif
#define vec128_zero _mm_setzero_si128()
#define vec128_set_16(a) _mm_set1_epi16(a)
#define vec128_load(a) _mm_load_si128(a)
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
#define vec128_add(a, b) _mm_add_epi16(a, b)
#define vec_nnz(a) \
_mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
#define NumRegistersSIMD 12
#define MaxChunkSize 32
#elif USE_SSE2
using vec_t = __m128i;
using vec_i8_t = std::uint64_t; // for the correct size -- will be loaded into an xmm reg
using vec128_t = __m128i;
using vec_i8_t = u64;
using psqt_vec_t = __m128i;
using vec_uint_t = __m128i;
#define vec_load(a) (*(a))
#define vec_store(a, b) *(a) = (b)
#define vec_add_16(a, b) _mm_add_epi16(a, b)
@@ -155,37 +144,39 @@ using vec_uint_t = __m128i;
#endif
#ifdef __i386__
inline __m128i _mm_cvtsi64_si128(int64_t val) {
inline __m128i _mm_cvtsi64_si128(i64 val) {
return _mm_loadl_epi64(reinterpret_cast<const __m128i*>(&val));
}
#endif
#ifdef USE_SSE41
#define vec_convert_8_16(a) _mm_cvtepi8_epi16(_mm_cvtsi64_si128(static_cast<int64_t>(a)))
#ifdef __wasm__
#define vec_convert_8_16(a) wasm_i16x8_load8x8(reinterpret_cast<const void*>(&a))
#else
#define vec_convert_8_16(a) _mm_cvtepi8_epi16(_mm_cvtsi64_si128(static_cast<i64>(a)))
#endif
#else
// Credit: Yoshie2000
inline __m128i vec_convert_8_16(uint64_t x) {
__m128i v8 = _mm_cvtsi64_si128(static_cast<int64_t>(x));
inline __m128i vec_convert_8_16(u64 x) {
__m128i v8 = _mm_cvtsi64_si128(static_cast<i64>(x));
__m128i sign = _mm_cmpgt_epi8(_mm_setzero_si128(), v8);
return _mm_unpacklo_epi8(v8, sign);
}
#endif
#define vec128_zero _mm_setzero_si128()
#define vec128_set_16(a) _mm_set1_epi16(a)
#define vec128_load(a) _mm_load_si128(a)
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
#define vec128_add(a, b) _mm_add_epi16(a, b)
#define NumRegistersSIMD (Is64Bit ? 12 : 6)
#define MaxChunkSize 16
#elif USE_NEON
using vec_t = int16x8_t;
using vec_i8_t = int8x16_t;
using psqt_vec_t = int32x4_t;
using vec128_t = uint16x8_t;
using vec_uint_t = uint32x4_t;
using vec_i8x8_t __attribute__((may_alias)) = int8x8_t;
using vec_i16x8_t __attribute__((may_alias)) = int16x8_t;
using vec_i8x16_t __attribute__((may_alias)) = int8x16_t;
using vec_u16x8_t __attribute__((may_alias)) = uint16x8_t;
using vec_i32x4_t __attribute__((may_alias)) = int32x4_t;
using vec_t __attribute__((may_alias)) = int16x8_t;
using vec_i8_t __attribute__((may_alias)) = int8x16_t;
using psqt_vec_t __attribute__((may_alias)) = int32x4_t;
#define vec_load(a) (*(a))
#define vec_store(a, b) *(a) = (b)
#define vec_add_16(a, b) vaddq_s16(a, b)
@@ -203,85 +194,152 @@ using vec_uint_t = uint32x4_t;
#define vec_sub_psqt_32(a, b) vsubq_s32(a, b)
#define vec_zero_psqt() psqt_vec_t{0}
static constexpr std::uint32_t Mask[4] = {1, 2, 4, 8};
#define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask)))
#define vec128_zero vdupq_n_u16(0)
#define vec128_set_16(a) vdupq_n_u16(a)
#define vec128_load(a) vld1q_u16(reinterpret_cast<const std::uint16_t*>(a))
#define vec128_storeu(a, b) vst1q_u16(reinterpret_cast<std::uint16_t*>(a), b)
#define vec128_add(a, b) vaddq_u16(a, b)
#define NumRegistersSIMD 16
#define MaxChunkSize 16
#ifndef __aarch64__
// Single instruction doesn't exist on 32-bit ARM
inline int8x16_t vmovl_high_s8(int8x16_t val) { return vmovl_s8(vget_high_s8(val)); }
// Instructions that don't exist on 32-bit ARM
inline int16x8_t vaddw_high_s8(int16x8_t a, int8x16_t b) { return vaddw_s8(a, vget_high_s8(b)); }
inline int16x8_t vsubw_high_s8(int16x8_t a, int8x16_t b) { return vsubw_s8(a, vget_high_s8(b)); }
#endif
#elif USE_LASX
using vec_t = __m256i;
using vec_i8_t = __m128i;
using psqt_vec_t = __m256i;
inline __m256i lasx_load256(const __m256i* a) {
return __lasx_xvld(reinterpret_cast<const void*>(a), 0);
}
inline void lasx_store256(__m256i* a, __m256i b) { __lasx_xvst(b, reinterpret_cast<void*>(a), 0); }
inline __m256i lasx_packus_16(__m256i a, __m256i b) {
#if defined(__clang__) && defined(__has_builtin) && __has_builtin(__builtin_lasx_xvssrani_bu_h)
return (__m256i) __builtin_lasx_xvssrani_bu_h((v32i8) b, (v32i8) a, 0);
#else
return __lasx_xvssrani_bu_h(b, a, 0);
#endif
}
inline __m256i lasx_packus_32(__m256i a, __m256i b) {
#if defined(__clang__) && defined(__has_builtin) && __has_builtin(__builtin_lasx_xvssrani_hu_w)
return (__m256i) __builtin_lasx_xvssrani_hu_w((v16i16) b, (v16i16) a, 0);
#else
return __lasx_xvssrani_hu_w(b, a, 0);
#endif
}
#define vec_load(a) lasx_load256(a)
#define vec_store(a, b) lasx_store256(a, b)
#define vec_add_16(a, b) __lasx_xvadd_h(a, b)
#define vec_sub_16(a, b) __lasx_xvsub_h(a, b)
#define vec_mulhi_16(a, b) __lasx_xvmuh_h(a, b)
#define vec_zero() __lasx_xvldi(0)
#define vec_set_16(a) __lasx_xvreplgr2vr_h(a)
#define vec_max_16(a, b) __lasx_xvmax_h(a, b)
#define vec_min_16(a, b) __lasx_xvmin_h(a, b)
#define vec_slli_16(a, b) __lasx_xvslli_h(a, b)
// Inverse permuted at load time
#define vec_packus_16(a, b) lasx_packus_16(a, b)
#define vec_load_psqt(a) lasx_load256(a)
#define vec_store_psqt(a, b) lasx_store256(a, b)
#define vec_add_psqt_32(a, b) __lasx_xvadd_w(a, b)
#define vec_sub_psqt_32(a, b) __lasx_xvsub_w(a, b)
#define vec_zero_psqt() __lasx_xvldi(0)
#define vec_nnz(a) lasx_vec_nnz(a)
#define vec_convert_8_16(a) lasx_cvtepi8_epi16(a)
#define vec_mulhi_8 __lasx_xvmuh_bu
#define vec_srli_8 __lasx_xvsrli_b
#define NumRegistersSIMD 24
#define MaxChunkSize 32
inline __m256i lasx_cvtepi8_epi16(__m128i a) {
#if defined(__has_builtin) && __has_builtin(__builtin_lasx_cast_128)
return __lasx_vext2xv_h_b(__lasx_cast_128(a));
#elif defined(__GNUC__) && !defined(__clang__)
__m256i out;
__asm__("vext2xv.h.b %u0, %u1" : "=f"(out) : "f"(a));
return out;
#else
i64 lo = (i64) __lsx_vpickve2gr_d(a, 0);
i64 hi = (i64) __lsx_vpickve2gr_d(a, 1);
__m256i v = __lasx_xvldi(0);
v = __lasx_xvinsgr2vr_d(v, lo, 0);
v = __lasx_xvinsgr2vr_d(v, hi, 2);
return __lasx_xvsllwil_h_b(v, 0);
#endif
}
inline int lasx_vec_nnz(__m256i a) {
const __m256i cmp = __lasx_xvslt_w(__lasx_xvldi(0), a);
const __m256i msk = __lasx_xvmskltz_w(cmp);
return ((int) __lasx_xvpickve2gr_w(msk, 0)) | (((int) __lasx_xvpickve2gr_w(msk, 4)) << 4);
}
#elif USE_LSX
using vec_t = __m128i;
using vec_i8_t = u64;
using psqt_vec_t = __m128i;
inline __m128i lsx_packus_16(__m128i a, __m128i b) {
#if defined(__clang__) && defined(__has_builtin) && __has_builtin(__builtin_lsx_vssrani_bu_h)
return (__m128i) __builtin_lsx_vssrani_bu_h((v16i8) b, (v16i8) a, 0);
#else
return __lsx_vssrani_bu_h(b, a, 0);
#endif
}
inline __m128i lsx_packus_32(__m128i a, __m128i b) {
#if defined(__clang__) && defined(__has_builtin) && __has_builtin(__builtin_lsx_vssrani_hu_w)
return (__m128i) __builtin_lsx_vssrani_hu_w((v8i16) b, (v8i16) a, 0);
#else
return __lsx_vssrani_hu_w(b, a, 0);
#endif
}
#define vec_load(a) (*(a))
#define vec_store(a, b) *(a) = (b)
#define vec_add_16(a, b) __lsx_vadd_h(a, b)
#define vec_sub_16(a, b) __lsx_vsub_h(a, b)
#define vec_mulhi_16(a, b) __lsx_vmuh_h(a, b)
#define vec_zero() __lsx_vldi(0)
#define vec_set_16(a) __lsx_vreplgr2vr_h(a)
#define vec_max_16(a, b) __lsx_vmax_h(a, b)
#define vec_min_16(a, b) __lsx_vmin_h(a, b)
#define vec_slli_16(a, b) __lsx_vslli_h(a, b)
// Inverse permuted at load time
#define vec_packus_16(a, b) lsx_packus_16(a, b)
#define vec_load_psqt(a) (*(a))
#define vec_store_psqt(a, b) *(a) = (b)
#define vec_add_psqt_32(a, b) __lsx_vadd_w(a, b)
#define vec_sub_psqt_32(a, b) __lsx_vsub_w(a, b)
#define vec_zero_psqt() __lsx_vldi(0)
inline int lsx_vec_nnz(__m128i a) {
const __m128i cmp = __lsx_vslt_w(__lsx_vldi(0), a);
const __m128i msk = __lsx_vmskltz_w(cmp);
return ((int) __lsx_vpickve2gr_w(msk, 0));
}
#define vec_nnz(a) lsx_vec_nnz(a)
inline __m128i vec_convert_8_16(u64 x) {
__m128i v = __lsx_vldrepl_d(reinterpret_cast<const void*>(&x), 0);
return __lsx_vsllwil_h_b(v, 0);
}
#define vec_mulhi_8 __lsx_vmuh_bu
#define vec_srli_8 __lsx_vsrli_b
#define NumRegistersSIMD 24
#define MaxChunkSize 16
#else
#undef VECTOR
#endif
struct Vec16Wrapper {
#ifdef VECTOR
using type = vec_t;
static type add(const type& lhs, const type& rhs) { return vec_add_16(lhs, rhs); }
static type sub(const type& lhs, const type& rhs) { return vec_sub_16(lhs, rhs); }
#else
using type = BiasType;
static type add(const type& lhs, const type& rhs) { return lhs + rhs; }
static type sub(const type& lhs, const type& rhs) { return lhs - rhs; }
#endif
};
struct Vec32Wrapper {
#ifdef VECTOR
using type = psqt_vec_t;
static type add(const type& lhs, const type& rhs) { return vec_add_psqt_32(lhs, rhs); }
static type sub(const type& lhs, const type& rhs) { return vec_sub_psqt_32(lhs, rhs); }
#else
using type = PSQTWeightType;
static type add(const type& lhs, const type& rhs) { return lhs + rhs; }
static type sub(const type& lhs, const type& rhs) { return lhs - rhs; }
#endif
};
enum UpdateOperation {
Add,
Sub
};
template<typename VecWrapper,
UpdateOperation... ops,
std::enable_if_t<sizeof...(ops) == 0, bool> = true>
typename VecWrapper::type fused(const typename VecWrapper::type& in) {
return in;
}
template<typename VecWrapper,
UpdateOperation update_op,
UpdateOperation... ops,
typename T,
typename... Ts,
std::enable_if_t<is_all_same_v<typename VecWrapper::type, T, Ts...>, bool> = true,
std::enable_if_t<sizeof...(ops) == sizeof...(Ts), bool> = true>
typename VecWrapper::type
fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operands) {
switch (update_op)
{
case Add :
return fused<VecWrapper, ops...>(VecWrapper::add(in, operand), operands...);
case Sub :
return fused<VecWrapper, ops...>(VecWrapper::sub(in, operand), operands...);
default :
static_assert(update_op == Add || update_op == Sub,
"Only Add and Sub are currently supported.");
return typename VecWrapper::type();
}
}
#if defined(USE_AVX512)
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
@@ -332,10 +390,13 @@ fused(const typename VecWrapper::type& in, const T& operand, const Ts&... operan
}
[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
#if defined(__wasm_relaxed_simd__)
acc = wasm_i32x4_relaxed_dot_i8x16_i7x16_add(b, a, acc);
#else
__m128i product0 = _mm_maddubs_epi16(a, b);
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
acc = _mm_add_epi32(acc, product0);
#endif
}
#endif
@@ -375,6 +436,42 @@ dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
}
#endif
#if defined(USE_LASX)
[[maybe_unused]] static int lasx_m256_hadd(__m256i sum, int bias) {
__m256i v = sum;
v = __lasx_xvadd_w(v, __lasx_xvshuf4i_w(v, 0x4E)); // [C,D,A,B] per lane
v = __lasx_xvadd_w(v, __lasx_xvshuf4i_w(v, 0xB1)); // [B,A,D,C] per lane
int lo_sum = (int) __lasx_xvpickve2gr_w(v, 0);
int hi_sum = (int) __lasx_xvpickve2gr_w(v, 4);
return lo_sum + hi_sum + bias;
}
[[maybe_unused]] static void lasx_m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) {
__m256i tmp = __lasx_xvmulwev_h_bu_b(a, b);
tmp = __lasx_xvmaddwod_h_bu_b(tmp, a, b);
acc = __lasx_xvadd_w(acc, __lasx_xvhaddw_w_h(tmp, tmp));
}
#endif // USE_LASX
#if defined(USE_LSX)
[[maybe_unused]] static int lsx_m128_hadd(__m128i sum, int bias) {
sum = __lsx_vadd_w(sum, __lsx_vshuf4i_w(sum, 0x4E)); // [C,D,A,B]
sum = __lsx_vadd_w(sum, __lsx_vshuf4i_w(sum, 0xB1)); // [B,A,D,C]
return __lsx_vpickve2gr_w(sum, 0) + bias;
}
[[maybe_unused]] static void lsx_m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
// tmp[i] = a[2i]*b[2i] + a[2i+1]*b[2i+1]
__m128i tmp = __lsx_vmulwev_h_bu_b(a, b);
tmp = __lsx_vmaddwod_h_bu_b(tmp, a, b);
acc = __lsx_vadd_w(acc, __lsx_vhaddw_w_h(tmp, tmp));
}
#endif // USE_LSX
// Compute optimal SIMD register count for feature transformer accumulation.
template<IndexType TransformedFeatureWidth, IndexType HalfDimensions, IndexType PSQTBuckets>
@@ -390,8 +487,8 @@ class SIMDTiling {
template<typename SIMDRegisterType, typename LaneType, int NumLanes, int MaxRegisters>
static constexpr int BestRegisterCount() {
constexpr std::size_t RegisterSize = sizeof(SIMDRegisterType);
constexpr std::size_t LaneSize = sizeof(LaneType);
constexpr usize RegisterSize = sizeof(SIMDRegisterType);
constexpr usize LaneSize = sizeof(LaneType);
static_assert(RegisterSize >= LaneSize);
static_assert(MaxRegisters <= NumRegistersSIMD);
@@ -429,6 +526,35 @@ class SIMDTiling {
static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets");
#endif
};
#if defined(USE_RVV)
#define RVV_DEFINE_DPBUSD(A, W, H) \
inline vint32##A##_t rvv_dpbusd_##A(vuint8##A##_t a, vint8##A##_t b, usize n) { \
vint16##W##_t prod = __riscv_vwmulsu_vv_i16##W(b, a, 4 * n); \
vuint32##W##_t prod32 = __riscv_vreinterpret_v_u16##W##_u32##W( \
__riscv_vreinterpret_v_i16##W##_u16##W(prod)); \
vint16##A##_t even = \
__riscv_vreinterpret_v_u16##A##_i16##A(__riscv_vnsrl_wx_u16##A(prod32, 0, 2 * n)); \
vint16##A##_t odd = \
__riscv_vreinterpret_v_u16##A##_i16##A(__riscv_vnsrl_wx_u16##A(prod32, 16, 2 * n)); \
vuint32##A##_t pairs = __riscv_vreinterpret_v_u16##A##_u32##A( \
__riscv_vreinterpret_v_i16##A##_u16##A(__riscv_vadd_vv_i16##A(even, odd, 2 * n))); \
vint16##H##_t lo = \
__riscv_vreinterpret_v_u16##H##_i16##H(__riscv_vnsrl_wx_u16##H(pairs, 0, n)); \
vint16##H##_t hi = \
__riscv_vreinterpret_v_u16##H##_i16##H(__riscv_vnsrl_wx_u16##H(pairs, 16, n)); \
return __riscv_vwadd_vv_i32##A(lo, hi, n); \
}
RVV_DEFINE_DPBUSD(m1, m2, mf2)
RVV_DEFINE_DPBUSD(m2, m4, m1)
RVV_DEFINE_DPBUSD(m4, m8, m2)
#undef RVV_DEFINE_DPBUSD
#endif
}
#endif
+66 -61
View File
@@ -37,6 +37,7 @@
#include <vector>
#include <cstring>
#include "misc.h"
#include "shm.h"
// We support linux very well, but we explicitly do NOT support Android,
@@ -53,10 +54,6 @@
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
#endif
// On Windows each processor group can have up to 64 processors.
// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups
static constexpr size_t WIN_PROCESSOR_GROUP_SIZE = 64;
#if !defined(NOMINMAX)
#define NOMINMAX
#endif
@@ -77,8 +74,8 @@ using GetThreadSelectedCpuSetMasks_t = BOOL (*)(HANDLE, PGROUP_AFFINITY, USHORT,
namespace Stockfish {
using CpuIndex = size_t;
using NumaIndex = size_t;
using CpuIndex = usize;
using NumaIndex = usize;
inline CpuIndex get_hardware_concurrency() {
CpuIndex concurrency = std::thread::hardware_concurrency();
@@ -97,6 +94,10 @@ inline const CpuIndex SYSTEM_THREADS_NB = std::max<CpuIndex>(1, get_hardware_con
#if defined(_WIN64)
// On Windows each processor group can have up to 64 processors.
// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups
static constexpr usize WIN_PROCESSOR_GROUP_SIZE = 64;
struct WindowsAffinity {
std::optional<std::set<CpuIndex>> oldApi;
std::optional<std::set<CpuIndex>> newApi;
@@ -134,7 +135,7 @@ inline std::pair<BOOL, std::vector<USHORT>> get_process_group_affinity() {
// GetProcessGroupAffinity requires the GroupArray argument to be
// aligned to 4 bytes instead of just 2.
static constexpr size_t GroupArrayMinimumAlignment = 4;
static constexpr usize GroupArrayMinimumAlignment = 4;
static_assert(GroupArrayMinimumAlignment >= alignof(USHORT));
// The function should succeed the second time, but it may fail if the group
@@ -217,9 +218,9 @@ inline WindowsAffinity get_process_affinity() {
for (USHORT i = 0; i < RequiredMaskCount; ++i)
{
const size_t procGroupIndex = groupAffinities[i].Group;
const usize procGroupIndex = groupAffinities[i].Group;
for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
for (usize j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
{
if (groupAffinities[i].Mask & (KAFFINITY(1) << j))
cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);
@@ -271,10 +272,10 @@ inline WindowsAffinity get_process_affinity() {
{
std::set<CpuIndex> cpus;
const size_t procGroupIndex = groupAffinity[0];
const usize procGroupIndex = groupAffinity[0];
const uint64_t mask = static_cast<uint64_t>(proc);
for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
const u64 mask = static_cast<u64>(proc);
for (usize j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
{
if (mask & (KAFFINITY(1) << j))
cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);
@@ -312,8 +313,8 @@ inline WindowsAffinity get_process_affinity() {
// choice could influence the resulting affinity.
// We assume the processor IDs within the group are
// filled sequentially from 0.
uint64_t procCombined = std::numeric_limits<uint64_t>::max();
uint64_t sysCombined = std::numeric_limits<uint64_t>::max();
u64 procCombined = std::numeric_limits<u64>::max();
u64 sysCombined = std::numeric_limits<u64>::max();
for (int i = 0; i < std::min(numActiveProcessors, 2); ++i)
{
@@ -341,14 +342,14 @@ inline WindowsAffinity get_process_affinity() {
return;
}
procCombined &= static_cast<uint64_t>(proc2);
sysCombined &= static_cast<uint64_t>(sys2);
procCombined &= static_cast<u64>(proc2);
sysCombined &= static_cast<u64>(sys2);
}
if (procCombined != sysCombined)
isAffinityFull = false;
for (size_t j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
for (usize j = 0; j < WIN_PROCESSOR_GROUP_SIZE; ++j)
{
if (procCombined & (KAFFINITY(1) << j))
cpus.insert(procGroupIndex * WIN_PROCESSOR_GROUP_SIZE + j);
@@ -439,7 +440,7 @@ inline std::set<CpuIndex> get_process_affinity() {
if (mask == nullptr)
std::exit(EXIT_FAILURE);
const size_t masksize = CPU_ALLOC_SIZE(MaxNumCpus);
const usize masksize = CPU_ALLOC_SIZE(MaxNumCpus);
CPU_ZERO_S(masksize, mask);
@@ -502,7 +503,7 @@ struct SystemNumaPolicy {};
struct L3DomainsPolicy {};
// Group system-reported L3 domains until they reach bundleSize
struct BundledL3Policy {
size_t bundleSize;
usize bundleSize;
};
using NumaAutoPolicy = std::variant<SystemNumaPolicy, L3DomainsPolicy, BundledL3Policy>;
@@ -581,7 +582,7 @@ class NumaConfig {
bool l3Success = false;
if (!std::holds_alternative<SystemNumaPolicy>(policy))
{
size_t l3BundleSize = 0;
usize l3BundleSize = 0;
if (const auto* v = std::get_if<BundledL3Policy>(&policy))
{
l3BundleSize = v->bundleSize;
@@ -627,10 +628,10 @@ class NumaConfig {
if (cpus.empty())
continue;
size_t lastProcGroupIndex = *(cpus.begin()) / WIN_PROCESSOR_GROUP_SIZE;
usize lastProcGroupIndex = *(cpus.begin()) / WIN_PROCESSOR_GROUP_SIZE;
for (CpuIndex c : cpus)
{
const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
const usize procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
if (procGroupIndex != lastProcGroupIndex)
{
splitNodeIndex += 1;
@@ -663,7 +664,7 @@ class NumaConfig {
// ','-separated cpu indices
// supports "first-last" range syntax for cpu indices
// For example "0-15,128-143:16-31,144-159:32-47,160-175:48-63,176-191"
static NumaConfig from_string(const std::string& s) {
static std::optional<NumaConfig> from_string(const std::string& s) {
NumaConfig cfg = empty();
NumaIndex n = 0;
@@ -675,13 +676,16 @@ class NumaConfig {
for (auto idx : indices)
{
if (!cfg.add_cpu_to_node(n, CpuIndex(idx)))
std::exit(EXIT_FAILURE);
return std::nullopt;
}
n += 1;
}
}
if (n == 0) // failed to parse any nodes
return std::nullopt;
cfg.customAffinity = true;
return cfg;
@@ -769,7 +773,7 @@ class NumaConfig {
if (numThreads <= 1)
return false;
size_t largestNodeSize = 0;
usize largestNodeSize = 0;
for (auto&& cpus : nodes)
if (cpus.size() > largestNodeSize)
largestNodeSize = cpus.size();
@@ -780,7 +784,7 @@ class NumaConfig {
<= SmallNodeThreshold;
};
size_t numNotSmallNodes = 0;
usize numNotSmallNodes = 0;
for (auto&& cpus : nodes)
if (!is_node_small(cpus))
numNotSmallNodes += 1;
@@ -800,7 +804,7 @@ class NumaConfig {
}
else
{
std::vector<size_t> occupation(nodes.size(), 0);
std::vector<usize> occupation(nodes.size(), 0);
for (CpuIndex c = 0; c < numThreads; ++c)
{
NumaIndex bestNode{0};
@@ -838,7 +842,7 @@ class NumaConfig {
if (mask == nullptr)
std::exit(EXIT_FAILURE);
const size_t masksize = CPU_ALLOC_SIZE(highestCpuIndex + 1);
const usize masksize = CPU_ALLOC_SIZE(highestCpuIndex + 1);
CPU_ZERO_S(masksize, mask);
@@ -880,8 +884,8 @@ class NumaConfig {
for (CpuIndex c : nodes[n])
{
const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;
const usize procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
const usize idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;
groupAffinities[procGroupIndex].Mask |= KAFFINITY(1) << idxWithinProcGroup;
}
@@ -919,12 +923,12 @@ class NumaConfig {
GROUP_AFFINITY affinity;
std::memset(&affinity, 0, sizeof(GROUP_AFFINITY));
// We use an ordered set to be sure to get the smallest cpu number here.
const size_t forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE;
affinity.Group = static_cast<WORD>(forcedProcGroupIndex);
const usize forcedProcGroupIndex = *(nodes[n].begin()) / WIN_PROCESSOR_GROUP_SIZE;
affinity.Group = static_cast<WORD>(forcedProcGroupIndex);
for (CpuIndex c : nodes[n])
{
const size_t procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
const size_t idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;
const usize procGroupIndex = c / WIN_PROCESSOR_GROUP_SIZE;
const usize idxWithinProcGroup = c % WIN_PROCESSOR_GROUP_SIZE;
// We skip processors that are not in the same processor group.
// If everything was set up correctly this will never be an issue,
// but we have to account for bad NUMA node specification.
@@ -1026,8 +1030,8 @@ class NumaConfig {
return true;
}
static std::vector<size_t> indices_from_shortened_string(const std::string& s) {
std::vector<size_t> indices;
static std::vector<usize> indices_from_shortened_string(const std::string& s) {
std::vector<usize> indices;
if (s.empty())
return indices;
@@ -1040,16 +1044,22 @@ class NumaConfig {
auto parts = split(ss, "-");
if (parts.size() == 1)
{
const CpuIndex c = CpuIndex{str_to_size_t(std::string(parts[0]))};
indices.emplace_back(c);
auto c = str_to_size_t(std::string(parts[0]));
if (c.has_value())
indices.emplace_back(*c);
}
else if (parts.size() == 2)
{
const CpuIndex cfirst = CpuIndex{str_to_size_t(std::string(parts[0]))};
const CpuIndex clast = CpuIndex{str_to_size_t(std::string(parts[1]))};
for (size_t c = cfirst; c <= clast; ++c)
constexpr usize MaxIndices = 1 << 20; // prevent oom
auto cfirst = str_to_size_t(std::string(parts[0]));
auto clast = str_to_size_t(std::string(parts[1]));
if (cfirst.has_value() && clast.has_value() && *clast - *cfirst < MaxIndices)
{
indices.emplace_back(c);
for (usize c = *cfirst; c <= *clast; ++c)
{
indices.emplace_back(c);
}
}
}
}
@@ -1088,7 +1098,7 @@ class NumaConfig {
else
{
remove_whitespace(*nodeIdsStr);
for (size_t n : indices_from_shortened_string(*nodeIdsStr))
for (usize n : indices_from_shortened_string(*nodeIdsStr))
{
// /sys/devices/system/node/node.../cpulist
std::string path =
@@ -1105,7 +1115,7 @@ class NumaConfig {
else
{
remove_whitespace(*cpuIdsStr);
for (size_t c : indices_from_shortened_string(*cpuIdsStr))
for (usize c : indices_from_shortened_string(*cpuIdsStr))
{
if (is_cpu_allowed(c))
cfg.add_cpu_to_node(n, c);
@@ -1156,7 +1166,7 @@ class NumaConfig {
template<typename Pred>
static std::optional<NumaConfig> try_get_l3_aware_config(
bool respectProcessAffinity, size_t bundleSize, [[maybe_unused]] Pred&& is_cpu_allowed) {
bool respectProcessAffinity, usize bundleSize, [[maybe_unused]] Pred&& is_cpu_allowed) {
// Get the normal system configuration so we know to which NUMA node
// each L3 domain belongs.
NumaConfig systemConfig =
@@ -1166,26 +1176,21 @@ class NumaConfig {
#if defined(__linux__) && !defined(__ANDROID__)
std::set<CpuIndex> seenCpus;
auto nextUnseenCpu = [&seenCpus]() {
for (CpuIndex i = 0;; ++i)
if (!seenCpus.count(i))
return i;
};
while (true)
for (const auto& [next, _] : systemConfig.nodeByCpu)
{
CpuIndex next = nextUnseenCpu();
auto siblingsStr =
if (seenCpus.count(next))
continue;
auto siblingsStr =
read_file_to_string("/sys/devices/system/cpu/cpu" + std::to_string(next)
+ "/cache/index3/shared_cpu_list");
if (!siblingsStr.has_value() || siblingsStr->empty())
{
break; // we have read all available CPUs
}
continue;
L3Domain domain;
for (size_t c : indices_from_shortened_string(*siblingsStr))
for (usize c : indices_from_shortened_string(*siblingsStr))
{
if (is_cpu_allowed(c))
{
@@ -1238,7 +1243,7 @@ class NumaConfig {
}
static NumaConfig from_l3_info(std::vector<L3Domain>&& domains, size_t bundleSize) {
static NumaConfig from_l3_info(std::vector<L3Domain>&& domains, usize bundleSize) {
assert(!domains.empty());
std::map<NumaIndex, std::vector<L3Domain>> list;
@@ -1255,7 +1260,7 @@ class NumaConfig {
do
{
changed = false;
for (size_t j = 0; j + 1 < ds.size(); ++j)
for (usize j = 0; j + 1 < ds.size(); ++j)
{
if (ds[j].cpus.size() + ds[j + 1].cpus.size() <= bundleSize)
{
@@ -1579,14 +1584,14 @@ class LazyNumaReplicatedSystemWide: public NumaReplicatedBase {
mutable std::vector<SystemWideSharedConstant<T>> instances;
mutable std::mutex mutex;
std::size_t get_discriminator(NumaIndex idx) const {
usize get_discriminator(NumaIndex idx) const {
const NumaConfig& cfg = get_numa_config();
const NumaConfig& cfg_sys = NumaConfig::from_system(SystemNumaPolicy{}, false);
// as a discriminator, locate the hardware/system numadomain this cpuindex belongs to
CpuIndex cpu = *cfg.nodes[idx].begin(); // get a CpuIndex from NumaIndex
NumaIndex sys_idx = cfg_sys.is_cpu_assigned(cpu) ? cfg_sys.nodeByCpu.at(cpu) : 0;
std::string s = cfg_sys.to_string() + "$" + std::to_string(sys_idx);
return static_cast<std::size_t>(hash_string(s));
return static_cast<usize>(hash_string(s));
}
void ensure_present(NumaIndex idx) const {
+8 -4
View File
@@ -20,6 +20,7 @@
#define PERFT_H_INCLUDED
#include <cstdint>
#include <variant>
#include "movegen.h"
#include "position.h"
@@ -31,11 +32,11 @@ namespace Stockfish::Benchmark {
// Utility to verify move generation. All the leaf nodes up
// to the given depth are generated and counted, and the sum is returned.
template<bool Root>
uint64_t perft(Position& pos, Depth depth) {
u64 perft(Position& pos, Depth depth) {
StateInfo st;
uint64_t cnt, nodes = 0;
u64 cnt, nodes = 0;
const bool leaf = (depth == 2);
for (const auto& m : MoveList<LEGAL>(pos))
@@ -55,10 +56,13 @@ uint64_t perft(Position& pos, Depth depth) {
return nodes;
}
inline uint64_t perft(const std::string& fen, Depth depth, bool isChess960) {
inline std::variant<u64, PositionSetError>
perft(const std::string& fen, Depth depth, bool isChess960) {
StateInfo st;
Position p;
p.set(fen, isChess960, &st);
if (auto err = p.set(fen, isChess960, &st))
return {*err};
return perft<true>(p, depth);
}
+424 -295
View File
File diff suppressed because it is too large Load Diff
+66 -33
View File
@@ -25,8 +25,11 @@
#include <iosfwd>
#include <memory>
#include <new>
#include <optional>
#include <stdexcept>
#include <string>
#include "attacks.h"
#include "bitboard.h"
#include "types.h"
@@ -70,6 +73,12 @@ struct StateInfo {
// elements are not invalidated upon list resizing.
using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
// This error should be used whenever a position is suspected to be unsupported
// by the engine. In particular positions that may cause hard errors like segmentation fault.
struct PositionSetError: std::runtime_error {
using std::runtime_error::runtime_error;
};
// Position class stores information regarding the board representation as
// pieces, side to move, hash keys, castling info, etc. Important methods are
// do_move() and undo_move(), used by the search to update node info when
@@ -83,9 +92,9 @@ class Position {
Position& operator=(const Position&) = delete;
// FEN string input/output
Position& set(const std::string& fenStr, bool isChess960, StateInfo* si);
Position& set(const std::string& code, Color c, StateInfo* si);
std::string fen() const;
std::optional<PositionSetError> set(const std::string& fenStr, bool isChess960, StateInfo* si);
std::optional<PositionSetError> set(const std::string& code, Color c, StateInfo* si);
std::string fen() const;
// Position representation
Bitboard pieces() const; // All pieces
@@ -138,12 +147,11 @@ class Position {
void do_move(Move m,
StateInfo& newSt,
bool givesCheck,
DirtyPiece& dp,
DirtyThreats& dts,
Dirties& dirties,
const TranspositionTable* tt,
const SharedHistories* worker);
void undo_move(Move m);
void do_null_move(StateInfo& newSt, const TranspositionTable& tt);
void do_null_move(StateInfo& newSt);
void undo_null_move();
// Static Exchange Evaluation
@@ -151,6 +159,7 @@ class Position {
// Accessing hash keys
Key key() const;
Key prefetch_key(Move m) const;
Key material_key() const;
Key pawn_key() const;
Key minor_piece_key() const;
@@ -167,11 +176,12 @@ class Position {
int rule50_count() const;
Value non_pawn_material(Color c) const;
Value non_pawn_material() const;
bool dtz_is_dtm() const; // Pawnless && (3-men || 4-men-minors-only)
// Position consistency check, for debugging
bool pos_is_ok() const;
bool material_key_is_ok() const;
void flip();
bool pos_is_ok() const;
bool material_key_is_ok() const;
std::optional<PositionSetError> flip();
StateInfo* state() const;
@@ -187,8 +197,9 @@ class Position {
void set_check_info() const;
// Other helpers
template<bool PutPiece, bool ComputeRay = true>
template<bool ComputeRay = true>
void update_piece_threats(Piece pc,
bool putPiece,
Square s,
DirtyThreats* const dts,
Bitboard noRaysContaining = -1ULL) const;
@@ -201,23 +212,23 @@ class Position {
Square& rto,
DirtyThreats* const dts = nullptr,
DirtyPiece* const dp = nullptr);
Key adjust_key50(Key k) const;
template<bool AfterMove = false>
Key adjust_key50(Key k) const;
// Data members
std::array<Piece, SQUARE_NB> board;
std::array<Bitboard, PIECE_TYPE_NB> byTypeBB;
std::array<Bitboard, COLOR_NB> byColorBB;
int pieceCount[PIECE_NB];
int castlingRightsMask[SQUARE_NB];
Square castlingRookSquare[CASTLING_RIGHT_NB];
Bitboard castlingPath[CASTLING_RIGHT_NB];
StateInfo* st;
int gamePly;
Color sideToMove;
bool chess960;
DirtyPiece scratch_dp;
DirtyThreats scratch_dts;
int pieceCount[PIECE_NB];
int castlingRightsMask[SQUARE_NB];
Square castlingRookSquare[CASTLING_RIGHT_NB];
Bitboard castlingPath[CASTLING_RIGHT_NB];
StateInfo* st;
int gamePly;
Color sideToMove;
bool chess960;
Dirties scratchDirties;
};
std::ostream& operator<<(std::ostream& os, const Position& pos);
@@ -292,7 +303,7 @@ inline Bitboard Position::attacks_by(Color c) const {
Bitboard threats = 0;
Bitboard attackers = pieces(c, Pt);
while (attackers)
threats |= attacks_bb<Pt>(pop_lsb(attackers), pieces());
threats |= Attacks::attacks_bb<Pt>(pop_lsb(attackers), pieces());
return threats;
}
}
@@ -307,8 +318,9 @@ inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSq
inline Key Position::key() const { return adjust_key50(st->key); }
template<bool AfterMove>
inline Key Position::adjust_key50(Key k) const {
return st->rule50 < 14 ? k : k ^ make_key((st->rule50 - 14) / 8);
return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
}
inline Key Position::pawn_key() const { return st->pawnKey; }
@@ -331,9 +343,20 @@ inline int Position::rule50_count() const { return st->rule50; }
inline bool Position::is_chess960() const { return chess960; }
inline bool Position::dtz_is_dtm() const {
return !count<PAWN>()
&& (count<ALL_PIECES>() == 3 || (count<ALL_PIECES>() == 4 && !pieces(QUEEN, ROOK)));
}
inline bool Position::capture(Move m) const {
assert(m.is_ok());
return (!empty(m.to_sq()) && m.type_of() != CASTLING) || m.type_of() == EN_PASSANT;
const MoveType mt = m.type_of();
if (mt == NORMAL || mt == PROMOTION)
return !empty(m.to_sq());
return mt == EN_PASSANT;
}
// Returns true if a move is generated from the capture stage, having also
@@ -341,7 +364,16 @@ inline bool Position::capture(Move m) const {
// generation is needed to avoid the generation of duplicate moves.
inline bool Position::capture_stage(Move m) const {
assert(m.is_ok());
return capture(m) || m.promotion_type() == QUEEN;
const MoveType mt = m.type_of();
if (mt == NORMAL)
return !empty(m.to_sq());
if (mt == PROMOTION)
return !empty(m.to_sq()) || m.promotion_type() == QUEEN;
return mt == EN_PASSANT;
}
inline Piece Position::captured_piece() const { return st->capturedPiece; }
@@ -354,14 +386,14 @@ inline void Position::put_piece(Piece pc, Square s, DirtyThreats* const dts) {
pieceCount[make_piece(color_of(pc), ALL_PIECES)]++;
if (dts)
update_piece_threats<true>(pc, s, dts);
update_piece_threats(pc, true, s, dts);
}
inline void Position::remove_piece(Square s, DirtyThreats* const dts) {
Piece pc = board[s];
if (dts)
update_piece_threats<false>(pc, s, dts);
update_piece_threats(pc, false, s, dts);
byTypeBB[ALL_PIECES] ^= s;
byTypeBB[type_of(pc)] ^= s;
@@ -376,7 +408,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts
Bitboard fromTo = from | to;
if (dts)
update_piece_threats<false>(pc, from, dts, fromTo);
update_piece_threats(pc, false, from, dts, fromTo);
byTypeBB[ALL_PIECES] ^= fromTo;
byTypeBB[type_of(pc)] ^= fromTo;
@@ -385,7 +417,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts
board[to] = pc;
if (dts)
update_piece_threats<true>(pc, to, dts, fromTo);
update_piece_threats(pc, true, to, dts, fromTo);
}
inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) {
@@ -394,17 +426,18 @@ inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) {
remove_piece(s);
if (dts)
update_piece_threats<false, false>(old, s, dts);
update_piece_threats<false>(old, false, s, dts);
put_piece(pc, s);
if (dts)
update_piece_threats<true, false>(pc, s, dts);
update_piece_threats<false>(pc, true, s, dts);
}
inline void Position::do_move(Move m, StateInfo& newSt, const TranspositionTable* tt = nullptr) {
new (&scratch_dts) DirtyThreats;
do_move(m, newSt, gives_check(m), scratch_dp, scratch_dts, tt, nullptr);
new (&scratchDirties.dirtyThreats) DirtyThreats;
new (&scratchDirties.dirtyPawnPairs) DirtyPawnPairs;
do_move(m, newSt, gives_check(m), scratchDirties, tt, nullptr);
}
inline StateInfo* Position::state() const { return st; }
+476 -362
View File
File diff suppressed because it is too large Load Diff
+105 -59
View File
@@ -23,14 +23,13 @@
#include <array>
#include <atomic>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <cstring>
#include "history.h"
#include "misc.h"
@@ -58,11 +57,52 @@ class OptionsMap;
namespace Search {
struct PVMoves {
Move moves[MAX_PLY + 1];
usize length = 0;
Move* begin() { return moves; }
const Move* begin() const { return moves; }
Move* end() { return moves + length; }
const Move* end() const { return moves + length; }
Move& operator[](usize index) { return moves[index]; }
const Move& operator[](usize index) const { return moves[index]; }
bool empty() const { return length == 0; }
usize size() const { return length; }
void clear() { length = 0; }
void push_back(Move move) {
assert(length < MAX_PLY + 1);
moves[length++] = move;
}
void resize(usize newSize) {
assert(newSize <= length);
length = newSize;
}
void update(Move move, const PVMoves* childPv) {
assert(childPv == nullptr || childPv->size() <= MAX_PLY);
length = childPv ? childPv->length : 0;
if (childPv)
{
std::memcpy(moves + 1, childPv->moves, length * sizeof(Move));
}
moves[0] = move;
++length;
}
};
// Stack struct keeps track of the information we need to remember from nodes
// shallower and deeper in the tree during the search. Each search thread has
// its own array of Stack objects, indexed by the current ply.
struct Stack {
Move* pv;
PVMoves* pv;
PieceToHistory* continuationHistory;
CorrectionHistory<PieceTo>* continuationCorrectionHistory;
int ply;
@@ -74,6 +114,7 @@ struct Stack {
bool inCheck;
bool ttPv;
bool ttHit;
bool followPV;
int cutoffCnt;
int reduction;
};
@@ -84,27 +125,32 @@ struct Stack {
// fail low). Score is normally set at -VALUE_INFINITE for all non-pv moves.
struct RootMove {
explicit RootMove(Move m) :
pv(1, m) {}
explicit RootMove(Move m) { pv.push_back(m); }
bool extract_ponder_from_tt(const TranspositionTable& tt, Position& pos);
bool score_is_bound() const { return scoreLowerbound || scoreUpperbound; }
bool score_is_exact_loss() const {
return score != -VALUE_INFINITE && is_loss(score) && !score_is_bound();
}
void unset_bound_flags() { scoreLowerbound = scoreUpperbound = false; }
bool operator==(const Move& m) const { return pv[0] == m; }
// Sort in descending order
bool operator<(const RootMove& m) const {
return m.score != score ? m.score < score : m.previousScore < previousScore;
}
uint64_t effort = 0;
Value score = -VALUE_INFINITE;
Value previousScore = -VALUE_INFINITE;
Value averageScore = -VALUE_INFINITE;
Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;
Value uciScore = -VALUE_INFINITE;
bool scoreLowerbound = false;
bool scoreUpperbound = false;
int selDepth = 0;
int tbRank = 0;
Value tbScore;
std::vector<Move> pv;
u64 effort = 0;
Value score = -VALUE_INFINITE;
Value previousScore = -VALUE_INFINITE;
Value averageScore = -VALUE_INFINITE;
Value meanSquaredScore = -VALUE_INFINITE * VALUE_INFINITE;
Value uciScore = -VALUE_INFINITE;
bool scoreLowerbound = false;
bool scoreUpperbound = false;
bool previousScoreExact = false;
int selDepth = 0;
int tbRank = 0;
Value tbScore;
PVMoves pv, previousPV;
};
using RootMoves = std::vector<RootMove>;
@@ -126,7 +172,7 @@ struct LimitsType {
std::vector<std::string> searchmoves;
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;
int movestogo, depth, mate, perft, infinite;
uint64_t nodes;
u64 nodes;
bool ponderMode;
};
@@ -134,22 +180,22 @@ struct LimitsType {
// The UCI stores the uci options, thread pool, and transposition table.
// This struct is used to easily forward data to the Search::Worker class.
struct SharedState {
SharedState(const OptionsMap& optionsMap,
ThreadPool& threadPool,
TranspositionTable& transpositionTable,
std::map<NumaIndex, SharedHistories>& sharedHists,
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& nets) :
SharedState(const OptionsMap& optionsMap,
ThreadPool& threadPool,
TranspositionTable& transpositionTable,
std::map<NumaIndex, SharedHistories>& sharedHists,
const LazyNumaReplicatedSystemWide<Eval::NNUE::Network>& net) :
options(optionsMap),
threads(threadPool),
tt(transpositionTable),
sharedHistories(sharedHists),
networks(nets) {}
network(net) {}
const OptionsMap& options;
ThreadPool& threads;
TranspositionTable& tt;
std::map<NumaIndex, SharedHistories>& sharedHistories;
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks;
const OptionsMap& options;
ThreadPool& threads;
TranspositionTable& tt;
std::map<NumaIndex, SharedHistories>& sharedHistories;
const LazyNumaReplicatedSystemWide<Eval::NNUE::Network>& network;
};
class Worker;
@@ -169,13 +215,13 @@ struct InfoShort {
struct InfoFull: InfoShort {
int selDepth;
size_t multiPV;
usize multiPV;
std::string_view wdl;
std::string_view bound;
size_t timeMs;
size_t nodes;
size_t nps;
size_t tbHits;
usize timeMs;
usize nodes;
usize nps;
usize tbHits;
std::string_view pv;
int hashfull;
};
@@ -183,7 +229,7 @@ struct InfoFull: InfoShort {
struct InfoIteration {
int depth;
std::string_view currmove;
size_t currmovenumber;
usize currmovenumber;
};
// Skill structure is used to implement strength limit. If we have a UCI_Elo,
@@ -208,7 +254,7 @@ struct Skill {
}
bool enabled() const { return level < 20.0; }
bool time_to_pick(Depth depth) const { return depth == 1 + int(level); }
Move pick_best(const RootMoves&, size_t multiPV);
Move pick_best(const RootMoves&, usize multiPV);
double level;
Move best = Move::none();
@@ -236,10 +282,10 @@ class SearchManager: public ISearchManager {
void check_time(Search::Worker& worker) override;
void pv(Search::Worker& worker,
const ThreadPool& threads,
const TranspositionTable& tt,
Depth depth);
void output_pv(Search::Worker& worker,
const ThreadPool& threads,
const TranspositionTable& tt,
Depth depth);
Stockfish::TimeManagement tm;
double originalTimeAdjust;
@@ -252,8 +298,6 @@ class SearchManager: public ISearchManager {
Value bestPreviousAverageScore;
bool stopOnPonderhit;
size_t id;
const UpdateContext& updates;
};
@@ -269,9 +313,9 @@ class Worker {
public:
Worker(SharedState&,
std::unique_ptr<ISearchManager>,
size_t,
size_t,
size_t,
usize,
usize,
usize,
NumaReplicatedAccessToken);
// Called at instantiation to initialize reductions tables.
@@ -291,14 +335,14 @@ class Worker {
LowPlyHistory lowPlyHistory;
CapturePieceToHistory captureHistory;
ContinuationHistory continuationHistory[2][2];
CorrectionHistory<Continuation> continuationCorrectionHistory;
TTMoveHistory ttMoveHistory;
SharedHistories& sharedHistory;
ContinuationHistory (&continuationHistory)[2][2];
private:
void iterative_deepening();
bool iterative_deepening();
void do_move(Position& pos, const Move move, StateInfo& st, Stack* const ss);
void
@@ -309,13 +353,14 @@ class Worker {
// This is the main search function, for both PV and non-PV nodes
template<NodeType nodeType>
Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode);
Value
search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, const bool cutNode);
// Quiescence search function, which is called by the main search
template<NodeType nodeType>
Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta);
Depth reduction(bool i, Depth d, int mn, int delta) const;
int reduction(bool i, Depth d, int mn, int delta) const;
// Pointer to the search manager, only allowed to be called by the main thread
SearchManager* main_manager() const {
@@ -324,25 +369,26 @@ class Worker {
}
TimePoint elapsed() const;
TimePoint elapsed_time() const;
Value evaluate(const Position&);
LimitsType limits;
size_t pvIdx, pvLast;
std::atomic<uint64_t> nodes, tbHits, bestMoveChanges;
int selDepth, nmpMinPly;
usize pvIdx, pvLast;
RelaxedAtomic<u64> nodes, tbHits, bestMoveChanges;
int selDepth, nmpMinPly;
Value optimism[COLOR_NB];
Position rootPos;
StateInfo rootState;
RootMoves rootMoves;
Depth rootDepth, completedDepth;
Depth rootDepth;
Value rootDelta;
size_t threadIdx, numaThreadIdx, numaTotal;
PVMoves lastIterationIdxPV;
usize threadIdx, numaThreadIdx, numaTotal;
NumaReplicatedAccessToken numaAccessToken;
// Reductions lookup table initialized at startup
@@ -353,10 +399,10 @@ class Worker {
Tablebases::Config tbConfig;
const OptionsMap& options;
ThreadPool& threads;
TranspositionTable& tt;
const LazyNumaReplicatedSystemWide<Eval::NNUE::Networks>& networks;
const OptionsMap& options;
ThreadPool& threads;
TranspositionTable& tt;
const LazyNumaReplicatedSystemWide<Eval::NNUE::Network>& network;
// Used by NNUE
Eval::NNUE::AccumulatorStack accumulatorStack;
+17 -21
View File
@@ -40,11 +40,6 @@
#include "shm_linux.h"
#endif
#if defined(__ANDROID__)
#include <limits.h>
#define SF_MAX_SEM_NAME_LEN NAME_MAX
#endif
#include "types.h"
#include "memory.h"
@@ -100,14 +95,14 @@ namespace Stockfish {
// amount of bytes of the path; in particular it can a hash of an empty string.
inline std::string getExecutablePathHash() {
char executable_path[4096] = {0};
std::size_t path_length = 0;
char executable_path[4096] = {0};
usize path_length = 0;
#if defined(_WIN32)
path_length = GetModuleFileNameA(NULL, executable_path, sizeof(executable_path));
#elif defined(__APPLE__)
uint32_t size = sizeof(executable_path);
u32 size = sizeof(executable_path);
if (_NSGetExecutablePath(executable_path, &size) == 0)
{
path_length = std::strlen(executable_path);
@@ -122,8 +117,8 @@ inline std::string getExecutablePathHash() {
}
#elif defined(__FreeBSD__)
size_t size = sizeof(executable_path);
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
usize size = sizeof(executable_path);
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
if (sysctl(mib, 4, executable_path, &size, NULL, 0) == 0)
{
path_length = std::strlen(executable_path);
@@ -171,10 +166,10 @@ inline std::string GetLastErrorAsString(DWORD error) {
//Ask Win32 to give us the string version of that message ID.
//The parameters we pass in, tell Win32 to create the buffer that holds the message for us (because we don't yet know how long the message string will be).
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR) &messageBuffer, 0, NULL);
usize size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
| FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR) &messageBuffer, 0, NULL);
//Copy the error message into a std::string.
std::string message(messageBuffer, size);
@@ -278,12 +273,12 @@ class SharedMemoryBackend {
private:
void initialize(const std::string& shm_name, const T& value) {
const size_t total_size = sizeof(T) + sizeof(IS_INITIALIZED_VALUE);
const usize total_size = sizeof(T) + sizeof(IS_INITIALIZED_VALUE);
// Try allocating with large pages first.
hMapFile = windows_try_with_large_page_priviliges(
[&](size_t largePageSize) {
const size_t total_size_aligned =
[&](usize largePageSize) {
const usize total_size_aligned =
(total_size + largePageSize - 1) / largePageSize * largePageSize;
#if defined(_WIN64)
@@ -530,12 +525,13 @@ struct SystemWideSharedConstant {
// Content is addressed by its hash. An additional discriminator can be added to account for differences
// that are not present in the content, for example NUMA node allocation.
SystemWideSharedConstant(const T& value, std::size_t discriminator = 0) {
std::size_t content_hash = std::hash<T>{}(value);
std::size_t executable_hash = hash_string(getExecutablePathHash());
SystemWideSharedConstant(const T& value, usize discriminator = 0) {
usize content_hash = std::hash<T>{}(value);
usize executable_hash = hash_string(getExecutablePathHash());
char buf[1024];
std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash, discriminator);
std::snprintf(buf, sizeof(buf), "Local\\sf_%zu$%zu$%zu", content_hash, executable_hash,
discriminator);
std::string shm_name = buf;
#if defined(__linux__) && !defined(__ANDROID__)
+45 -38
View File
@@ -20,7 +20,7 @@
#define SHM_LINUX_H_INCLUDED
#if !defined(__linux__) || defined(__ANDROID__)
#error shm_linux.h should not be included on this platform.
#error shm_linux.h should not be included on this platform.
#endif
#include <atomic>
@@ -54,23 +54,23 @@ namespace Stockfish::shm {
namespace detail {
struct ShmHeader {
static constexpr uint32_t SHM_MAGIC = 0xAD5F1A12;
pthread_mutex_t mutex;
std::atomic<uint32_t> ref_count{0};
std::atomic<bool> initialized{false};
uint32_t magic = SHM_MAGIC;
static constexpr u32 SHM_MAGIC = 0xAD5F1A12;
pthread_mutex_t mutex;
std::atomic<u32> ref_count{0};
std::atomic<bool> initialized{false};
u32 magic = SHM_MAGIC;
};
class SharedMemoryBase {
public:
virtual ~SharedMemoryBase() = default;
virtual void close(bool skip_unmap = false) noexcept = 0;
virtual const std::string& name() const noexcept = 0;
virtual ~SharedMemoryBase() = default;
virtual void close(bool skip_unmap = false) noexcept = 0;
virtual const std::string& name() const noexcept = 0;
};
class SharedMemoryRegistry {
private:
static std::mutex registry_mutex_;
static std::mutex registry_mutex_;
static std::vector<SharedMemoryBase*> active_instances_;
public:
@@ -82,7 +82,8 @@ class SharedMemoryRegistry {
static void unregister_instance(SharedMemoryBase* instance) {
std::scoped_lock lock(registry_mutex_);
active_instances_.erase(
std::remove(active_instances_.begin(), active_instances_.end(), instance), active_instances_.end());
std::remove(active_instances_.begin(), active_instances_.end(), instance),
active_instances_.end());
}
static void cleanup_all(bool skip_unmap = false) noexcept {
@@ -93,7 +94,7 @@ class SharedMemoryRegistry {
}
};
inline std::mutex SharedMemoryRegistry::registry_mutex_;
inline std::mutex SharedMemoryRegistry::registry_mutex_;
inline std::vector<SharedMemoryBase*> SharedMemoryRegistry::active_instances_;
class CleanupHooks {
@@ -140,23 +141,6 @@ class CleanupHooks {
inline std::once_flag CleanupHooks::register_once_;
inline int portable_fallocate(int fd, off_t offset, off_t length) {
#ifdef __APPLE__
fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, offset, length, 0};
int ret = fcntl(fd, F_PREALLOCATE, &store);
if (ret == -1)
{
store.fst_flags = F_ALLOCATEALL;
ret = fcntl(fd, F_PREALLOCATE, &store);
}
if (ret != -1)
ret = ftruncate(fd, offset + length);
return ret;
#else
return posix_fallocate(fd, offset, length);
#endif
}
} // namespace detail
template<typename T>
@@ -170,16 +154,16 @@ class SharedMemory: public detail::SharedMemoryBase {
void* mapped_ptr_ = nullptr;
T* data_ptr_ = nullptr;
detail::ShmHeader* header_ptr_ = nullptr;
size_t total_size_ = 0;
usize total_size_ = 0;
std::string sentinel_base_;
std::string sentinel_path_;
static constexpr size_t calculate_total_size() noexcept {
static constexpr usize calculate_total_size() noexcept {
return sizeof(T) + sizeof(detail::ShmHeader);
}
static std::string make_sentinel_base(const std::string& name) {
char buf[32];
char buf[32];
// Using std::to_string here causes non-deterministic PGO builds.
// snprintf, being part of libc, is insensitive to the formatted values.
std::snprintf(buf, sizeof(buf), "sfshm_%016" PRIu64, hash_string(name));
@@ -386,7 +370,7 @@ class SharedMemory: public detail::SharedMemoryBase {
[[nodiscard]] const T& operator*() const noexcept { return *data_ptr_; }
[[nodiscard]] uint32_t ref_count() const noexcept {
[[nodiscard]] u32 ref_count() const noexcept {
return header_ptr_ ? header_ptr_->ref_count.load(std::memory_order_acquire) : 0;
}
@@ -451,7 +435,7 @@ class SharedMemory: public detail::SharedMemoryBase {
if (!header_ptr_)
return;
uint32_t expected = header_ptr_->ref_count.load(std::memory_order_relaxed);
u32 expected = header_ptr_->ref_count.load(std::memory_order_relaxed);
while (expected != 0
&& !header_ptr_->ref_count.compare_exchange_weak(
expected, expected - 1, std::memory_order_acq_rel, std::memory_order_relaxed))
@@ -598,9 +582,6 @@ class SharedMemory: public detail::SharedMemoryBase {
if (ftruncate(fd_, static_cast<off_t>(total_size_)) == -1)
return false;
if (detail::portable_fallocate(fd_, 0, static_cast<off_t>(total_size_)) != 0)
return false;
mapped_ptr_ = mmap(nullptr, total_size_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0);
if (mapped_ptr_ == MAP_FAILED)
{
@@ -608,6 +589,28 @@ class SharedMemory: public detail::SharedMemoryBase {
return false;
}
#ifdef MADV_POPULATE_WRITE
// Pre-populate, attempting first with THP, to guarantee that the memory
// is allocated and avoid crashing on the first write.
madvise(mapped_ptr_, total_size_, MADV_HUGEPAGE);
const bool populated = madvise(mapped_ptr_, total_size_, MADV_POPULATE_WRITE) == 0;
#else
const bool populated = false;
#endif
// If the THP population failed, try with fallocate
if (!populated && posix_fallocate(fd_, 0, static_cast<off_t>(total_size_)) != 0)
{
// Release any partially populated pages by a failed MADV_POPULATE_WRITE.
// TODO: Not robust. Need to avoid residual pages if the process
// is terminated between the failed madvise and the ftruncate.
int err = ftruncate(fd_, 0);
(void) err;
munmap(mapped_ptr_, total_size_);
mapped_ptr_ = nullptr;
return false;
}
data_ptr_ = static_cast<T*>(mapped_ptr_);
header_ptr_ =
reinterpret_cast<detail::ShmHeader*>(static_cast<char*>(mapped_ptr_) + sizeof(T));
@@ -628,7 +631,7 @@ class SharedMemory: public detail::SharedMemoryBase {
struct stat st;
fstat(fd_, &st);
if (static_cast<size_t>(st.st_size) < total_size_)
if (static_cast<usize>(st.st_size) < total_size_)
{
invalid_header = true;
return false;
@@ -641,6 +644,10 @@ class SharedMemory: public detail::SharedMemoryBase {
return false;
}
#ifdef MADV_HUGEPAGE
madvise(mapped_ptr_, total_size_, MADV_HUGEPAGE);
#endif
data_ptr_ = static_cast<T*>(mapped_ptr_);
header_ptr_ = std::launder(
reinterpret_cast<detail::ShmHeader*>(static_cast<char*>(mapped_ptr_) + sizeof(T)));
+187 -119
View File
@@ -29,14 +29,17 @@
#include <initializer_list>
#include <iostream>
#include <mutex>
#include <optional>
#include <sstream>
#include <string_view>
#include <filesystem>
#include <sys/stat.h>
#include <type_traits>
#include <utility>
#include <vector>
#include <array>
#include "../attacks.h"
#include "../bitboard.h"
#include "../misc.h"
#include "../movegen.h"
@@ -57,6 +60,40 @@
#include <windows.h>
#endif
#ifdef NO_TABLEBASES
// Stubbed out impls
namespace Stockfish::Tablebases {
int MaxCardinality;
void init(const std::string&) {}
WDLScore probe_wdl(Position&, ProbeState* result) {
*result = FAIL;
return WDLDraw;
}
int probe_dtz(Position&, ProbeState* result) {
*result = FAIL;
return 0;
}
bool root_probe(Position&, Search::RootMoves&, bool, bool, const std::function<bool()>&) {
return false;
}
bool root_probe_wdl(Position&, Search::RootMoves&, bool) { return false; }
Config rank_root_moves(
const OptionsMap&, Position&, Search::RootMoves&, bool, const std::function<bool()>&) {
return Config{};
}
} // namespace Stockfish::Tablebases
#else
using namespace Stockfish::Tablebases;
int Stockfish::Tablebases::MaxCardinality;
@@ -113,12 +150,12 @@ template<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>
inline void swap_endian(T& x) {
static_assert(std::is_unsigned_v<T>, "Argument of swap_endian not unsigned");
uint8_t tmp, *c = (uint8_t*) &x;
u8 tmp, *c = (u8*) &x;
for (int i = 0; i < Half; ++i)
tmp = c[i], c[i] = c[End - i], c[End - i] = tmp;
}
template<>
inline void swap_endian<uint8_t>(uint8_t&) {}
inline void swap_endian<u8>(u8&) {}
template<typename T, int LE>
T number(void* addr) {
@@ -159,7 +196,7 @@ struct SparseEntry {
static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");
using Sym = uint16_t; // Huffman symbol
using Sym = u16; // Huffman symbol
struct LR {
enum Side {
@@ -167,9 +204,9 @@ struct LR {
Right
};
uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12
// bits is the right-hand symbol. If the symbol has length 1,
// then the left-hand symbol is the stored value.
u8 lr[3]; // The first 12 bits is the left-hand symbol, the second 12
// bits is the right-hand symbol. If the symbol has length 1,
// then the left-hand symbol is the stored value.
template<Side S>
Sym get() {
return S == Left ? ((lr[1] & 0xF) << 8) | lr[0]
@@ -191,7 +228,7 @@ static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes");
// time only existence of the file is checked.
class TBFile: public std::ifstream {
std::string fname;
std::filesystem::path fname;
public:
// Look for and open the file among the Paths directories where the .rtbw
@@ -200,21 +237,13 @@ class TBFile: public std::ifstream {
//
// Example:
// C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6
static std::string Paths;
static std::vector<std::filesystem::path> Paths;
TBFile(const std::string& f) {
#ifndef _WIN32
constexpr char SepChar = ':';
#else
constexpr char SepChar = ';';
#endif
std::stringstream ss(Paths);
std::string path;
while (std::getline(ss, path, SepChar))
for (const auto& path : Paths)
{
fname = path + "/" + f;
fname = path / std::filesystem::path(f);
std::ifstream::open(fname);
if (is_open())
return;
@@ -222,11 +251,11 @@ class TBFile: public std::ifstream {
}
// Memory map the file and check it.
uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {
u8* map(void** baseAddress, u64* mapping, TBType type) {
if (is_open())
close(); // Need to re-open to get native file descriptor
#ifndef _WIN32
#ifndef _WIN32
struct stat statbuf;
int fd = ::open(fname.c_str(), O_RDONLY);
@@ -237,25 +266,25 @@ class TBFile: public std::ifstream {
if (statbuf.st_size % 64 != 16)
{
std::cerr << "Corrupt tablebase file " << fname << std::endl;
std::cerr << "Corrupt tablebase file " << fname.string() << std::endl;
exit(EXIT_FAILURE);
}
*mapping = statbuf.st_size;
*baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);
#if defined(MADV_RANDOM)
#if defined(MADV_RANDOM)
madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);
#endif
#endif
::close(fd);
if (*baseAddress == MAP_FAILED)
{
std::cerr << "Could not mmap() " << fname << std::endl;
std::cerr << "Could not mmap() " << fname.string() << std::endl;
exit(EXIT_FAILURE);
}
#else
#else
// Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.
HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
HANDLE fd = CreateFileW(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);
if (fd == INVALID_HANDLE_VALUE)
@@ -266,7 +295,7 @@ class TBFile: public std::ifstream {
if (size_low % 64 != 16)
{
std::cerr << "Corrupt tablebase file " << fname << std::endl;
std::cerr << "Corrupt tablebase file " << fname.string() << std::endl;
exit(EXIT_FAILURE);
}
@@ -279,23 +308,23 @@ class TBFile: public std::ifstream {
exit(EXIT_FAILURE);
}
*mapping = uint64_t(mmap);
*mapping = u64(mmap);
*baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);
if (!*baseAddress)
{
std::cerr << "MapViewOfFile() failed, name = " << fname
std::cerr << "MapViewOfFile() failed, name = " << fname.string()
<< ", error = " << GetLastError() << std::endl;
exit(EXIT_FAILURE);
}
#endif
uint8_t* data = (uint8_t*) *baseAddress;
#endif
u8* data = (u8*) *baseAddress;
constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}};
constexpr u8 Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}};
if (memcmp(data, Magics[type == WDL], 4))
{
std::cerr << "Corrupted table in file " << fname << std::endl;
std::cerr << "Corrupted table in file " << fname.string() << std::endl;
unmap(*baseAddress, *mapping);
return *baseAddress = nullptr, nullptr;
}
@@ -303,44 +332,43 @@ class TBFile: public std::ifstream {
return data + 4; // Skip Magics's header
}
static void unmap(void* baseAddress, uint64_t mapping) {
static void unmap(void* baseAddress, u64 mapping) {
#ifndef _WIN32
#ifndef _WIN32
munmap(baseAddress, mapping);
#else
#else
UnmapViewOfFile(baseAddress);
CloseHandle((HANDLE) mapping);
#endif
#endif
}
};
std::string TBFile::Paths;
std::vector<std::filesystem::path> TBFile::Paths;
// struct PairsData contains low-level indexing information to access TB data.
// There are 8, 4, or 2 PairsData records for each TBTable, according to the type
// of table and if positions have pawns or not. It is populated at first access.
struct PairsData {
uint8_t flags; // Table flags, see enum TBFlag
uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols
uint8_t minSymLen; // Minimum length in bits of the Huffman symbols
uint32_t blocksNum; // Number of blocks in the TB file
size_t sizeofBlock; // Block size in bytes
size_t span; // About every span values there is a SparseIndex[] entry
Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value
LR* btree; // btree[sym] stores the left and right symbols that expand sym
uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536
uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum
SparseEntry* sparseIndex; // Partial indices into blockLength[]
size_t sparseIndexSize; // Size of SparseIndex[] table
uint8_t* data; // Start of Huffman compressed data
std::vector<uint64_t>
u8 flags; // Table flags, see enum TBFlag
u8 maxSymLen; // Maximum length in bits of the Huffman symbols
u8 minSymLen; // Minimum length in bits of the Huffman symbols
u32 blocksNum; // Number of blocks in the TB file
usize sizeofBlock; // Block size in bytes
usize span; // About every span values there is a SparseIndex[] entry
Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value
LR* btree; // btree[sym] stores the left and right symbols that expand sym
u16* blockLength; // Number of stored positions (minus one) for each block: 1..65536
u32 blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum
SparseEntry* sparseIndex; // Partial indices into blockLength[]
usize sparseIndexSize; // Size of SparseIndex[] table
u8* data; // Start of Huffman compressed data
std::vector<u64>
base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l
std::vector<uint8_t>
symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256
Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups
uint64_t groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces
int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1)
uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)
std::vector<u8> symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256
Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups
u64 groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces
int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1)
u16 map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)
};
// struct TBTable contains indexing information to access the corresponding TBFile.
@@ -355,14 +383,14 @@ struct TBTable {
std::atomic_bool ready;
void* baseAddress;
uint8_t* map;
uint64_t mapping;
u8* map;
u64 mapping;
Key key;
Key key2;
int pieceCount;
bool hasPawns;
bool hasUniquePieces;
uint8_t pawnCount[2]; // [Lead color / other color]
u8 pawnCount[2]; // [Lead color / other color]
PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0]
PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; }
@@ -386,7 +414,16 @@ TBTable<WDL>::TBTable(const std::string& code) :
StateInfo st;
Position pos;
key = pos.set(code, WHITE, &st).material_key();
auto err = pos.set(code, WHITE, &st);
// IMPORTANT: We cannot assert here because it WILL produce validation errors
// on some TB7 and higher positions due to the black king being attacked
// while white is to move. This is not fixable without significant changes.
// As using pos.set here is already a very hacky way to achieve the desired
// result here so we leave it for now. The validation checks that fail are
// done after the position is fully set up, so it's fine for now.
// assert(!err.has_value());
(void) err;
key = pos.material_key();
pieceCount = pos.count<ALL_PIECES>();
hasPawns = pos.pieces(PAWN);
@@ -404,7 +441,16 @@ TBTable<WDL>::TBTable(const std::string& code) :
pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);
pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);
key2 = pos.set(code, BLACK, &st).material_key();
err = pos.set(code, BLACK, &st);
// IMPORTANT: We cannot assert here because it WILL produce validation errors
// on some TB7 and higher positions due to the black king being attacked
// while white is to move. This is not fixable without significant changes.
// As using pos.set here is already a very hacky way to achieve the desired
// result here so we leave it for now. The validation checks that fail are
// done after the position is fully set up, so it's fine for now.
// assert(!err.has_value());
(void) err;
key2 = pos.material_key();
}
template<>
@@ -444,15 +490,15 @@ class TBTables {
std::deque<TBTable<WDL>> wdlTable;
std::deque<TBTable<DTZ>> dtzTable;
size_t foundDTZFiles = 0;
size_t foundWDLFiles = 0;
usize foundDTZFiles = 0;
usize foundWDLFiles = 0;
void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
uint32_t homeBucket = uint32_t(key) & (Size - 1);
Entry entry{key, wdl, dtz};
u32 homeBucket = u32(key) & (Size - 1);
Entry entry{key, wdl, dtz};
// Ensure last element is empty to avoid overflow when looking up
for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)
for (u32 bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)
{
Key otherKey = hashTable[bucket].key;
if (otherKey == key || !hashTable[bucket].get<WDL>())
@@ -463,7 +509,7 @@ class TBTables {
// Robin Hood hashing: If we've probed for longer than this element,
// insert here and search for a new spot for the other element instead.
uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1);
u32 otherHomeBucket = u32(otherKey) & (Size - 1);
if (otherHomeBucket > homeBucket)
{
std::swap(entry, hashTable[bucket]);
@@ -478,7 +524,7 @@ class TBTables {
public:
template<TBType Type>
TBTable<Type>* get(Key key) {
for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry)
for (const Entry* entry = &hashTable[u32(key) & (Size - 1)];; ++entry)
{
if (entry->key == key || !entry->get<Type>())
return entry->get<Type>();
@@ -553,7 +599,7 @@ void TBTables::add(const std::vector<PieceType>& pieces) {
// Huffman codes are the same for all blocks in the table. A non-symmetric pawnless TB file
// will have one table for wtm and one for btm, a TB file with pawns will have tables per
// file a,b,c,d also, in this case, one set for wtm and one for btm.
int decompress_pairs(PairsData* d, uint64_t idx) {
int decompress_pairs(PairsData* d, u64 idx) {
// Special case where all table positions store the same value
if (d->flags & TBFlag::SingleValue)
@@ -574,11 +620,11 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
// I(k) = k * d->span + d->span / 2 (1)
// First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)
uint32_t k = uint32_t(idx / d->span);
u32 k = u32(idx / d->span);
// Then we read the corresponding SparseIndex[] entry
uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
int offset = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);
u32 block = number<u32, LittleEndian>(&d->sparseIndex[k].block);
int offset = number<u16, LittleEndian>(&d->sparseIndex[k].offset);
// Now compute the difference idx - I(k). From the definition of k, we know that
//
@@ -599,12 +645,12 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
offset -= d->blockLength[block++] + 1;
// Finally, we find the start address of our block of canonical Huffman symbols
uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(block) * d->sizeofBlock));
u32* ptr = (u32*) (d->data + (u64(block) * d->sizeofBlock));
// Read the first 64 bits in our block, this is a (truncated) sequence of
// unknown number of symbols of unknown length but we know the first one
// is at the beginning of this 64-bit sequence.
uint64_t buf64 = number<uint64_t, BigEndian>(ptr);
u64 buf64 = number<u64, BigEndian>(ptr);
ptr += 2;
int buf64Size = 64;
Sym sym;
@@ -641,7 +687,7 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
if (buf64Size <= 32)
{ // Refill the buffer
buf64Size += 32;
buf64 |= uint64_t(number<uint32_t, BigEndian>(ptr++)) << (64 - buf64Size);
buf64 |= u64(number<u32, BigEndian>(ptr++)) << (64 - buf64Size);
}
}
@@ -689,12 +735,12 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
auto flags = entry->get(0, f)->flags;
uint8_t* map = entry->map;
uint16_t* idx = entry->get(0, f)->map_idx;
u8* map = entry->map;
u16* idx = entry->get(0, f)->map_idx;
if (flags & TBFlag::Mapped)
{
if (flags & TBFlag::Wide)
value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value];
value = ((u16*) map)[idx[WDLMap[wdl + 2]] + value];
else
value = map[idx[WDLMap[wdl + 2]] + value];
}
@@ -709,12 +755,12 @@ int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
return value + 1;
}
// A temporary fix for the compiler bug with vectorization. (#4450)
#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15
#define DISABLE_CLANG_LOOP_VEC _Pragma("clang loop vectorize(disable)")
#else
#define DISABLE_CLANG_LOOP_VEC
#endif
// A temporary fix for the compiler bug with vectorization. (#4450)
#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15
#define DISABLE_CLANG_LOOP_VEC _Pragma("clang loop vectorize(disable)")
#else
#define DISABLE_CLANG_LOOP_VEC
#endif
// Compute a unique index out of a position and use it to probe the TB file. To
// encode k pieces of the same type and color, first sort the pieces by square in
@@ -727,7 +773,7 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
Square squares[TBPIECES];
Piece pieces[TBPIECES];
uint64_t idx;
u64 idx;
int next = 0, size = 0, leadPawnsCnt = 0;
PairsData* d;
Bitboard b, leadPawns = 0;
@@ -924,14 +970,16 @@ encode_remaining:
while (d->groupLen[++next])
{
std::stable_sort(groupSq, groupSq + d->groupLen[next]);
uint64_t n = 0;
u64 n = 0;
// Map down a square if "comes later" than a square in the previous
// groups (similar to what was done earlier for leading group pieces).
for (int i = 0; i < d->groupLen[next]; ++i)
{
auto f = [&](Square s) { return groupSq[i] > s; };
auto adjust = std::count_if(squares, groupSq, f);
int adjust = 0;
DISABLE_CLANG_LOOP_VEC
for (Square* sq = squares; sq != groupSq; ++sq)
adjust += groupSq[i] > *sq;
n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns];
}
@@ -981,10 +1029,10 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
// pawns/pieces -> remaining pawns -> remaining pieces. In particular the
// first group is at order[0] position and the remaining pawns, when present,
// are at order[1] position.
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
int next = pp ? 2 : 1;
int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0);
uint64_t idx = 1;
bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides
int next = pp ? 2 : 1;
int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0);
u64 idx = 1;
for (int k = 0; next < n || k == order[0] || k == order[1]; ++k)
if (k == order[0]) // Leading pawns or pieces
@@ -1010,7 +1058,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
// In Recursive Pairing each symbol represents a pair of children symbols. So
// read d->btree[] symbols data and expand each one in his left and right child
// symbol until reaching the leaves that represent the symbol value.
uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
u8 set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
visited[s] = true; // We can set it now because tree is acyclic
Sym sr = d->btree[s].get<LR::Right>();
@@ -1029,7 +1077,7 @@ uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {
return d->symlen[sl] + d->symlen[sr] + 1;
}
uint8_t* set_sizes(PairsData* d, uint8_t* data) {
u8* set_sizes(PairsData* d, u8* data) {
d->flags = *data++;
@@ -1043,14 +1091,14 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
// groupLen[] is a zero-terminated list of group lengths, the last groupIdx[]
// element stores the biggest index that is the tb size.
uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen];
u64 tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen];
d->sizeofBlock = 1ULL << *data++;
d->span = 1ULL << *data++;
d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up
auto padding = number<uint8_t, LittleEndian>(data++);
d->blocksNum = number<uint32_t, LittleEndian>(data);
data += sizeof(uint32_t);
d->sparseIndexSize = usize((tbSize + d->span - 1) / d->span); // Round up
auto padding = number<u8, LittleEndian>(data++);
d->blocksNum = number<u32, LittleEndian>(data);
data += sizeof(u32);
d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
// does not point out of range.
d->maxSymLen = *data++;
@@ -1087,8 +1135,8 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits
data += base64_size * sizeof(Sym);
d->symlen.resize(number<uint16_t, LittleEndian>(data));
data += sizeof(uint16_t);
d->symlen.resize(number<u16, LittleEndian>(data));
data += sizeof(u16);
d->btree = (LR*) data;
// The compression scheme used is "Recursive Pairing", that replaces the most
@@ -1105,9 +1153,9 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1);
}
uint8_t* set_dtz_map(TBTable<WDL>&, uint8_t* data, File) { return data; }
u8* set_dtz_map(TBTable<WDL>&, u8* data, File) { return data; }
uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {
u8* set_dtz_map(TBTable<DTZ>& e, u8* data, File maxFile) {
e.map = data;
@@ -1121,15 +1169,15 @@ uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {
data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table
for (int i = 0; i < 4; ++i)
{ // Sequence like 3,x,x,x,1,x,0,2,x,x
e.get(0, f)->map_idx[i] = uint16_t((uint16_t*) data - (uint16_t*) e.map + 1);
data += 2 * number<uint16_t, LittleEndian>(data) + 2;
e.get(0, f)->map_idx[i] = u16((u16*) data - (u16*) e.map + 1);
data += 2 * number<u16, LittleEndian>(data) + 2;
}
}
else
{
for (int i = 0; i < 4; ++i)
{
e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1);
e.get(0, f)->map_idx[i] = u16(data - e.map + 1);
data += *data + 1;
}
}
@@ -1142,7 +1190,7 @@ uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {
// Populate entry's PairsData records with data from the just memory-mapped file.
// Called at first access.
template<typename T>
void set(T& e, uint8_t* data) {
void set(T& e, u8* data) {
PairsData* d;
@@ -1199,14 +1247,14 @@ void set(T& e, uint8_t* data) {
for (File f = FILE_A; f <= maxFile; ++f)
for (int i = 0; i < sides; i++)
{
(d = e.get(i, f))->blockLength = (uint16_t*) data;
data += d->blockLengthSize * sizeof(uint16_t);
(d = e.get(i, f))->blockLength = (u16*) data;
data += d->blockLengthSize * sizeof(u16);
}
for (File f = FILE_A; f <= maxFile; ++f)
for (int i = 0; i < sides; i++)
{
data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment
data = (u8*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment
(d = e.get(i, f))->data = data;
data += d->blocksNum * d->sizeofBlock;
}
@@ -1244,7 +1292,7 @@ void* mapped(TBTable<Type>& e, const Position& pos) {
fname =
(e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? ".rtbw" : ".rtbz");
uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type);
u8* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type);
if (data)
set(e, data);
@@ -1286,8 +1334,8 @@ WDLScore search(Position& pos, ProbeState* result) {
WDLScore value, bestValue = WDLLoss;
StateInfo st;
auto moveList = MoveList<LEGAL>(pos);
size_t totalCount = moveList.size(), moveCount = 0;
auto moveList = MoveList<LEGAL>(pos);
usize totalCount = moveList.size(), moveCount = 0;
for (const Move move : moveList)
{
@@ -1350,9 +1398,24 @@ void Tablebases::init(const std::string& paths) {
TBTables.clear();
MaxCardinality = 0;
TBFile::Paths = paths;
TBFile::Paths.clear();
if (paths.empty())
if (!paths.empty())
{
#ifndef _WIN32
constexpr char SepChar = ':';
#else
constexpr char SepChar = ';';
#endif
std::stringstream ss(paths);
std::string path;
while (std::getline(ss, path, SepChar))
if (!path.empty())
TBFile::Paths.emplace_back(path_from_utf8(path));
}
if (TBFile::Paths.empty())
return;
// MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27
@@ -1385,7 +1448,7 @@ void Tablebases::init(const std::string& paths) {
if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0
{
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
if ((PseudoAttacks[KING][s1] | s1) & s2)
if ((Attacks::PseudoAttacks[KING][s1] | s1) & s2)
continue; // Illegal position
else if (!off_A1H8(s1) && off_A1H8(s2) > 0)
@@ -1739,8 +1802,11 @@ Config Tablebases::rank_root_moves(const OptionsMap& options,
config.probeDepth = 0;
}
if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
if (config.cardinality >= pos.count<ALL_PIECES>() && !pos.can_castle(ANY_CASTLING))
{
// Use DTZ to rank the moves if checkmate is the only zeroing move
rankDTZ = rankDTZ || pos.dtz_is_dtm();
// Rank moves using DTZ tables, bail out if time_abort flags zeitnot
config.rootInTB =
root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ, time_abort);
@@ -1774,3 +1840,5 @@ Config Tablebases::rank_root_moves(const OptionsMap& options,
return config;
}
} // namespace Stockfish
#endif // NO_TABLEBASES
+61 -60
View File
@@ -20,11 +20,14 @@
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <deque>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include "bitboard.h"
@@ -44,9 +47,9 @@ namespace Stockfish {
// in idle_loop(). Note that 'searching' and 'exit' should be already set.
Thread::Thread(Search::SharedState& sharedState,
std::unique_ptr<Search::ISearchManager> sm,
size_t n,
size_t numaN,
size_t totalNumaCount,
usize n,
usize numaN,
usize totalNumaCount,
OptionalThreadToNumaNodeBinder binder) :
idx(n),
idxInNuma(numaN),
@@ -138,10 +141,10 @@ void Thread::idle_loop() {
Search::SearchManager* ThreadPool::main_manager() { return main_thread()->worker->main_manager(); }
uint64_t ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); }
uint64_t ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); }
u64 ThreadPool::nodes_searched() const { return accumulate(&Search::Worker::nodes); }
u64 ThreadPool::tb_hits() const { return accumulate(&Search::Worker::tbHits); }
static size_t next_power_of_two(uint64_t count) { return count > 1 ? (2ULL << msb(count - 1)) : 1; }
static usize next_power_of_two(u64 count) { return count > 1 ? (2ULL << msb(count - 1)) : 1; }
// Creates/destroys threads to match the requested number.
// Created and launched threads will immediately go to sleep in idle_loop.
@@ -159,7 +162,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
boundThreadToNumaNode.clear();
}
const size_t requested = sharedState.options["Threads"];
const usize requested = sharedState.options["Threads"];
if (requested > 0) // create new thread(s)
{
@@ -181,7 +184,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
return true;
}();
std::map<NumaIndex, size_t> counts;
std::map<NumaIndex, usize> counts;
boundThreadToNumaNode = doBindThreads
? numaConfig.distribute_threads_among_numa_nodes(requested)
: std::vector<NumaIndex>{};
@@ -190,7 +193,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
counts[0] = requested; // Pretend all threads are part of numa node 0
else
{
for (size_t i = 0; i < boundThreadToNumaNode.size(); ++i)
for (usize i = 0; i < boundThreadToNumaNode.size(); ++i)
counts[boundThreadToNumaNode[i]]++;
}
@@ -198,7 +201,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
for (auto pair : counts)
{
NumaIndex numaIndex = pair.first;
uint64_t count = pair.second;
u64 count = pair.second;
auto f = [&]() {
sharedState.sharedHistories.try_emplace(numaIndex, next_power_of_two(count));
};
@@ -213,7 +216,7 @@ void ThreadPool::set(const NumaConfig& numaConfig,
while (threads.size() < requested)
{
const size_t threadId = threads.size();
const usize threadId = threads.size();
const NumaIndex numaId = doBindThreads ? boundThreadToNumaNode[threadId] : 0;
auto create_thread = [&]() {
auto manager = threadId == 0
@@ -268,17 +271,17 @@ void ThreadPool::clear() {
main_manager()->tm.clear();
}
void ThreadPool::run_on_thread(size_t threadId, std::function<void()> f) {
void ThreadPool::run_on_thread(usize threadId, std::function<void()> f) {
assert(threads.size() > threadId);
threads[threadId]->run_custom_job(std::move(f));
}
void ThreadPool::wait_on_thread(size_t threadId) {
void ThreadPool::wait_on_thread(usize threadId) {
assert(threads.size() > threadId);
threads[threadId]->wait_for_search_finished();
}
size_t ThreadPool::num_threads() const { return threads.size(); }
usize ThreadPool::num_threads() const { return threads.size(); }
// Wakes up main thread waiting in idle_loop() and returns immediately.
@@ -290,8 +293,8 @@ void ThreadPool::start_thinking(const OptionsMap& options,
main_thread()->wait_for_search_finished();
main_manager()->stopOnPonderhit = stop = abortedSearch = false;
main_manager()->ponder = limits.ponderMode;
main_manager()->stopOnPonderhit = stop = false;
main_manager()->ponder = limits.ponderMode;
increaseDepth = true;
@@ -330,8 +333,8 @@ void ThreadPool::start_thinking(const OptionsMap& options,
th->worker->limits = limits;
th->worker->nodes = th->worker->tbHits = th->worker->bestMoveChanges = 0;
th->worker->nmpMinPly = 0;
th->worker->rootDepth = th->worker->completedDepth = 0;
th->worker->rootMoves = rootMoves;
th->worker->rootDepth = 0;
th->worker->rootMoves = rootMoves;
th->worker->rootPos.set(pos.fen(), pos.is_chess960(), &th->worker->rootState);
th->worker->rootState = setupStates->back();
th->worker->tbConfig = tbConfig;
@@ -347,63 +350,50 @@ void ThreadPool::start_thinking(const OptionsMap& options,
Thread* ThreadPool::get_best_thread() const {
Thread* bestThread = threads.front().get();
Value minScore = VALUE_NONE;
Value minScore = VALUE_INFINITE;
std::unordered_map<Move, int64_t, Move::MoveHash> votes(
std::unordered_map<Move, i64, Move::MoveHash> votes(
2 * std::min(size(), bestThread->worker->rootMoves.size()));
// Find the minimum score of all threads
for (auto&& th : threads)
minScore = std::min(minScore, th->worker->rootMoves[0].score);
// Vote according to score and depth, and select the best thread
auto thread_voting_value = [minScore](Thread* th) {
return (th->worker->rootMoves[0].score - minScore + 14) * int(th->worker->completedDepth);
};
// Vote according to score, and select the best thread
for (auto&& th : threads)
votes[th->worker->rootMoves[0].pv[0]] += thread_voting_value(th.get());
votes[th->worker->rootMoves[0].pv[0]] += th->worker->rootMoves[0].score - minScore + 14;
for (auto&& th : threads)
{
const auto bestThreadScore = bestThread->worker->rootMoves[0].score;
const auto newThreadScore = th->worker->rootMoves[0].score;
const auto& bestThreadMove = bestThread->worker->rootMoves[0];
const auto& newThreadMove = th->worker->rootMoves[0];
const auto& bestThreadPV = bestThread->worker->rootMoves[0].pv;
const auto& newThreadPV = th->worker->rootMoves[0].pv;
const auto bestThreadMoveVote = votes[bestThreadMove.pv[0]];
const auto newThreadMoveVote = votes[newThreadMove.pv[0]];
const auto bestThreadMoveVote = votes[bestThreadPV[0]];
const auto newThreadMoveVote = votes[newThreadPV[0]];
// Aborted (d1) searches may lead to inexact win (or loss) scores.
const bool bestThreadDecisive = bestThreadMove.score != -VALUE_INFINITE
&& is_decisive(bestThreadMove.score)
&& !bestThreadMove.score_is_bound();
const bool newThreadDecisive = newThreadMove.score != -VALUE_INFINITE
&& is_decisive(newThreadMove.score)
&& !newThreadMove.score_is_bound();
const bool bestThreadInProvenWin = is_win(bestThreadScore);
const bool newThreadInProvenWin = is_win(newThreadScore);
const bool bestThreadInProvenLoss =
bestThreadScore != -VALUE_INFINITE && is_loss(bestThreadScore);
const bool newThreadInProvenLoss =
newThreadScore != -VALUE_INFINITE && is_loss(newThreadScore);
// We make sure not to pick a thread with truncated principal variation
const bool betterVotingValue =
thread_voting_value(th.get()) * int(newThreadPV.size() > 2)
> thread_voting_value(bestThread) * int(bestThreadPV.size() > 2);
if (bestThreadInProvenWin)
if (bestThreadDecisive)
{
// Make sure we pick the shortest mate / TB conversion
if (newThreadScore > bestThreadScore)
// Make sure we pick the shortest mate / TB conversion.
if (newThreadDecisive && std::abs(newThreadMove.score) > std::abs(bestThreadMove.score))
{
assert((is_win(bestThreadMove.score) && is_win(newThreadMove.score))
|| (is_loss(bestThreadMove.score) && is_loss(newThreadMove.score)));
bestThread = th.get();
}
}
else if (bestThreadInProvenLoss)
{
// Make sure we pick the shortest mated / TB conversion
if (newThreadInProvenLoss && newThreadScore < bestThreadScore)
bestThread = th.get();
}
else if (newThreadInProvenWin || newThreadInProvenLoss
|| (!is_loss(newThreadScore)
else if (newThreadDecisive
|| (!is_loss(newThreadMove.score)
&& (newThreadMoveVote > bestThreadMoveVote
|| (newThreadMoveVote == bestThreadMoveVote && betterVotingValue))))
|| (newThreadMoveVote == bestThreadMoveVote
&& newThreadMove.pv.size() > bestThreadMove.pv.size()))))
bestThread = th.get();
}
@@ -429,8 +419,12 @@ void ThreadPool::wait_for_search_finished() const {
th->wait_for_search_finished();
}
std::vector<size_t> ThreadPool::get_bound_thread_count_by_numa_node() const {
std::vector<size_t> counts;
std::vector<usize> ThreadPool::get_bound_thread_to_numa_node() const {
return boundThreadToNumaNode;
}
std::vector<usize> ThreadPool::get_bound_thread_count_by_numa_node() const {
std::vector<usize> counts;
if (!boundThreadToNumaNode.empty())
{
@@ -448,6 +442,13 @@ std::vector<size_t> ThreadPool::get_bound_thread_count_by_numa_node() const {
return counts;
}
usize ThreadPool::numa_nodes() const {
std::unordered_set<usize> seen;
for (NumaIndex n : boundThreadToNumaNode)
seen.insert(n);
return std::max(seen.size(), usize(1));
}
void ThreadPool::ensure_network_replicated() {
for (auto&& th : threads)
th->ensure_network_replicated();
+24 -23
View File
@@ -21,18 +21,17 @@
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <vector>
#include "memory.h"
#include "misc.h"
#include "numa.h"
#include "position.h"
#include "search.h"
#include "thread_win32_osx.h"
#include "thread_native.h"
namespace Stockfish {
@@ -75,9 +74,9 @@ class Thread {
public:
Thread(Search::SharedState&,
std::unique_ptr<Search::ISearchManager>,
size_t,
size_t,
size_t,
usize,
usize,
usize,
OptionalThreadToNumaNodeBinder);
virtual ~Thread();
@@ -93,8 +92,8 @@ class Thread {
// require further work to make them properly generic while maintaining
// appropriate specificity regarding search, from the point of view of an
// outside user, so renaming of this function is left for whenever that happens.
void wait_for_search_finished();
size_t id() const { return idx; }
void wait_for_search_finished();
usize id() const { return idx; }
LargePagePtr<Search::Worker> worker;
std::function<void()> jobFunc;
@@ -102,7 +101,7 @@ class Thread {
private:
std::mutex mutex;
std::condition_variable cv;
size_t idx, idxInNuma, totalNuma, nthreads;
usize idx, idxInNuma, totalNuma, nthreads;
bool exit = false, searching = true; // Set before starting std::thread
NativeThread stdThread;
NumaReplicatedAccessToken numaAccessToken;
@@ -132,28 +131,30 @@ class ThreadPool {
ThreadPool& operator=(const ThreadPool&) = delete;
ThreadPool& operator=(ThreadPool&&) = delete;
void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType);
void run_on_thread(size_t threadId, std::function<void()> f);
void wait_on_thread(size_t threadId);
size_t num_threads() const;
void clear();
void set(const NumaConfig& numaConfig,
Search::SharedState,
const Search::SearchManager::UpdateContext&);
void start_thinking(const OptionsMap&, Position&, StateListPtr&, Search::LimitsType);
void run_on_thread(usize threadId, std::function<void()> f);
void wait_on_thread(usize threadId);
usize num_threads() const;
void clear();
void set(const NumaConfig& numaConfig,
Search::SharedState,
const Search::SearchManager::UpdateContext&);
Search::SearchManager* main_manager();
Thread* main_thread() const { return threads.front().get(); }
uint64_t nodes_searched() const;
uint64_t tb_hits() const;
u64 nodes_searched() const;
u64 tb_hits() const;
Thread* get_best_thread() const;
void start_searching();
void wait_for_search_finished() const;
std::vector<size_t> get_bound_thread_count_by_numa_node() const;
std::vector<usize> get_bound_thread_to_numa_node() const;
std::vector<usize> get_bound_thread_count_by_numa_node() const;
usize numa_nodes() const;
void ensure_network_replicated();
std::atomic_bool stop, abortedSearch, increaseDepth;
std::atomic_bool stop, increaseDepth;
auto cbegin() const noexcept { return threads.cbegin(); }
auto begin() noexcept { return threads.begin(); }
@@ -167,9 +168,9 @@ class ThreadPool {
std::vector<std::unique_ptr<Thread>> threads;
std::vector<NumaIndex> boundThreadToNumaNode;
uint64_t accumulate(std::atomic<uint64_t> Search::Worker::* member) const {
u64 accumulate(RelaxedAtomic<u64> Search::Worker::* member) const {
uint64_t sum = 0;
u64 sum = 0;
for (auto&& th : threads)
sum += (th->worker.get()->*member).load(std::memory_order_relaxed);
return sum;
+25 -21
View File
@@ -16,10 +16,29 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef THREAD_WIN32_OSX_H_INCLUDED
#define THREAD_WIN32_OSX_H_INCLUDED
#ifndef THREAD_NATIVE_H_INCLUDED
#define THREAD_NATIVE_H_INCLUDED
#include <thread>
#ifdef _MSC_VER
#include <thread>
#else
#include <pthread.h>
#include <functional>
#include <utility>
#include "misc.h"
#endif
namespace Stockfish {
#ifdef _MSC_VER
// MSVC-compatible toolchains use std::thread because they do not provide
// pthreads by default. On all other platforms, pthreads is required and used.
using NativeThread = std::thread;
#else
// On OSX threads other than the main thread are created with a reduced stack
// size of 512KB by default, this is too low for deep searches, which require
@@ -27,17 +46,10 @@
// The implementation calls pthread_create() with the stack size parameter
// equal to the Linux 8MB default, on platforms that support it.
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
#include <pthread.h>
#include <functional>
namespace Stockfish {
class NativeThread {
pthread_t thread;
static constexpr size_t TH_STACK_SIZE = 8 * 1024 * 1024;
static constexpr usize TH_STACK_SIZE = 8 * 1024 * 1024;
public:
template<class Function, class... Args>
@@ -63,16 +75,8 @@ class NativeThread {
void join() { pthread_join(thread, nullptr); }
};
} // namespace Stockfish
#else // Default case: use STL classes
namespace Stockfish {
using NativeThread = std::thread;
#endif // _MSC_VER
} // namespace Stockfish
#endif
#endif // #ifndef THREAD_WIN32_OSX_H_INCLUDED
#endif // #ifndef THREAD_NATIVE_H_INCLUDED
+20 -23
View File
@@ -21,7 +21,6 @@
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdint>
#include "search.h"
#include "ucioption.h"
@@ -35,9 +34,9 @@ void TimeManagement::clear() {
availableNodes = -1; // When in 'nodes as time' mode
}
void TimeManagement::advance_nodes_time(std::int64_t nodes) {
void TimeManagement::advance_nodes_time(i64 nodes) {
assert(useNodesTime);
availableNodes = std::max(int64_t(0), availableNodes - nodes);
availableNodes = std::max(i64(0), availableNodes - nodes);
}
// Called at the beginning of the search and calculates
@@ -81,23 +80,21 @@ void TimeManagement::init(Search::LimitsType& limits,
moveOverhead *= npmsec;
}
// These numbers are used where multiplications, divisions or comparisons
// with constants are involved.
const int64_t scaleFactor = useNodesTime ? npmsec : 1;
// These numbers are used where multiplications, divisions,
// or comparisons with constants are involved.
const i64 scaleFactor = useNodesTime ? npmsec : 1;
const TimePoint scaledTime = limits.time[us] / scaleFactor;
// Maximum move horizon
int centiMTG = limits.movestogo ? std::min(limits.movestogo * 100, 5000) : 5051;
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
// If less than one second, gradually reduce mtg
if (scaledTime < 1000)
centiMTG = int(scaledTime * 5.051);
mtg = int(scaledTime * 0.05);
// Make sure timeLeft is > 0 since we may use it as a divisor
TimePoint timeLeft =
std::max(TimePoint(1),
limits.time[us]
+ (limits.inc[us] * (centiMTG - 100) - moveOverhead * (200 + centiMTG)) / 100);
TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1)
- moveOverhead * (2 + mtg));
// x basetime (+ z increment)
// If there is a healthy increment, timeLeft can exceed the actual available
@@ -106,32 +103,32 @@ void TimeManagement::init(Search::LimitsType& limits,
{
// Extra time according to timeLeft
if (originalTimeAdjust < 0)
originalTimeAdjust = 0.3128 * std::log10(timeLeft) - 0.4354;
originalTimeAdjust = 0.3272 * std::log10(timeLeft) - 0.4141;
// Calculate time constants based on current time left.
double logTimeInSec = std::log10(scaledTime / 1000.0);
double optConstant = std::min(0.0032116 + 0.000321123 * logTimeInSec, 0.00508017);
double maxConstant = std::max(3.3977 + 3.03950 * logTimeInSec, 2.94761);
double optConstant = std::min(0.0029869 + 0.00033554 * logTimeInSec, 0.004905);
double maxConstant = std::max(3.3744 + 3.0608 * logTimeInSec, 3.1441);
optScale = std::min(0.0121431 + std::pow(ply + 2.94693, 0.461073) * optConstant,
0.213035 * limits.time[us] / timeLeft)
optScale = std::min(0.012112 + std::pow(ply + 3.22713, 0.46866) * optConstant,
0.19404 * limits.time[us] / timeLeft)
* originalTimeAdjust;
maxScale = std::min(6.67704, maxConstant + ply / 11.9847);
maxScale = std::min(6.873, maxConstant + ply / 12.352);
}
// x moves in y seconds (+ z increment)
else
{
optScale =
std::min((0.88 + ply / 116.4) / (centiMTG / 100.0), 0.88 * limits.time[us] / timeLeft);
maxScale = 1.3 + 0.11 * (centiMTG / 100.0);
optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / timeLeft);
maxScale = 1.3 + 0.11 * mtg;
}
// Limit the maximum possible time for this move
optimumTime = TimePoint(optScale * timeLeft);
optimumTime = TimePoint(std::max(1.0, optScale * timeLeft));
maximumTime =
TimePoint(std::min(0.825179 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
TimePoint(std::max(double(optimumTime), std::min(0.8097 * limits.time[us] - moveOverhead,
maxScale * optimumTime)));
if (options["Ponder"])
optimumTime += optimumTime / 4;
+4 -5
View File
@@ -19,14 +19,13 @@
#ifndef TIMEMAN_H_INCLUDED
#define TIMEMAN_H_INCLUDED
#include <cstdint>
#include "misc.h"
namespace Stockfish {
class OptionsMap;
enum Color : uint8_t;
enum Color : u8;
namespace Search {
struct LimitsType;
@@ -51,15 +50,15 @@ class TimeManagement {
TimePoint elapsed_time() const { return now() - startTime; };
void clear();
void advance_nodes_time(std::int64_t nodes);
void advance_nodes_time(i64 nodes);
private:
TimePoint startTime;
TimePoint optimumTime;
TimePoint maximumTime;
std::int64_t availableNodes = -1; // When in 'nodes as time' mode
bool useNodesTime = false; // True if we are in 'nodes as time' mode
i64 availableNodes = -1; // When in 'nodes as time' mode
bool useNodesTime = false; // True if we are in 'nodes as time' mode
};
} // namespace Stockfish
+115 -85
View File
@@ -18,11 +18,13 @@
#include "tt.h"
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <numeric>
#include <vector>
#include "memory.h"
#include "misc.h"
@@ -32,94 +34,101 @@
namespace Stockfish {
// TTEntry struct is the 10 bytes transposition table entry, defined as below:
// TTEntry struct is the 10 bytes transposition table entry, defined as:
//
// key 16 bit
// depth 8 bit
// generation 5 bit
// pv node 1 bit
// bound type 2 bit
// generation 5 bit
// move 16 bit
// value 16 bit
// evaluation 16 bit
//
// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially.
// Equally, the store order in save() matches this order.
//
// We use `bool(depth8)` as the cheap internal occupancy check, corresponding to `depth == DEPTH_NONE`
// externally, so we offset the internal depth by DEPTH_NONE.
//
// Pv, bound and generation are packed in a single byte.
static constexpr u8 GENERATION_BITS = 5;
static constexpr u8 GENERATION_MASK = (1 << GENERATION_BITS) - 1;
static constexpr u8 BOUND_SHIFT = GENERATION_BITS;
static constexpr u8 BOUND_MASK = 0b11 << BOUND_SHIFT;
static constexpr u8 PV_SHIFT = BOUND_SHIFT + 2;
static constexpr u8 PV_MASK = 1 << PV_SHIFT;
struct TTEntry {
// Convert internal bitfields to external types
TTData read() const {
return TTData{Move(move16), Value(value16),
Value(eval16), Depth(depth8 + DEPTH_ENTRY_OFFSET),
Bound(genBound8 & 0x3), bool(genBound8 & 0x4)};
return TTData{Move(move16),
Value(value16),
Value(eval16),
Depth(DEPTH_NONE + depth8),
Bound((genBound8 & BOUND_MASK) >> BOUND_SHIFT),
bool(genBound8 & PV_MASK)};
}
bool is_occupied() const;
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
// The returned age is a multiple of TranspositionTable::GENERATION_DELTA
uint8_t relative_age(const uint8_t generation8) const;
bool is_occupied() const { return bool(depth8); };
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, u8 curr_generation);
u8 relative_age(const u8 curr_generation) const;
private:
friend class TranspositionTable;
friend struct TTWriter;
uint16_t key16;
uint8_t depth8;
uint8_t genBound8;
Move move16;
int16_t value16;
int16_t eval16;
RelaxedAtomic<u16> key16;
RelaxedAtomic<u8> depth8;
RelaxedAtomic<u8> genBound8;
RelaxedAtomic<Move> move16;
RelaxedAtomic<i16> value16;
RelaxedAtomic<i16> eval16;
};
// `genBound8` is where most of the details are. We use the following constants to manipulate 5 leading generation bits
// and 3 trailing miscellaneous bits.
// These bits are reserved for other things.
static constexpr unsigned GENERATION_BITS = 3;
// increment for generation field
static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS);
// cycle length
static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA;
// mask to pull out generation number
static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF;
// DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but
// 2) we need to store negative depths for QS. (`depth8` is the only field with "spare bits":
// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted in `save`.)
bool TTEntry::is_occupied() const { return bool(depth8); }
// Populates the TTEntry with a new node's data, possibly
// overwriting an old position. The update is not atomic and can be racy.
// overwriting an old position. The update is non-atomic and can be racy.
void TTEntry::save(
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, u8 curr_generation) {
// Preserve the old ttmove if we don't have a new one
if (m || uint16_t(k) != key16)
if (m || u16(k) != key16)
move16 = m;
// Overwrite less valuable entries (cheapest checks first)
if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_ENTRY_OFFSET + 2 * pv > depth8 - 4
|| relative_age(generation8))
if (b == BOUND_EXACT || u16(k) != key16 || d - DEPTH_NONE + 2 * pv > depth8 - 4
|| relative_age(curr_generation))
{
assert(d > DEPTH_ENTRY_OFFSET);
assert(d < 256 + DEPTH_ENTRY_OFFSET);
assert(d > DEPTH_NONE);
assert(d - DEPTH_NONE < 256);
assert(curr_generation <= GENERATION_MASK); // TT::new_search() plays nice
key16 = uint16_t(k);
depth8 = uint8_t(d - DEPTH_ENTRY_OFFSET);
genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b);
value16 = int16_t(v);
eval16 = int16_t(ev);
key16 = u16(k);
depth8 = u8(d - DEPTH_NONE);
genBound8 = u8(curr_generation | b << BOUND_SHIFT | u8(pv) << PV_SHIFT);
value16 = i16(v);
eval16 = i16(ev);
}
// Secondary aging. Important for elementary mate finding.
// (*Scaler) Secondary aging on entries relevant to singular extensions
// generally scales poorly and requires VVLTC verification.
else if (depth8 + DEPTH_NONE >= 5
&& Bound((genBound8 & BOUND_MASK) >> BOUND_SHIFT) != BOUND_EXACT)
{
auto v16 = value16;
if (std::abs(v16) < VALUE_INFINITE && is_decisive(v16))
depth8 = std::max(int(depth8) - 1,
0); // guard against racy underflows, default to "unoccupied"
}
}
uint8_t TTEntry::relative_age(const uint8_t generation8) const {
// Due to our packed storage format for generation and its cyclic
// nature we add GENERATION_CYCLE (256 is the modulus, plus what
// is needed to keep the unrelated lowest n bits from affecting
// the result) to calculate the entry age correctly even after
// generation8 overflows into the next cycle.
return (GENERATION_CYCLE + generation8 - genBound8) & GENERATION_MASK;
u8 TTEntry::relative_age(const u8 curr_generation) const {
// Returns this entry's age. We count generations like clocks count hours,
// i.e. we require 0 - 1 == 31. Unsigned subtraction guarantees the required
// borrowing regardless of the upper pv/bound bits.
return (curr_generation - genBound8) & GENERATION_MASK;
}
@@ -128,8 +137,13 @@ TTWriter::TTWriter(TTEntry* tte) :
entry(tte) {}
void TTWriter::write(
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {
entry->save(k, v, pv, b, d, m, ev, generation8);
Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, u8 curr_generation) {
entry->save(k, v, pv, b, d, m, ev, curr_generation);
}
void TTWriter::penalize(int penalty) {
// guard against racy underflows, default to "unoccupied"
entry->depth8 = std::max(int(entry->depth8) - penalty, 0);
}
@@ -150,12 +164,17 @@ static_assert(sizeof(Cluster) == 32, "Suboptimal Cluster size");
// Sets the size of the transposition table,
// measured in megabytes. Transposition table consists
// of clusters and each cluster consists of ClusterSize number of TTEntry.
void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) {
void TranspositionTable::resize(usize mbSize, ThreadPool& threads) {
aligned_large_pages_free(table);
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);
usize ttBytes = clusterCount * sizeof(Cluster);
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
// Request 1GB pages if we'd get at least eight per NUMA node, to avoid
// memory oversubscription
bool hugePageHint = ttBytes >= threads.numa_nodes() * HugePageSize * 8;
table = static_cast<Cluster*>(aligned_large_pages_alloc_with_hint(ttBytes, hugePageHint));
if (!table)
{
@@ -170,60 +189,72 @@ void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) {
// Initializes the entire transposition table to zero,
// in a multi-threaded way.
void TranspositionTable::clear(ThreadPool& threads) {
generation8 = 0;
const size_t threadCount = threads.num_threads();
generation8 = 0;
const usize threadCount = threads.num_threads();
for (size_t i = 0; i < threadCount; ++i)
std::vector<usize> threadToNuma = threads.get_bound_thread_to_numa_node();
std::vector<usize> order(threadCount);
std::iota(order.begin(), order.end(), 0);
// To promote good NUMA distribution (esp. with huge pages), we permute threads so that
// all threads in a NUMA node clear a contiguous region of the TT.
if (threadToNuma.size() == threadCount)
{
threads.run_on_thread(i, [this, i, threadCount]() {
// Each thread will zero its part of the hash table
const size_t stride = clusterCount / threadCount;
const size_t start = stride * i;
const size_t len = i + 1 != threadCount ? stride : clusterCount - start;
std::memset(&table[start], 0, len * sizeof(Cluster));
std::stable_sort(order.begin(), order.end(), [&threadToNuma](usize t1, usize t2) {
return threadToNuma.at(t1) < threadToNuma.at(t2);
});
}
for (size_t i = 0; i < threadCount; ++i)
for (usize i = 0; i < threadCount; ++i)
{
threads.run_on_thread(order[i], [this, i, threadCount]() {
// Each thread will zero its part of the hash table
const usize stride = clusterCount / threadCount;
const usize start = stride * i;
const usize len = i + 1 != threadCount ? stride : clusterCount - start;
std::memset(static_cast<void*>(&table[start]), 0, len * sizeof(Cluster));
});
}
for (usize i = 0; i < threadCount; ++i)
threads.wait_on_thread(i);
}
// Returns an approximation of the hashtable
// occupation during a search. The hash is x permill full, as per UCI protocol.
// Only counts entries which match the current generation.
// Only counts entries which are younger than maxAge.
int TranspositionTable::hashfull(int maxAge) const {
int maxAgeInternal = maxAge << GENERATION_BITS;
int cnt = 0;
int cnt = 0;
for (int i = 0; i < 1000; ++i)
for (int j = 0; j < ClusterSize; ++j)
cnt += table[i].entry[j].is_occupied()
&& table[i].entry[j].relative_age(generation8) <= maxAgeInternal;
&& table[i].entry[j].relative_age(generation8) <= maxAge;
return cnt / ClusterSize;
}
void TranspositionTable::new_search() {
// increment by delta to keep lower bits as is
generation8 += GENERATION_DELTA;
++generation8;
// Don't overflow into the other bits of TTEntry::genBound8
generation8 &= GENERATION_MASK;
}
uint8_t TranspositionTable::generation() const { return generation8; }
u8 TranspositionTable::generation() const { return generation8; }
// Looks up the current position in the transposition
// table. It returns true if the position is found.
// Looks up the current position in the transposition table.
// It returns true if the key is found (which may be a collision), and has non-null data.
// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry
// to be replaced later. The replace value of an entry is calculated as its depth
// minus 8 times its relative age. TTEntry t1 is considered more valuable than
// TTEntry t2 if its replace value is greater than that of t2.
// to be replaced later. The value of an entry is its depth minus 8 times its relative age.
std::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) const {
TTEntry* const tte = first_entry(key);
const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster
const u16 key16 = u16(key); // Use the low 16 bits as key inside the cluster
for (int i = 0; i < ClusterSize; ++i)
if (tte[i].key16 == key16)
@@ -234,12 +265,11 @@ std::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) cons
// Find an entry to be replaced according to the replacement strategy
TTEntry* replace = tte;
for (int i = 1; i < ClusterSize; ++i)
if (replace->depth8 - replace->relative_age(generation8)
> tte[i].depth8 - tte[i].relative_age(generation8))
if (replace->depth8 - 8 * replace->relative_age(generation8)
> tte[i].depth8 - 8 * tte[i].relative_age(generation8))
replace = &tte[i];
return {false,
TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_ENTRY_OFFSET, BOUND_NONE, false},
return {false, TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_NONE, BOUND_NONE, false},
TTWriter(replace)};
}
+23 -21
View File
@@ -19,10 +19,9 @@
#ifndef TT_H_INCLUDED
#define TT_H_INCLUDED
#include <cstddef>
#include <cstdint>
#include <tuple>
#include "misc.h"
#include "memory.h"
#include "types.h"
@@ -37,14 +36,11 @@ struct Cluster;
// thus elo. As a hash table, collisions are possible and may cause chess playing issues (bizarre blunders, faulty mate
// reports, etc). Fixing these also loses elo; however such risk decreases quickly with larger TT size.
//
// `probe` is the primary method: given a board position, we lookup its entry in the table, and return a tuple of:
// 1) whether the entry already has this position
// 2) a copy of the prior data (if any) (may be inconsistent due to read races)
// 3) a writer object to this entry
// The copied data and the writer are separated to maintain clear boundaries between local vs global objects.
// We clearly separate TTData, a local copy of an entry, from TTWriter, which writes to the global table.
// A copy of the data already in the entry (possibly collided). `probe` may be racy, resulting in inconsistent data.
// A copy of the data already in an entry (possibly collided). Probes and reads are racy and non-atomic,
// possibly resulting in inconsistent data.
struct TTData {
Move move;
Value value, eval;
@@ -66,10 +62,12 @@ struct TTData {
};
// This is used to make racy writes to the global TT.
// This is used to make racy, non-atomic writes to the global TT. Writes are not "guaranteed":
// for chess reasons, we may decide the new data is less important than the old.
struct TTWriter {
public:
void write(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);
void write(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, u8 generation8);
void penalize(int penalty); // decrement stored depth by the penalty
private:
friend class TranspositionTable;
@@ -83,26 +81,30 @@ class TranspositionTable {
public:
~TranspositionTable() { aligned_large_pages_free(table); }
void resize(size_t mbSize, ThreadPool& threads); // Set TT size
void clear(ThreadPool& threads); // Re-initialize memory, multithreaded
int hashfull(int maxAge = 0)
const; // Approximate what fraction of entries (permille) have been written to during this root search
void resize(usize mbSize, ThreadPool& threads); // Set TT size in MiB
void clear(ThreadPool& threads); // Re-initialize memory, multithreaded
void
new_search(); // This must be called at the beginning of each root search to track entry aging
uint8_t generation() const; // The current age, used when writing new data to the TT
std::tuple<bool, TTData, TTWriter>
probe(const Key key) const; // The main method, whose retvals separate local vs global objects
TTEntry* first_entry(const Key key)
const; // This is the hash function; its only external use is memory prefetching.
u8 generation() const; // The current age, used when writing new data to the TT
// Approximate what fraction of entries (permille) have been written to during this root search
int hashfull(int maxAge = 0) const;
// `probe` is the primary method: given a board position, we lookup its entry in the table, and return a tuple of:
// 1) whether the entry already had data on this position
// 2) a copy of the prior data, if any (may be self-inconsistent due to read races)
// 3) a writer object to the entry
std::tuple<bool, TTData, TTWriter> probe(const Key key) const;
// The hash function; its only external use is memory prefetching
TTEntry* first_entry(const Key key) const;
private:
friend struct TTEntry;
size_t clusterCount;
usize clusterCount;
Cluster* table = nullptr;
uint8_t generation8 = 0; // Size must be not bigger than TTEntry::genBound8
u8 generation8 = 0;
};
} // namespace Stockfish
+4 -3
View File
@@ -19,13 +19,14 @@
#ifndef TUNE_H_INCLUDED
#define TUNE_H_INCLUDED
#include <cstddef>
#include <memory>
#include <string>
#include <type_traits> // IWYU pragma: keep
#include <utility>
#include <vector>
#include "misc.h"
namespace Stockfish {
class OptionsMap;
@@ -132,9 +133,9 @@ class Tune {
}
// Template specialization for arrays: recursively handle multi-dimensional arrays
template<typename T, size_t N, typename... Args>
template<typename T, usize N, typename... Args>
int add(const SetRange& range, std::string&& names, T (&value)[N], Args&&... args) {
for (size_t i = 0; i < N; i++)
for (usize i = 0; i < N; i++)
add(range, next(names, i == N - 1) + "[" + std::to_string(i) + "]", value[i]);
return add(range, std::move(names), args...);
}
+60 -50
View File
@@ -39,7 +39,6 @@
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#include "misc.h"
#if defined(_MSC_VER)
@@ -58,15 +57,15 @@
// _WIN32 Building on Windows (any)
// _WIN64 Building on Windows 64 bit
// Enforce minimum GCC version
// Enforce minimum GCC version
#if defined(__GNUC__) && !defined(__clang__) \
&& (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ < 3))
#error "Stockfish requires GCC 9.3 or later for correct compilation"
#endif
// Enforce minimum Clang version
#if defined(__clang__) && (__clang_major__ < 10)
#error "Stockfish requires Clang 10.0 or later for correct compilation"
#if defined(__clang__) && (__clang_major__ < 11)
#error "Stockfish requires Clang 11.0 or later for correct compilation"
#endif
#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
@@ -76,7 +75,7 @@
#define IS_64BIT
#endif
#if defined(USE_POPCNT) && defined(_MSC_VER)
#if defined(_MSC_VER)
#include <nmmintrin.h> // Microsoft header for _mm_popcnt_u64()
#endif
@@ -87,8 +86,7 @@
#if defined(USE_PEXT)
#include <immintrin.h> // Header for _pext_u64() intrinsic
#define pext(b, m) _pext_u64(b, m)
#else
#define pext(b, m) 0
#define pdep(b, m) _pdep_u64(b, m)
#endif
namespace Stockfish {
@@ -111,19 +109,19 @@ constexpr bool Is64Bit = true;
constexpr bool Is64Bit = false;
#endif
using Key = uint64_t;
using Bitboard = uint64_t;
using Key = u64;
using Bitboard = u64;
constexpr int MAX_MOVES = 256;
constexpr int MAX_PLY = 246;
enum Color : uint8_t {
enum Color : u8 {
WHITE,
BLACK,
COLOR_NB = 2
};
enum CastlingRights : uint8_t {
enum CastlingRights : u8 {
NO_CASTLING,
WHITE_OO,
WHITE_OOO = WHITE_OO << 1,
@@ -139,7 +137,7 @@ enum CastlingRights : uint8_t {
CASTLING_RIGHT_NB = 16
};
enum Bound : uint8_t {
enum Bound : u8 {
BOUND_NONE,
BOUND_UPPER,
BOUND_LOWER,
@@ -179,6 +177,22 @@ constexpr bool is_loss(Value value) {
constexpr bool is_decisive(Value value) { return is_win(value) || is_loss(value); }
constexpr bool is_mate(Value value) {
assert(is_valid(value));
return value >= VALUE_MATE_IN_MAX_PLY;
}
constexpr bool is_mated(Value value) {
assert(is_valid(value));
return value <= VALUE_MATED_IN_MAX_PLY;
}
constexpr bool is_mate_or_mated(Value value) { return is_mate(value) || is_mated(value); }
constexpr Value mate_in(int ply) { return VALUE_MATE - ply; }
constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; }
// In the code, we make the assumption that these values
// are such that non_pawn_material() can be used to uniquely
// identify the material on the board.
@@ -190,13 +204,13 @@ constexpr Value QueenValue = 2538;
// clang-format off
enum PieceType : std::uint8_t {
enum PieceType : u8 {
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
ALL_PIECES = 0,
PIECE_TYPE_NB = 8
};
enum Piece : std::uint8_t {
enum Piece : u8 {
NO_PIECE,
W_PAWN = PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
@@ -220,14 +234,14 @@ using Depth = int;
constexpr Depth DEPTH_QS = 0;
// For transposition table entries where no searching at all was done
// (whether regular or qsearch) we use DEPTH_UNSEARCHED, which should thus
// compare lower than any quiescence or regular depth. DEPTH_ENTRY_OFFSET
// is used only for the transposition table entry occupancy check (see tt.cpp),
// and should thus be lower than DEPTH_UNSEARCHED.
constexpr Depth DEPTH_UNSEARCHED = -2;
constexpr Depth DEPTH_ENTRY_OFFSET = -3;
// compare lower than any quiescence or regular depth. DEPTH_NONE is used
// for the transposition table entry occupancy check (see tt.cpp), and
// should thus be lower than DEPTH_UNSEARCHED.
constexpr Depth DEPTH_UNSEARCHED = -2;
constexpr Depth DEPTH_NONE = -3;
// clang-format off
enum Square : uint8_t {
enum Square : u8 {
SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1,
SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2,
SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3,
@@ -243,7 +257,7 @@ enum Square : uint8_t {
};
// clang-format on
enum Direction : int8_t {
enum Direction : i8 {
NORTH = 8,
EAST = 1,
SOUTH = -NORTH,
@@ -255,7 +269,7 @@ enum Direction : int8_t {
NORTH_WEST = NORTH + WEST
};
enum File : uint8_t {
enum File : u8 {
FILE_A,
FILE_B,
FILE_C,
@@ -267,7 +281,7 @@ enum File : uint8_t {
FILE_NB
};
enum Rank : uint8_t {
enum Rank : u8 {
RANK_1,
RANK_2,
RANK_3,
@@ -299,10 +313,10 @@ struct DirtyThreat {
static constexpr int PcOffset = 20;
DirtyThreat() { /* don't initialize data */ }
DirtyThreat(uint32_t raw) :
DirtyThreat(u32 raw) :
data(raw) {}
DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) {
data = (uint32_t(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset)
data = (u32(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset)
| (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset);
}
@@ -311,10 +325,10 @@ struct DirtyThreat {
Square threatened_sq() const { return static_cast<Square>(data >> ThreatenedSqOffset & 0xff); }
Square pc_sq() const { return static_cast<Square>(data >> PcSqOffset & 0xff); }
bool add() const { return data >> 31; }
uint32_t raw() const { return data; }
u32 raw() const { return data; }
private:
uint32_t data;
u32 data;
};
// A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks.
@@ -328,10 +342,17 @@ using DirtyThreatList = ValueList<DirtyThreat, 96>;
struct DirtyThreats {
DirtyThreatList list;
Color us;
Square prevKsq, ksq;
};
Bitboard threatenedSqs, threateningSqs;
struct DirtyPawnPairs {
Bitboard before[COLOR_NB];
Bitboard after[COLOR_NB];
};
struct Dirties {
DirtyPiece dirtyPiece;
DirtyThreats dirtyThreats;
DirtyPawnPairs dirtyPawnPairs;
};
#define ENABLE_INCR_OPERATORS_ON(T) \
@@ -370,10 +391,6 @@ constexpr CastlingRights operator&(Color c, CastlingRights cr) {
return CastlingRights((c == WHITE ? WHITE_CASTLING : BLACK_CASTLING) & cr);
}
constexpr Value mate_in(int ply) { return VALUE_MATE - ply; }
constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; }
constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); }
constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); }
@@ -401,12 +418,10 @@ constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; }
// Based on a congruential pseudo-random number generator
constexpr Key make_key(uint64_t seed) {
return seed * 6364136223846793005ULL + 1442695040888963407ULL;
}
constexpr Key make_key(u64 seed) { return seed * 6364136223846793005ULL + 1442695040888963407ULL; }
enum MoveType : uint16_t {
enum MoveType : u16 {
NORMAL,
PROMOTION = 1 << 14,
EN_PASSANT = 2 << 14,
@@ -428,7 +443,7 @@ enum MoveType : uint16_t {
class Move {
public:
Move() = default;
constexpr explicit Move(std::uint16_t d) :
constexpr explicit Move(u16 d) :
data(d) {}
constexpr Move(Square from, Square to) :
@@ -463,24 +478,19 @@ class Move {
constexpr explicit operator bool() const { return data != 0; }
constexpr std::uint16_t raw() const { return data; }
constexpr u16 raw() const { return data; }
struct MoveHash {
std::size_t operator()(const Move& m) const { return make_key(m.data); }
usize operator()(const Move& m) const { return make_key(m.data); }
};
static constexpr int FromSqShift = 6;
static constexpr int ToSqShift = 0;
protected:
std::uint16_t data;
u16 data;
};
template<typename T, typename... Ts>
struct is_all_same {
static constexpr bool value = (std::is_same_v<T, Ts> && ...);
};
template<typename... Ts>
constexpr auto is_all_same_v = is_all_same<Ts...>::value;
} // namespace Stockfish
#endif // #ifndef TYPES_H_INCLUDED
+61 -31
View File
@@ -21,12 +21,14 @@
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <iterator>
#include <optional>
#include <sstream>
#include <string_view>
#include <filesystem>
#include <utility>
#include <variant>
#include <vector>
#include "benchmark.h"
@@ -43,7 +45,6 @@ namespace Stockfish {
constexpr auto BenchmarkCommand = "speedtest";
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
template<typename... Ts>
struct overload: Ts... {
using Ts::operator()...;
@@ -64,9 +65,9 @@ void UCIEngine::print_info_string(std::string_view str) {
sync_cout_end();
}
UCIEngine::UCIEngine(int argc, char** argv) :
engine(argv[0]),
cli(argc, argv) {
UCIEngine::UCIEngine(CommandLine cli_) :
engine(cli_.argc > 0 ? std::optional{path_from_utf8(cli_.argv[0])} : std::nullopt),
cli(std::move(cli_)) {
engine.get_options().add_info_listener([](const std::optional<std::string>& str) {
if (str.has_value())
@@ -82,10 +83,11 @@ void UCIEngine::init_search_update_listeners() {
engine.set_on_update_full(
[this](const auto& i) { on_update_full(i, engine.get_options()["UCI_ShowWDL"]); });
engine.set_on_bestmove([](const auto& bm, const auto& p) { on_bestmove(bm, p); });
engine.set_on_verify_networks([](const auto& s) { print_info_string(s); });
engine.set_on_verify_network([](const auto& s) { print_info_string(s); });
}
void UCIEngine::loop() {
set_console_utf8();
std::string token, cmd;
for (int i = 1; i < cli.argc; ++i)
@@ -97,10 +99,11 @@ void UCIEngine::loop() {
&& !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
cmd = "quit";
currentCmd = cmd;
std::istringstream is(cmd);
token.clear(); // Avoid a stale if getline() returns nothing or a blank line
is >> std::skipws >> token;
is >> token;
if (token == "quit" || token == "stop")
engine.stop();
@@ -137,9 +140,13 @@ void UCIEngine::loop() {
sync_cout << "readyok" << sync_endl;
// Add custom non-UCI commands, mainly for debugging purposes.
// These commands must not be used during a search!
else if (token == "flip")
engine.flip();
{
if (auto err = engine.flip())
{
terminate_on_critical_error(err->what());
}
}
else if (token == "bench")
bench(is);
else if (token == BenchmarkCommand)
@@ -152,15 +159,13 @@ void UCIEngine::loop() {
sync_cout << compiler_info() << sync_endl;
else if (token == "export_net")
{
std::pair<std::optional<std::string>, std::string> files[2];
std::optional<std::filesystem::path> file;
std::string filename;
if (is >> std::skipws >> files[0].second)
files[0].first = files[0].second;
if (is >> filename)
file = path_from_utf8(filename);
if (is >> std::skipws >> files[1].second)
files[1].first = files[1].second;
engine.save_network(files);
engine.save_network(file);
}
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
sync_cout
@@ -175,7 +180,7 @@ void UCIEngine::loop() {
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
<< sync_endl;
} while (token != "quit" && cli.argc == 1); // The command-line arguments are one-shot
} while (token != "quit" && cli.argc <= 1); // The command-line arguments are one-shot
}
Search::LimitsType UCIEngine::parse_limits(std::istream& is) {
@@ -185,9 +190,13 @@ Search::LimitsType UCIEngine::parse_limits(std::istream& is) {
limits.startTime = now(); // The search starts as early as possible
while (is >> token)
{
if (token == "searchmoves") // Needs to be the last command on the line
{
while (is >> token)
limits.searchmoves.push_back(to_lower(token));
break;
}
else if (token == "wtime")
is >> limits.time[WHITE];
@@ -214,6 +223,10 @@ Search::LimitsType UCIEngine::parse_limits(std::istream& is) {
else if (token == "ponder")
limits.ponderMode = true;
if (is.fail())
terminate_on_critical_error("Invalid argument for '" + token + "'");
}
return limits;
}
@@ -229,8 +242,8 @@ void UCIEngine::go(std::istringstream& is) {
void UCIEngine::bench(std::istream& args) {
std::string token;
uint64_t num, nodes = 0, cnt = 1;
uint64_t nodesSearched = 0;
u64 num, nodes = 0, cnt = 1;
u64 nodesSearched = 0;
const auto& options = engine.get_options();
engine.set_on_update_full([&](const auto& i) {
@@ -248,7 +261,7 @@ void UCIEngine::bench(std::istream& args) {
for (const auto& cmd : list)
{
std::istringstream is(cmd);
is >> std::skipws >> token;
is >> token;
if (token == "go" || token == "eval")
{
@@ -301,15 +314,15 @@ void UCIEngine::benchmark(std::istream& args) {
static constexpr int NUM_WARMUP_POSITIONS = 3;
std::string token;
uint64_t nodes = 0, cnt = 1;
uint64_t nodesSearched = 0;
u64 nodes = 0, cnt = 1;
u64 nodesSearched = 0;
engine.set_on_update_full([&](const Engine::InfoFull& i) { nodesSearched = i.nodes; });
engine.set_on_iter([](const auto&) {});
engine.set_on_update_no_moves([](const auto&) {});
engine.set_on_bestmove([](const auto&, const auto&) {});
engine.set_on_verify_networks([](const auto&) {});
engine.set_on_verify_network([](const auto&) {});
Benchmark::BenchmarkSetup setup = Benchmark::setup_benchmark(args);
@@ -330,7 +343,7 @@ void UCIEngine::benchmark(std::istream& args) {
for (const auto& cmd : setup.commands)
{
std::istringstream is(cmd);
is >> std::skipws >> token;
is >> token;
if (token == "go")
{
@@ -381,7 +394,7 @@ void UCIEngine::benchmark(std::istream& args) {
for (const auto& cmd : setup.commands)
{
std::istringstream is(cmd);
is >> std::skipws >> token;
is >> token;
if (token == "go")
{
@@ -459,13 +472,19 @@ void UCIEngine::setoption(std::istringstream& is) {
engine.get_options().setoption(is);
}
std::uint64_t UCIEngine::perft(const Search::LimitsType& limits) {
auto nodes = engine.perft(engine.fen(), limits.perft, engine.get_options()["UCI_Chess960"]);
u64 UCIEngine::perft(const Search::LimitsType& limits) {
auto result = engine.perft(engine.fen(), limits.perft, engine.get_options()["UCI_Chess960"]);
if (auto err = std::get_if<PositionSetError>(&result))
terminate_on_critical_error(err->what());
auto nodes = std::get<u64>(result);
sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl;
return nodes;
}
void UCIEngine::position(std::istringstream& is) {
const std::string fullCommand = is.str();
std::string token, fen;
is >> token;
@@ -488,7 +507,11 @@ void UCIEngine::position(std::istringstream& is) {
moves.push_back(token);
}
engine.set_position(fen, moves);
auto err = engine.set_position(fen, moves);
if (err.has_value())
{
terminate_on_critical_error(err->what());
}
}
namespace {
@@ -535,8 +558,7 @@ std::string UCIEngine::format_score(const Score& s) {
return std::string("mate ") + std::to_string(m);
},
[](Score::Tablebase tb) -> std::string {
return std::string("cp ")
+ std::to_string((tb.win ? TB_CP - tb.plies : -TB_CP - tb.plies));
return std::string("cp ") + std::to_string((tb.win ? TB_CP : -TB_CP) - tb.plies);
},
[](Score::InternalUnits units) -> std::string {
return std::string("cp ") + std::to_string(units.value);
@@ -596,7 +618,8 @@ std::string UCIEngine::move(Move m, bool chess960) {
std::string UCIEngine::to_lower(std::string str) {
std::transform(str.begin(), str.end(), str.begin(), [](auto c) { return std::tolower(c); });
std::transform(str.begin(), str.end(), str.begin(),
[](unsigned char c) { return std::tolower(c); });
return str;
}
@@ -658,4 +681,11 @@ void UCIEngine::on_bestmove(std::string_view bestmove, std::string_view ponder)
std::cout << sync_endl;
}
void UCIEngine::terminate_on_critical_error(const std::string& message) {
sync_cout << "info string CRITICAL ERROR: Command `" << currentCmd
<< "` failed. Reason: " << message << '\n'
<< sync_endl;
std::exit(1);
}
} // namespace Stockfish
+15 -11
View File
@@ -19,7 +19,6 @@
#ifndef UCI_H_INCLUDED
#define UCI_H_INCLUDED
#include <cstdint>
#include <iostream>
#include <string>
#include <string_view>
@@ -33,39 +32,42 @@ namespace Stockfish {
class Position;
class Move;
class Score;
enum Square : uint8_t;
enum Square : u8;
using Value = int;
constexpr auto StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
class UCIEngine {
public:
UCIEngine(int argc, char** argv);
UCIEngine(CommandLine cli);
void loop();
static int to_cp(Value v, const Position& pos);
static std::string format_score(const Score& s);
static std::string square(Square s);
static std::string move(Move m, bool chess960);
static std::string move(Move m, bool chess960 = false);
static std::string wdl(Value v, const Position& pos);
static std::string to_lower(std::string str);
static Move to_move(const Position& pos, std::string str);
static Search::LimitsType parse_limits(std::istream& is);
Search::LimitsType parse_limits(std::istream& is);
auto& engine_options() { return engine.get_options(); }
private:
Engine engine;
CommandLine cli;
std::string currentCmd;
static void print_info_string(std::string_view str);
void go(std::istringstream& is);
void bench(std::istream& args);
void benchmark(std::istream& args);
void position(std::istringstream& is);
void setoption(std::istringstream& is);
std::uint64_t perft(const Search::LimitsType&);
void go(std::istringstream& is);
void bench(std::istream& args);
void benchmark(std::istream& args);
void position(std::istringstream& is);
void setoption(std::istringstream& is);
u64 perft(const Search::LimitsType&);
static void on_update_no_moves(const Engine::InfoShort& info);
static void on_update_full(const Engine::InfoFull& info, bool showWDL);
@@ -73,6 +75,8 @@ class UCIEngine {
static void on_bestmove(std::string_view bestmove, std::string_view ponder);
void init_search_update_listeners();
[[noreturn]] void terminate_on_critical_error(const std::string& message);
};
} // namespace Stockfish
+16 -5
View File
@@ -21,6 +21,7 @@
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cerrno>
#include <cstdlib>
#include <iostream>
#include <sstream>
@@ -34,7 +35,7 @@ bool CaseInsensitiveLess::operator()(const std::string& s1, const std::string& s
return std::lexicographical_compare(
s1.begin(), s1.end(), s2.begin(), s2.end(),
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
[](unsigned char c1, unsigned char c2) { return std::tolower(c1) < std::tolower(c2); });
}
void OptionsMap::add_info_listener(InfoListener&& message_func) { info = std::move(message_func); }
@@ -68,7 +69,7 @@ const Option& OptionsMap::operator[](const std::string& name) const {
void OptionsMap::add(const std::string& name, const Option& option) {
if (!options_map.count(name))
{
static size_t insert_order = 0;
static usize insert_order = 0;
options_map[name] = option;
@@ -83,7 +84,7 @@ void OptionsMap::add(const std::string& name, const Option& option) {
}
std::size_t OptionsMap::count(const std::string& name) const { return options_map.count(name); }
usize OptionsMap::count(const std::string& name) const { return options_map.count(name); }
Option::Option(const OptionsMap* map) :
parent(map) {}
@@ -144,6 +145,16 @@ bool Option::operator==(const char* s) const {
bool Option::operator!=(const char* s) const { return !(*this == s); }
static bool value_in_range(const std::string& v, int min, int max) {
if (v.empty())
return false;
errno = 0;
char* end = nullptr;
const long long result = std::strtoll(v.c_str(), &end, 10);
if (errno == ERANGE || *end != '\0')
return false;
return result >= min && result <= max;
}
// Updates currentValue and triggers on_change() action. It's up to
// the GUI to check for option's limits, but we could receive the new value
@@ -154,7 +165,7 @@ Option& Option::operator=(const std::string& v) {
if ((type != "button" && type != "string" && v.empty())
|| (type == "check" && v != "true" && v != "false")
|| (type == "spin" && (std::stoi(v) < min || std::stoi(v) > max)))
|| (type == "spin" && !value_in_range(v, min, max)))
return *this;
if (type == "combo")
@@ -185,7 +196,7 @@ Option& Option::operator=(const std::string& v) {
}
std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
for (size_t idx = 0; idx < om.options_map.size(); ++idx)
for (usize idx = 0; idx < om.options_map.size(); ++idx)
for (const auto& it : om.options_map)
if (it.second.idx == idx)
{
+4 -3
View File
@@ -19,13 +19,14 @@
#ifndef UCIOPTION_H_INCLUDED
#define UCIOPTION_H_INCLUDED
#include <cstddef>
#include <functional>
#include <iosfwd>
#include <map>
#include <optional>
#include <string>
#include "misc.h"
namespace Stockfish {
// Define a custom comparator, because the UCI options should be case-insensitive
struct CaseInsensitiveLess {
@@ -64,7 +65,7 @@ class Option {
std::string defaultValue, currentValue, type;
int min, max;
size_t idx;
usize idx;
OnChange on_change;
const OptionsMap* parent = nullptr;
};
@@ -87,7 +88,7 @@ class OptionsMap {
void add(const std::string&, const Option& option);
std::size_t count(const std::string&) const;
usize count(const std::string&) const;
private:
friend class Engine;
+76
View File
@@ -0,0 +1,76 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#if defined(_WIN32)
#include <windows.h>
#else
#include <sys/auxv.h>
#ifndef HWCAP_ASIMDDP
#define HWCAP_ASIMDDP (1 << 20)
#endif
#endif
#define DEFINE_BUILD(x) \
namespace Stockfish_##x { \
extern int main(int argc, char* argv[]); \
} \
extern "C" void (*__start_##x##_init[])(void); \
extern "C" void (*__stop_##x##_init[])(void); \
int entry_##x(int argc, char* argv[]) { \
unsigned count = __stop_##x##_init - __start_##x##_init; \
for (unsigned i = 0; i < count; i++) \
__start_##x##_init[i](); \
return Stockfish_##x::main(argc, argv); \
}
DEFINE_BUILD(armv8)
DEFINE_BUILD(armv8_dotprod)
struct CpuFeatures {
bool dotprod;
};
static CpuFeatures query_cpu_features() {
#if defined(_WIN32)
return {
.dotprod = (bool) IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE),
};
#else
unsigned long hwcap = getauxval(AT_HWCAP);
return {
.dotprod = (bool) (hwcap & HWCAP_ASIMDDP),
};
#endif
}
// Selects the most capable ISA variant supported by this CPU and OS
static int dispatch(const CpuFeatures& f, int argc, char* argv[]) {
if (!f.dotprod)
return entry_armv8(argc, argv);
return entry_armv8_dotprod(argc, argv);
}
int main(int argc, char* argv[]) {
CpuFeatures features = query_cpu_features();
return dispatch(features, argc, argv);
}
+96
View File
@@ -0,0 +1,96 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#ifdef __linux__
#include <sys/auxv.h>
#include <sys/syscall.h>
#include <unistd.h>
#else
#error This file is only supported on Linux
#endif
#define DEFINE_BUILD(x) \
namespace Stockfish_##x { \
extern int main(int argc, char* argv[]); \
} \
extern "C" void (*__start_##x##_init[])(void); \
extern "C" void (*__stop_##x##_init[])(void); \
int entry_##x(int argc, char* argv[]) { \
unsigned count = __stop_##x##_init - __start_##x##_init; \
for (unsigned i = 0; i < count; i++) \
__start_##x##_init[i](); \
return Stockfish_##x::main(argc, argv); \
}
DEFINE_BUILD(riscv64)
DEFINE_BUILD(riscv64_rva23)
struct CpuFeatures {
bool gcv; // GCV
bool zbitmanip; // Zba + Zbb + Zbs
bool zicond; // Zicond
int vlen; // vector length in bits (valid only if gcv is true), always a power of two
};
static CpuFeatures query_cpu_features() {
unsigned long hwcap = getauxval(AT_HWCAP);
constexpr long MASK_GCV = 0x20112d;
bool gcv = (hwcap & MASK_GCV) == MASK_GCV;
int vlen = 0;
if (gcv)
{
unsigned long vlenb;
asm volatile("csrr %0, 0xc22" : "=r"(vlenb)); // 0xc22 = vlenb
vlen = int(vlenb * 8);
}
constexpr long NR_riscv_hwprobe = 258;
constexpr int64_t KEY_IMA_EXT_0 = 4;
constexpr uint64_t EXT_ZB = 1 << 3 | 1 << 4 | 1 << 5;
constexpr uint64_t EXT_ZICOND = uint64_t(1) << 35;
struct {
int64_t key;
uint64_t value;
} pair = {KEY_IMA_EXT_0, 0};
long rc = syscall(NR_riscv_hwprobe, &pair, 1UL, 0UL, nullptr, 0U);
bool zbitmanip = false, zicond = false;
if (rc == 0 && pair.key == KEY_IMA_EXT_0)
{
zbitmanip = (pair.value & EXT_ZB) == EXT_ZB;
zicond = (pair.value & EXT_ZICOND) == EXT_ZICOND;
}
return CpuFeatures{.gcv = gcv, .zbitmanip = zbitmanip, .zicond = zicond, .vlen = vlen};
}
// Selects the most capable ISA variant supported by this CPU
static int dispatch(const CpuFeatures& f, int argc, char* argv[]) {
if (!f.gcv || !f.zbitmanip || !f.zicond || f.vlen < 128)
return entry_riscv64(argc, argv);
return entry_riscv64_rva23(argc, argv);
}
int main(int argc, char* argv[]) {
CpuFeatures f = query_cpu_features();
return dispatch(f, argc, argv);
}
+177
View File
@@ -0,0 +1,177 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cpuid.h>
#include <stdint.h>
#ifdef __APPLE__
// We locate each arch's initializer pointer array at runtime via getsectiondata().
// Example name is "_i_sse41_popcnt", baseline build is just "_i_"
#include <stdio.h>
#include <mach-o/getsect.h>
#include <sys/sysctl.h>
extern "C" const struct mach_header_64 _mh_execute_header;
#define DEFINE_BUILD(x) \
namespace Stockfish_##x { \
extern int main(int argc, char* argv[]); \
} \
int entry_##x(int argc, char* argv[]) { \
char name[17]; \
const char* full = #x; \
snprintf(name, sizeof(name), "_i_%s", full[6] ? full + 7 : ""); \
unsigned long size = 0; \
auto** fns = reinterpret_cast<void (**)()>( \
getsectiondata(&_mh_execute_header, "__DATA", name, &size)); \
for (unsigned long i = 0; i < size / sizeof(*fns); i++) \
fns[i](); \
return Stockfish_##x::main(argc, argv); \
}
#else
#define DEFINE_BUILD(x) \
namespace Stockfish_##x { \
extern int main(int argc, char* argv[]); \
} \
extern "C" void (*__start_##x##_init[])(void); \
extern "C" void (*__stop_##x##_init[])(void); \
int entry_##x(int argc, char* argv[]) { \
unsigned count = __stop_##x##_init - __start_##x##_init; \
for (unsigned i = 0; i < count; i++) \
__start_##x##_init[i](); \
return Stockfish_##x::main(argc, argv); \
}
#endif
DEFINE_BUILD(x86_64)
DEFINE_BUILD(x86_64_sse41_popcnt)
DEFINE_BUILD(x86_64_avx2)
DEFINE_BUILD(x86_64_bmi2)
DEFINE_BUILD(x86_64_avxvnni)
DEFINE_BUILD(x86_64_avx512)
DEFINE_BUILD(x86_64_vnni512)
DEFINE_BUILD(x86_64_avx512icl)
// AMD Excavator (family 15h) and Zen/Zen+/Zen2 (family 17h) implement pdep/pext via microcode.
static bool has_slow_bmi2() {
return __builtin_cpu_is("amd")
&& (__builtin_cpu_is("bdver4") || __builtin_cpu_is("znver1") || __builtin_cpu_is("znver2"));
}
struct CpuFeatures {
bool sse41; // SSE4.1
bool popcnt; // POPCNT
bool avx2; // AVX2
bool bmi2; // BMI2 (may be slow on AMD Excavator and Zen/Zen+/Zen2)
bool avx512f; // AVX-512 Foundation
bool avx512vl; // AVX-512 Vector Length extensions
bool avx512bw; // AVX-512 Byte and Word instructions
bool avx512vnni; // AVX-512 Vector Neural Network Instructions
bool avx512ifma; // AVX-512 Integer Fused Multiply-Add
bool avx512vbmi; // AVX-512 Vector Bit Manipulation Instructions
bool avx512vbmi2; // AVX-512 VBMI2
bool avx512vpopcntdq; // AVX-512 VPOPCNTDQ
bool avx512bitalg; // AVX-512 BITALG
bool vpclmulqdq; // Carry-less multiplication (AVX512 variant)
bool gfni; // Galois Field instructions
bool vaes; // AES instructions (AVX512 variant)
bool avxvnni; // AVX-VNNI (non-512 dot product instructions)
};
static CpuFeatures query_cpu_features() {
return {
.sse41 = (bool) __builtin_cpu_supports("sse4.1"),
.popcnt = (bool) __builtin_cpu_supports("popcnt"),
.avx2 = (bool) __builtin_cpu_supports("avx2"),
.bmi2 = (bool) __builtin_cpu_supports("bmi2"),
.avx512f = (bool) __builtin_cpu_supports("avx512f"),
.avx512vl = (bool) __builtin_cpu_supports("avx512vl"),
.avx512bw = (bool) __builtin_cpu_supports("avx512bw"),
.avx512vnni = (bool) __builtin_cpu_supports("avx512vnni"),
.avx512ifma = (bool) __builtin_cpu_supports("avx512ifma"),
.avx512vbmi = (bool) __builtin_cpu_supports("avx512vbmi"),
.avx512vbmi2 = (bool) __builtin_cpu_supports("avx512vbmi2"),
.avx512vpopcntdq = (bool) __builtin_cpu_supports("avx512vpopcntdq"),
.avx512bitalg = (bool) __builtin_cpu_supports("avx512bitalg"),
.vpclmulqdq = (bool) __builtin_cpu_supports("vpclmulqdq"),
.gfni = (bool) __builtin_cpu_supports("gfni"),
.vaes = (bool) __builtin_cpu_supports("vaes"),
.avxvnni = (bool) __builtin_cpu_supports("avxvnni"),
};
}
// Selects the most capable ISA variant supported by this CPU and OS
static int dispatch(const CpuFeatures& f, int argc, char* argv[]) {
if (!f.sse41 || !f.popcnt)
return entry_x86_64(argc, argv);
if (!f.avx2)
return entry_x86_64_sse41_popcnt(argc, argv);
if (!f.bmi2 || has_slow_bmi2())
return entry_x86_64_avx2(argc, argv);
if (!f.avx512f || !f.avx512vl || !f.avx512bw)
{
if (f.avxvnni)
return entry_x86_64_avxvnni(argc, argv);
return entry_x86_64_bmi2(argc, argv);
}
if (!f.avx512vnni)
return entry_x86_64_avx512(argc, argv);
// AVX512ICL requires the full Icelake-client feature suite
if (!f.avx512ifma //
|| !f.avx512vbmi //
|| !f.avx512vbmi2 //
|| !f.avx512vpopcntdq //
|| !f.avx512bitalg //
|| !f.vpclmulqdq //
|| !f.gfni //
|| !f.vaes //
)
return entry_x86_64_vnni512(argc, argv);
return entry_x86_64_avx512icl(argc, argv);
}
static void maybe_promote_thread_to_avx512() {
#ifdef __APPLE__
// Intel Macs supporting AVX512 don't advertise it in xgetbv and only
// do so once at least one avx512 instruction has been executed.
// See https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/osfmk/i386/fpu.c#L176
int supported = 0;
size_t len = sizeof(supported);
if (sysctlbyname("hw.optional.avx512f", &supported, &len, nullptr, 0) == 0 && supported)
{
asm volatile(".byte 0x62, 0xf1, 0x7d, 0x48, 0x6f, 0xc0"); // vmovdqa32 zmm0,zmm0
}
#endif
}
int main(int argc, char* argv[]) {
maybe_promote_thread_to_avx512();
__builtin_cpu_init();
CpuFeatures features = query_cpu_features();
return dispatch(features, argc, argv);
}
+85
View File
@@ -0,0 +1,85 @@
/*
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
Stockfish is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Stockfish is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Standalone NNUE embedding for universal binary builds
#include "../evaluate.h"
#ifdef UNIVERSAL_BINARY_MACOS_X86_SLICE
// In a macOS universal binary the network is embedded only in the arm64 slice,
// and the x86-64 slice mmaps it from the arm64 slice.
#include <climits>
#include <cstdint>
#include <fcntl.h>
#include <mach-o/dyld.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
// Must be kept in sync with patch_x86_slice.sh
extern const volatile Stockfish::u64 gUniversalNNUEOffset = 0xCAFE0FF5E70FF5E7ULL;
extern const volatile Stockfish::u64 gUniversalNNUESize = 0xCAFE512ECAFE512EULL;
static const unsigned char* map_embedded_nnue() {
char path[PATH_MAX];
Stockfish::u32 len = sizeof(path);
if (_NSGetExecutablePath(path, &len) != 0)
return nullptr;
char resolved[PATH_MAX];
const char* file = realpath(path, resolved) ? resolved : path;
int fd = open(file, O_RDONLY);
if (fd < 0)
return nullptr;
// Align down to page size for mmap
const Stockfish::u64 pageSize = Stockfish::u64(sysconf(_SC_PAGESIZE));
const Stockfish::u64 base = gUniversalNNUEOffset & ~(pageSize - 1);
const Stockfish::u64 pad = gUniversalNNUEOffset - base;
void* p =
mmap(nullptr, size_t(gUniversalNNUESize + pad), PROT_READ, MAP_PRIVATE, fd, off_t(base));
close(fd);
if (p == MAP_FAILED)
return nullptr;
return reinterpret_cast<const unsigned char*>(p) + pad;
}
extern const unsigned char* const gEmbeddedNNUEData = map_embedded_nnue();
extern const unsigned int gEmbeddedNNUESize = static_cast<unsigned int>(gUniversalNNUESize);
#else
extern const unsigned char gEmbeddedNNUEData[] =
#ifdef __has_embed
{
#embed EvalFileDefaultName
};
const unsigned int padding = 0;
#else
#include "network_dump.inc"
;
const unsigned int padding = 1; // trailing NUL byte
#endif
extern const unsigned int gEmbeddedNNUESize = sizeof(gEmbeddedNNUEData) - padding;
#endif
+73
View File
@@ -0,0 +1,73 @@
#!/bin/sh
# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
# Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
#
# Stockfish is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Stockfish is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This finds the gUniversalNNUEOffset/gUniversalNNUESize symbols in the x86 slice
# and patches them to be an offset+size into the full executable.
#
# Usage: patch_x86_slice.sh <fat_binary> <x86_64_thin_slice> <nnue_file>
set -eu
FAT="$1"
X86="$2"
NET="$3"
# Must match volatile initializers in nnue_embed.cpp; byte order
# is reversed to match how it appears in the binary xxd
OFFSET_MAGIC=e7f50fe7f50ffeca
SIZE_MAGIC=2e51feca2e51feca
die() { echo "patch_x86_slice: $*" >&2; exit 1; }
# Find hex pattern in file, return byte offset
# Optional $3 = bytes to skip, $4 = max bytes to scan
find_bytes() {
file=$1; needle=$2; skip=${3:-0}; window=${4:-0}
[ "$window" -gt 0 ] && win="-l $window" || win=""
# Disassemble as hex, strip \n and search for the pattern
pos=$(xxd -s "$skip" $win -p "$file" | tr -d '\n' \
| awk -v n="$needle" '{ p = index($0, n); if (p) { print p; exit } }')
[ -n "$pos" ] || return 1
echo $(( skip + (pos - 1) / 2 ))
}
# Overwrite 8 bytes at a file offset
write_u64_le() {
f="$1"; off="$2"; v="$3"; bytes=""; i=0
while [ "$i" -lt 8 ]; do
bytes="$bytes$(printf '\\%03o' "$(( v & 255 ))")"
v=$(( v >> 8 )); i=$(( i + 1 ))
done
printf "$bytes" | dd of="$f" bs=1 seek="$off" conv=notrunc 2>/dev/null
}
needle=$(xxd -p -l 64 "$NET" | tr -d '\n') # take first bytes of network as needle
net_size=$(wc -c < "$NET" | tr -d ' ')
# The arm64 slice always follows the x86-64 slice in
# the fat binary, so scan from ~after the x86-64 slice.
x86_size=$(wc -c < "$X86" | tr -d ' ')
net_off=$(find_bytes "$FAT" "$needle" "$x86_size" 8000000) || die "network not found"
off_pos=$(find_bytes "$X86" "$OFFSET_MAGIC") || die "offset sentinel not found"
size_pos=$(find_bytes "$X86" "$SIZE_MAGIC") || die "size sentinel not found"
write_u64_le "$X86" "$off_pos" "$net_off"
write_u64_le "$X86" "$size_pos" "$net_size"
echo "patch_x86_slice.sh: network at offset $net_off + size $net_size "
+83
View File
@@ -0,0 +1,83 @@
# Stockfish, a UCI chess playing engine derived from Glaurung 2.1
# Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file)
#
# Stockfish is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Stockfish is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This is used if the universal binary is compiled on clang+Windows.
# Usage (avx2 example):
# awk -v MODNAME=x86_64_avx2 -f rewrite_asm_sections.awk stockfish.exe.lto.s > renamed.s
#
# The relevant output from --lto-emit-asm looks like:
# ...
# .section .ctors,"dw",associative,_ZN21Stockfish_x86_64_avx217SYSTEM_THREADS_NBE,unique,0
# .p2align 3, 0x0
# .quad __cxx_global_var_init
# .section .ctors,"a",unique,0
# .p2align 3, 0x0
# .quad _GLOBAL__sub_I_benchmark.cpp
# .quad _GLOBAL__sub_I_evaluate.cpp
# ...
# .quad _GLOBAL__sub_I_engine.cpp
# .quad _GLOBAL__sub_I_score.cpp
#
# .section .bss$_ZN21Stockfish_x86_64_avx2L26STARTUP_PROCESSOR_AFFINITYE,"bw",one_only,_ZN21Stockfish_x86_64_avx2L26STARTUP_PROCESSOR_AFFINITYE,unique,570
# ...
#
# We want to consolidate everything into a single section named x86_64_avx2_init, and insert
# symbols __start_x86_64_avx2_init/__stop_x86_64_avx2_init for entry_x86.cpp to use.
# (On ELF these symbols are implicitly defined, so naming it this way keeps the universal
# entry point consistent.)
BEGIN {
in_ctors = 0
}
# Match any .section line containing .ctors
/^[[:space:]]*\.section[[:space:]].*\.ctors/ {
# First .ctors section, emit start symbol
if (!in_ctors) {
in_ctors = 1
section = MODNAME "_init"
printf "\t.section\t%s,\"a\"\n", section
printf "\t.globl\t__start_%s\n", section
printf "__start_%s:\n", section
}
# Suppress all original .ctors section directives
next
}
# First non-.ctors .section after ctors block, emit stop symbol
in_ctors && /^[[:space:]]*\.section/ {
section = MODNAME "_init"
printf "\t.globl\t__stop_%s\n", section
printf "__stop_%s:\n", section
in_ctors = 0
}
{
print
}
END {
if (in_ctors) {
section = MODNAME "_init"
printf "\t.globl\t__stop_%s\n", section
printf "__stop_%s:\n", section
}
}
+210 -5
View File
@@ -4,10 +4,10 @@ import sys
import subprocess
import pathlib
import os
import fnmatch
from testing import (
EPD,
TSAN,
Stockfish as Engine,
MiniTestFramework,
OrderedClassMembers,
@@ -30,7 +30,7 @@ def get_prefix():
def get_threads():
if args.valgrind_thread or args.sanitizer_thread:
return 2
return 4
return 1
@@ -474,6 +474,203 @@ class TestSyzygy(metaclass=OrderedClassMembers):
self.stockfish.check_output(check_output)
self.stockfish.expect("bestmove *")
class TestEnPassantSanitization(metaclass=OrderedClassMembers):
def beforeAll(self):
self.stockfish = Stockfish()
def afterAll(self):
self.stockfish.quit()
assert self.stockfish.close() == 0
def afterEach(self):
assert postfix_check(self.stockfish.get_output()) == True
self.stockfish.clear_output()
def test_position_1(self):
self.stockfish.send_command("position fen rnbqkbnr/ppp1p1pp/5p2/3pP3/8/8/PPPP1PPP/RNBQKBNR w kq d6 0 3")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*rnbqkbnr/ppp1p1pp/5p2/3pP3/8/8/PPPP1PPP/RNBQKBNR w kq d6 0 3*")
def test_position_2(self):
self.stockfish.send_command("position fen k7/8/8/1pP5/2K5/8/8/8 w - b6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*k7/8/8/1pP5/2K5/8/8/8 w - b6 0 1*")
def test_position_3(self):
self.stockfish.send_command("position fen k1r5/8/8/1pP5/2K5/8/8/8 w - b6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*k1r5/8/8/1pP5/2K5/8/8/8 w - - 0 1*")
def test_position_4(self):
self.stockfish.send_command("position fen k1r5/8/8/1pP5/8/2K5/8/8 w - b6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*k1r5/8/8/1pP5/8/2K5/8/8 w - - 0 1*")
def test_position_5(self):
self.stockfish.send_command("position fen k1r5/8/8/PpP5/8/2K5/8/8 w - b6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*k1r5/8/8/PpP5/8/2K5/8/8 w - b6 0 1*")
def test_position_6(self):
self.stockfish.send_command("position fen k1r5/8/8/PpP5/2K5/8/8/8 w - b6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*k1r5/8/8/PpP5/2K5/8/8/8 w - b6 0 1*")
def test_position_7(self):
self.stockfish.send_command("position fen k7/4b3/8/PpP5/1K6/8/8/8 w - b6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*k7/4b3/8/PpP5/1K6/8/8/8 w - b6 0 1*")
def test_position_8(self):
self.stockfish.send_command("position fen k7/b5b1/8/2PpP3/3K4/8/8/8 w - d6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*k7/b5b1/8/2PpP3/3K4/8/8/8 w - - 0 1*")
def test_position_9(self):
self.stockfish.send_command("position fen k7/8/8/r2pPK2/8/8/8/8 w - d6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*k7/8/8/r2pPK2/8/8/8/8 w - - 0 1*")
def test_position_10(self):
self.stockfish.send_command("position fen k7/8/8/r1PpPK2/8/8/8/8 w - d6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*k7/8/8/r1PpPK2/8/8/8/8 w - d6 0 1*")
def test_position_11(self):
self.stockfish.send_command("position fen kb6/8/8/3pP3/5K2/8/8/8 w - d6 0 1")
self.stockfish.send_command("d")
self.stockfish.expect_for_line_matching("Fen*", "*kb6/8/8/3pP3/5K2/8/8/8 w - d6 0 1*")
def test_position_find_draw(self):
self.stockfish.send_command("position fen q4kb1/3Q2nq/8/r3PpK1/2n5/7q/8/q7 w - f6 0 1 moves d7c8 f8f7 c8d7 f7f8 d7d8 f8f7")
self.stockfish.send_command("go nodes 10000")
def check_output(output):
if fnmatch.fnmatch(output, "* score cp 0 * pv d8d7*"):
return True
self.stockfish.check_output(check_output)
self.stockfish.expect("bestmove d8d7*")
class TestInvalidFEN(metaclass=OrderedClassMembers):
def beforeEach(self):
self.stockfish = None
def afterEach(self):
assert postfix_check(self.stockfish.get_output()) == True
self.stockfish.clear_output()
def _expect_critical(self, fen):
self.stockfish = Stockfish(f"position fen {fen}".split(" "), True)
assert self.stockfish.process.returncode != 0
assert "CRITICAL ERROR" in self.stockfish.process.stdout
def test_no_kings(self):
self._expect_critical("8/8/8/8/8/8/8/8 w - - 0 1")
def test_invalid_piece(self):
self._expect_critical("rnbqkbnr/pppXpppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
def test_invalid_side_to_move(self):
self._expect_critical("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR x KQkq - 0 1")
def test_pawns_on_back_rank(self):
self._expect_critical("pppppppp/8/8/8/8/8/8/4K2k w - - 0 1")
def test_invalid_skip_count(self):
self._expect_critical("9/8/8/8/8/8/8/8 w - - 0 1")
class TestInvalidOptions(metaclass=OrderedClassMembers):
def beforeAll(self):
self.stockfish = Stockfish()
def afterAll(self):
self.stockfish.quit()
assert self.stockfish.close() == 0
def afterEach(self):
assert postfix_check(self.stockfish.get_output()) == True
self.stockfish.clear_output()
# Ignore bogus spin values
def test_spin_non_numeric(self):
self.stockfish.send_command("setoption name Threads value abc")
self.stockfish.send_command("isready")
self.stockfish.equals("readyok")
def test_spin_out_of_range(self):
self.stockfish.send_command("setoption name Threads value 999999999999")
self.stockfish.send_command("isready")
self.stockfish.equals("readyok")
def test_spin_negative(self):
self.stockfish.send_command("setoption name Threads value -5")
self.stockfish.send_command("isready")
self.stockfish.equals("readyok")
# Warn on bogus NUMA configs
def test_numa_garbage(self):
self.stockfish.send_command("setoption name NumaPolicy value zzz")
self.stockfish.expect("*NumaPolicy: invalid value 'zzz', keeping previous config.*")
def test_numa_malformed_range(self):
self.stockfish.send_command("setoption name NumaPolicy value 0-")
self.stockfish.expect("*NumaPolicy: invalid value '0-', keeping previous config.*")
def test_numa_overflow(self):
self.stockfish.send_command(
"setoption name NumaPolicy value 99999999999999999999999"
)
self.stockfish.expect("*NumaPolicy: invalid value*keeping previous config.*")
self.stockfish.send_command("isready")
self.stockfish.equals("readyok")
class TestBenchFile(metaclass=OrderedClassMembers):
def beforeEach(self):
self.stockfish = None
def afterEach(self):
assert postfix_check(self.stockfish.get_output()) == True
self.stockfish.clear_output()
def _bench(self, name, content):
with open(name, "w") as f:
f.write(content)
self.stockfish = Stockfish(f"bench 16 1 4 {name} depth".split(" "), True)
def test_valid_file(self):
self._bench("good.epd", "4k3/8/4K3/8/8/8/8/8 w - - 0 1\n")
assert self.stockfish.process.returncode == 0
assert "Nodes searched" in self.stockfish.process.stderr
def test_empty_file(self):
self._bench("empty.epd", "")
assert self.stockfish.process.returncode == 0
def test_malformed_fen(self):
self._bench("bad.epd", "not a valid fen\n")
assert self.stockfish.process.returncode != 0
assert "CRITICAL ERROR" in self.stockfish.process.stdout
def test_missing_file(self):
self.stockfish = Stockfish(
"bench 16 1 4 does_not_exist.epd depth".split(" "), True
)
assert self.stockfish.process.returncode != 0
def parse_args():
parser = argparse.ArgumentParser(description="Run Stockfish with testing options")
@@ -502,16 +699,24 @@ if __name__ == "__main__":
args = parse_args()
EPD.create_bench_epd()
TSAN.set_tsan_option()
Syzygy.download_syzygy()
framework = MiniTestFramework()
# Each test suite will be run inside a temporary directory
framework.run([TestCLI, TestInteractive, TestSyzygy])
framework.run(
[
TestCLI,
TestInteractive,
TestSyzygy,
TestEnPassantSanitization,
TestInvalidFEN,
TestInvalidOptions,
TestBenchFile,
]
)
EPD.delete_bench_epd()
TSAN.unset_tsan_option()
if framework.has_failed():
sys.exit(1)
+2 -1
View File
@@ -18,7 +18,8 @@ error()
trap 'error ${LINENO}' ERR
# obtain
eval "$RUN_PREFIX ./stockfish bench" > "$STDOUT_FILE" 2> "$STDERR_FILE" || error ${LINENO}
EXE=${EXE:-./stockfish}
eval "$RUN_PREFIX $EXE bench" > "$STDOUT_FILE" 2> "$STDERR_FILE" || error ${LINENO}
signature=$(grep "Nodes searched : " "$STDERR_FILE" | awk '{print $4}')
rm -f "$STDOUT_FILE" "$STDERR_FILE"
+60 -51
View File
@@ -6,12 +6,12 @@ import time
import sys
import traceback
import fnmatch
from functools import wraps
from contextlib import redirect_stdout
import io
import tarfile
import pathlib
import concurrent.futures
import queue
import threading
import tempfile
import shutil
import requests
@@ -43,27 +43,6 @@ class Valgrind:
return ["valgrind", "--error-exitcode=42", "--fair-sched=try"]
class TSAN:
@staticmethod
def set_tsan_option():
with open(f"tsan.supp", "w") as f:
f.write(
"""
race:Stockfish::TTEntry::read
race:Stockfish::TTEntry::save
race:Stockfish::TranspositionTable::probe
race:Stockfish::TranspositionTable::hashfull
"""
)
os.environ["TSAN_OPTIONS"] = "suppressions=./tsan.supp"
@staticmethod
def unset_tsan_option():
os.environ.pop("TSAN_OPTIONS", None)
os.remove(f"tsan.supp")
class EPD:
@staticmethod
def create_bench_epd():
@@ -122,29 +101,15 @@ class OrderedClassMembers(type):
class TimeoutException(Exception):
def __init__(self, message: str, timeout: int):
def __init__(self, message: str, timeout: float):
super().__init__(message)
self.message = message
self.timeout = timeout
def timeout_decorator(timeout: float):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(func, *args, **kwargs)
try:
result = future.result(timeout=timeout)
except concurrent.futures.TimeoutError:
raise TimeoutException(
f"Function {func.__name__} timed out after {timeout} seconds",
timeout,
)
return result
return wrapper
return decorator
class UnexpectedOutputException(Exception):
def __init__(self, actual: str, expected: str):
self.actual = actual
self.expected = expected
class MiniTestFramework:
@@ -230,6 +195,11 @@ class MiniTestFramework:
f" {method} (hit execution limit of {e.timeout} seconds)"
)
if isinstance(e, UnexpectedOutputException):
self.print_failure(
f" {method} encountered unexpected output: \"{e.actual}\" when output matching \"{e.expected}\" was expected"
)
if isinstance(e, AssertionError):
self.__handle_assertion_error(t0, method)
@@ -294,6 +264,8 @@ class Stockfish:
self.cli = cli
self.prefix = prefix
self.output = []
self.output_queue = queue.Queue()
self.reader_thread = None
self.start()
@@ -325,6 +297,21 @@ class Stockfish:
universal_newlines=True,
bufsize=1,
)
self.reader_thread = threading.Thread(
target=self._read_process_output, daemon=True
)
self.reader_thread.start()
def _read_process_output(self):
try:
for line in self.process.stdout:
line = line.strip()
self.output.append(line)
self.output_queue.put(line)
except (OSError, ValueError):
pass
finally:
self.output_queue.put(None)
def setoption(self, name: str, value: str):
self.send_command(f"setoption name {name} value {value}")
@@ -338,31 +325,26 @@ class Stockfish:
self.process.stdin.write(command + "\n")
self.process.stdin.flush()
@timeout_decorator(MAX_TIMEOUT)
def equals(self, expected_output: str):
for line in self.readline():
if line == expected_output:
return
@timeout_decorator(MAX_TIMEOUT)
def expect(self, expected_output: str):
for line in self.readline():
if fnmatch.fnmatch(line, expected_output):
return
@timeout_decorator(MAX_TIMEOUT)
def contains(self, expected_output: str):
for line in self.readline():
if expected_output in line:
return
@timeout_decorator(MAX_TIMEOUT)
def starts_with(self, expected_output: str):
for line in self.readline():
if line.startswith(expected_output):
return
@timeout_decorator(MAX_TIMEOUT)
def check_output(self, callback):
if not callback:
raise ValueError("Callback function is required")
@@ -371,14 +353,41 @@ class Stockfish:
if callback(line) == True:
return
def readline(self):
def expect_for_line_matching(self, line_match: str, expected: str):
for line in self.readline():
if fnmatch.fnmatch(line, line_match):
if fnmatch.fnmatch(line, expected):
break
else:
raise UnexpectedOutputException(line, expected)
def readline(self, timeout: float = MAX_TIMEOUT):
if not self.process:
raise RuntimeError("Stockfish process is not started")
deadline = time.monotonic() + timeout
while True:
self._check_process_alive()
line = self.process.stdout.readline().strip()
self.output.append(line)
remaining_time = deadline - time.monotonic()
if remaining_time <= 0:
raise TimeoutException(
f"No matching output received after {timeout} seconds",
timeout,
)
try:
line = self.output_queue.get(timeout=remaining_time)
except queue.Empty:
raise TimeoutException(
f"No matching output received after {timeout} seconds",
timeout,
)
if line is None:
self._check_process_alive()
raise RuntimeError("Stockfish process has terminated")
yield line