1
0
Fork 0
mirror of https://github.com/sockspls/badfish synced 2025-04-29 16:23:09 +00:00

add clang-format

This introduces clang-format to enforce a consistent code style for Stockfish.

Having a documented and consistent style across the code will make contributing easier
for new developers, and will make larger changes to the codebase easier to make.

To facilitate formatting, this PR includes a Makefile target (`make format`) to format the code,
this requires clang-format (version 17 currently) to be installed locally.

Installing clang-format is straightforward on most OS and distros
(e.g. with https://apt.llvm.org/, brew install clang-format, etc), as this is part of quite commonly
used suite of tools and compilers (llvm / clang).

Additionally, a CI action is present that will verify if the code requires formatting,
and comment on the PR as needed. Initially, correct formatting is not required, it will be
done by maintainers as part of the merge or in later commits, but obviously this is encouraged.

fixes https://github.com/official-stockfish/Stockfish/issues/3608
closes https://github.com/official-stockfish/Stockfish/pull/4790

Co-Authored-By: Joost VandeVondele <Joost.VandeVondele@gmail.com>
This commit is contained in:
Disservin 2023-10-21 11:40:56 +02:00 committed by Joost VandeVondele
parent 8366ec48ae
commit 2d0237db3f
49 changed files with 6403 additions and 6197 deletions

44
.clang-format Normal file
View file

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

View file

@ -0,0 +1,51 @@
# This workflow will run clang-format and comment on the PR.
# Because of security reasons, it is crucial that this workflow
# executes no shell script nor runs make.
# Read this before editing: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
name: Stockfish
on:
pull_request_target:
branches:
- 'master'
paths:
- '**.cpp'
- '**.h'
jobs:
Stockfish:
name: clang-format check
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Run clang-format style check
uses: jidicula/clang-format-action@f62da5e3d3a2d88ff364771d9d938773a618ab5e
id: clang-format
continue-on-error: true
with:
clang-format-version: '17'
exclude-regex: 'incbin'
- name: Comment on PR
if: steps.clang-format.outcome == 'failure'
uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308
with:
message: |
clang-format 17 needs to be run on this PR.
If you do not have clang-format installed, the maintainer will run it when merging.
For the exact version please see https://packages.ubuntu.com/mantic/clang-format-17.
_(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
comment_tag: execution
- name: Comment on PR
if: steps.clang-format.outcome != 'failure'
uses: thollander/actions-comment-pull-request@1d3973dc4b8e1399c0620d3f2b1aa5e795465308
with:
message: |
_(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_
create_if_not_exists: false
comment_tag: execution
mode: delete

View file

@ -57,8 +57,9 @@ discussion._
## Code Style
We do not have a strict code style. But it is best to stick to the existing
style of the file you are editing.
Changes to Stockfish C++ code should respect our coding style defined by
[.clang-format](.clang-format). You can format your changes by running
`make format`. This requires clang-format version 17 to be installed on your system.
## Community and Communication

View file

@ -57,6 +57,14 @@ SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \
search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
nnue/evaluate_nnue.cpp nnue/features/half_ka_v2_hm.cpp
HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h \
nnue/evaluate_nnue.h nnue/features/half_ka_v2_hm.h nnue/layers/affine_transform.h \
nnue/layers/affine_transform_sparse_input.h nnue/layers/clipped_relu.h nnue/layers/simd.h \
nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h nnue/nnue_architecture.h \
nnue/nnue_common.h nnue/nnue_feature_transformer.h position.h \
search.h syzygy/tbprobe.h thread.h thread_win32_osx.h timeman.h \
tt.h tune.h types.h uci.h
OBJS = $(notdir $(SRCS:.cpp=.o))
VPATH = syzygy:nnue:nnue/features
@ -145,6 +153,12 @@ dotprod = no
arm_version = 0
STRIP = strip
ifneq ($(shell command -v clang-format-17),)
CLANG-FORMAT = clang-format-17
else
CLANG-FORMAT = clang-format
endif
### 2.2 Architecture specific
ifeq ($(findstring x86,$(ARCH)),x86)
@ -936,6 +950,9 @@ net: netvariables
fi; \
fi; \
format:
$(CLANG-FORMAT) -i $(SRCS) $(HEADERS) -style=file:../.clang-format
# default target
default:
help

View file

@ -27,6 +27,7 @@
namespace {
// clang-format off
const std::vector<std::string> Defaults = {
"setoption name UCI_Chess960 value false",
"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
@ -90,6 +91,7 @@ const std::vector<std::string> Defaults = {
"nqbnrkrb/pppppppp/8/8/8/8/PPPPPPPP/NQBNRKRB w KQkq - 0 1",
"setoption name UCI_Chess960 value false"
};
// clang-format on
} // namespace

View file

@ -39,10 +39,10 @@ Magic BishopMagics[SQUARE_NB];
namespace {
Bitboard RookTable[0x19000]; // To store rook attacks
Bitboard BishopTable[0x1480]; // To store bishop attacks
Bitboard RookTable[0x19000]; // To store rook attacks
Bitboard BishopTable[0x1480]; // To store bishop attacks
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
void init_magics(PieceType pt, Bitboard table[], Magic magics[]);
}
@ -95,22 +95,23 @@ void Bitboards::init() {
PawnAttacks[WHITE][s1] = pawn_attacks_bb<WHITE>(square_bb(s1));
PawnAttacks[BLACK][s1] = pawn_attacks_bb<BLACK>(square_bb(s1));
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9} )
for (int step : {-9, -8, -7, -1, 1, 7, 8, 9})
PseudoAttacks[KING][s1] |= safe_destination(s1, step);
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17} )
for (int step : {-17, -15, -10, -6, 6, 10, 15, 17})
PseudoAttacks[KNIGHT][s1] |= safe_destination(s1, step);
PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb<BISHOP>(s1, 0);
PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0);
PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ROOK][s1] = attacks_bb<ROOK>(s1, 0);
for (PieceType pt : { BISHOP, ROOK })
for (PieceType pt : {BISHOP, ROOK})
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
{
if (PseudoAttacks[pt][s1] & s2)
{
LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2;
BetweenBB[s1][s2] = (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
BetweenBB[s1][s2] =
(attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1)));
}
BetweenBB[s1][s2] |= s2;
}
@ -119,7 +120,7 @@ void Bitboards::init() {
namespace {
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
Bitboard attacks = 0;
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
@ -133,19 +134,19 @@ namespace {
}
return attacks;
}
}
// init_magics() computes all rook and bishop attacks at startup. Magic
// bitboards are used to look up attacks of sliding pieces. As a reference see
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
// called "fancy" approach.
// init_magics() computes all rook and bishop attacks at startup. Magic
// bitboards are used to look up attacks of sliding pieces. As a reference see
// www.chessprogramming.org/Magic_Bitboards. In particular, here we use the so
// called "fancy" approach.
void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
void init_magics(PieceType pt, Bitboard table[], Magic magics[]) {
// Optimal PRNG seeds to pick the correct magics in the shortest time
int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 },
{ 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } };
int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020},
{728, 10316, 55013, 32803, 12281, 15100, 16645, 255}};
Bitboard occupancy[4096], reference[4096], edges, b;
int epoch[4096] = {}, cnt = 0, size = 0;
@ -171,7 +172,8 @@ namespace {
// Use Carry-Rippler trick to enumerate all subsets of masks[s] and
// store the corresponding sliding attack bitboard in reference[].
b = size = 0;
do {
do
{
occupancy[size] = b;
reference[size] = sliding_attack(pt, s, b);
@ -189,9 +191,9 @@ namespace {
// Find a magic for square 's' picking up an (almost) random number
// until we find the one that passes the verification test.
for (int i = 0; i < size; )
for (int i = 0; i < size;)
{
for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; )
for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;)
m.magic = rng.sparse_rand<Bitboard>();
// A good magic must map every possible occupancy to an index that
@ -214,7 +216,7 @@ namespace {
}
}
}
}
}
}
} // namespace Stockfish

View file

@ -98,9 +98,9 @@ inline Bitboard square_bb(Square s) {
// Overloads of bitwise operators between a Bitboard and a Square for testing
// whether a given bit is set in a bitboard, and for setting and clearing bits.
inline Bitboard operator&( Bitboard b, Square s) { return b & square_bb(s); }
inline Bitboard operator|( Bitboard b, Square s) { return b | square_bb(s); }
inline Bitboard operator^( Bitboard b, Square s) { return b ^ square_bb(s); }
inline Bitboard operator&(Bitboard b, Square s) { return b & square_bb(s); }
inline Bitboard operator|(Bitboard b, Square s) { return b | square_bb(s); }
inline Bitboard operator^(Bitboard b, Square s) { return b ^ square_bb(s); }
inline Bitboard& operator|=(Bitboard& b, Square s) { return b |= square_bb(s); }
inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= square_bb(s); }
@ -110,40 +110,35 @@ inline Bitboard operator^(Square s, Bitboard b) { return b ^ s; }
inline Bitboard operator|(Square s1, Square s2) { return square_bb(s1) | s2; }
constexpr bool more_than_one(Bitboard b) {
return b & (b - 1);
}
constexpr bool more_than_one(Bitboard b) { return b & (b - 1); }
// rank_bb() and file_bb() return a bitboard representing all the squares on
// the given file or rank.
constexpr Bitboard rank_bb(Rank r) {
return Rank1BB << (8 * r);
}
constexpr Bitboard rank_bb(Rank r) { return Rank1BB << (8 * r); }
constexpr Bitboard rank_bb(Square s) {
return rank_bb(rank_of(s));
}
constexpr Bitboard rank_bb(Square s) { return rank_bb(rank_of(s)); }
constexpr Bitboard file_bb(File f) {
return FileABB << f;
}
constexpr Bitboard file_bb(File f) { return FileABB << f; }
constexpr Bitboard file_bb(Square s) {
return file_bb(file_of(s));
}
constexpr Bitboard file_bb(Square s) { return file_bb(file_of(s)); }
// shift() moves a bitboard one or two steps as specified by the direction D
template<Direction D>
constexpr Bitboard shift(Bitboard b) {
return D == NORTH ? b << 8 : D == SOUTH ? b >> 8
: D == NORTH+NORTH? b <<16 : D == SOUTH+SOUTH? b >>16
: D == EAST ? (b & ~FileHBB) << 1 : D == WEST ? (b & ~FileABB) >> 1
: D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == NORTH_WEST ? (b & ~FileABB) << 7
: D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9
return D == NORTH ? b << 8
: D == SOUTH ? b >> 8
: D == NORTH + NORTH ? b << 16
: D == SOUTH + SOUTH ? b >> 16
: D == EAST ? (b & ~FileHBB) << 1
: D == WEST ? (b & ~FileABB) >> 1
: D == NORTH_EAST ? (b & ~FileHBB) << 9
: D == NORTH_WEST ? (b & ~FileABB) << 7
: D == SOUTH_EAST ? (b & ~FileHBB) >> 7
: D == SOUTH_WEST ? (b & ~FileABB) >> 9
: 0;
}
@ -194,18 +189,26 @@ inline Bitboard between_bb(Square s1, Square s2) {
// aligned() returns true if the squares s1, s2 and s3 are aligned either on a
// straight or on a diagonal line.
inline bool aligned(Square s1, Square s2, Square s3) {
return line_bb(s1, s2) & s3;
}
inline bool aligned(Square s1, Square s2, Square s3) { return line_bb(s1, s2) & s3; }
// distance() functions return the distance between x and y, defined as the
// number of steps for a king in x to reach y.
template<typename T1 = Square> inline int distance(Square x, Square y);
template<> inline int distance<File>(Square x, Square y) { return std::abs(file_of(x) - file_of(y)); }
template<> inline int distance<Rank>(Square x, Square y) { return std::abs(rank_of(x) - rank_of(y)); }
template<> inline int distance<Square>(Square x, Square y) { return SquareDistance[x][y]; }
template<typename T1 = Square>
inline int distance(Square x, Square y);
template<>
inline int distance<File>(Square x, Square y) {
return std::abs(file_of(x) - file_of(y));
}
template<>
inline int distance<Rank>(Square x, Square y) {
return std::abs(rank_of(x) - rank_of(y));
}
template<>
inline int distance<Square>(Square x, Square y) {
return SquareDistance[x][y];
}
inline int edge_distance(File f) { return std::min(f, File(FILE_H - f)); }
@ -232,10 +235,14 @@ inline Bitboard attacks_bb(Square s, Bitboard occupied) {
switch (Pt)
{
case BISHOP: return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
case ROOK : return RookMagics[s].attacks[ RookMagics[s].index(occupied)];
case QUEEN : return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
default : return PseudoAttacks[Pt][s];
case BISHOP :
return BishopMagics[s].attacks[BishopMagics[s].index(occupied)];
case ROOK :
return RookMagics[s].attacks[RookMagics[s].index(occupied)];
case QUEEN :
return attacks_bb<BISHOP>(s, occupied) | attacks_bb<ROOK>(s, occupied);
default :
return PseudoAttacks[Pt][s];
}
}
@ -245,10 +252,14 @@ inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) {
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];
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];
}
}
@ -259,7 +270,10 @@ inline int popcount(Bitboard b) {
#ifndef USE_POPCNT
union { Bitboard bb; uint16_t u[4]; } v = { b };
union {
Bitboard bb;
uint16_t u[4];
} v = {b};
return PopCnt16[v.u[0]] + PopCnt16[v.u[1]] + PopCnt16[v.u[2]] + PopCnt16[v.u[3]];
#elif defined(_MSC_VER)
@ -290,7 +304,7 @@ inline Square msb(Bitboard b) {
#elif defined(_MSC_VER) // MSVC
#ifdef _WIN64 // MSVC, WIN64
#ifdef _WIN64 // MSVC, WIN64
inline Square lsb(Bitboard b) {
assert(b);
@ -306,16 +320,19 @@ inline Square msb(Bitboard b) {
return (Square) idx;
}
#else // MSVC, WIN32
#else // MSVC, WIN32
inline Square lsb(Bitboard b) {
assert(b);
unsigned long idx;
if (b & 0xffffffff) {
if (b & 0xffffffff)
{
_BitScanForward(&idx, int32_t(b));
return Square(idx);
} else {
}
else
{
_BitScanForward(&idx, int32_t(b >> 32));
return Square(idx + 32);
}
@ -325,20 +342,23 @@ inline Square msb(Bitboard b) {
assert(b);
unsigned long idx;
if (b >> 32) {
if (b >> 32)
{
_BitScanReverse(&idx, int32_t(b >> 32));
return Square(idx + 32);
} else {
}
else
{
_BitScanReverse(&idx, int32_t(b));
return Square(idx);
}
}
#endif
#endif
#else // Compiler is neither GCC nor MSVC compatible
#error "Compiler not supported."
#error "Compiler not supported."
#endif

View file

@ -43,11 +43,11 @@
// 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(EmbeddedNNUE, EvalFileDefaultName);
INCBIN(EmbeddedNNUE, EvalFileDefaultName);
#else
const unsigned char gEmbeddedNNUEData[1] = {0x0};
const unsigned char *const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
const unsigned int gEmbeddedNNUESize = 1;
const unsigned char gEmbeddedNNUEData[1] = {0x0};
const unsigned char* const gEmbeddedNNUEEnd = &gEmbeddedNNUEData[1];
const unsigned int gEmbeddedNNUESize = 1;
#endif
@ -55,27 +55,28 @@ namespace Stockfish {
namespace Eval {
std::string currentEvalFileName = "None";
std::string currentEvalFileName = "None";
// NNUE::init() tries to load a NNUE network at startup time, or when the engine
// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
// The name of the NNUE network is always retrieved from the EvalFile option.
// We search the given network in three locations: internally (the default
// network may be embedded in the binary), in the active working directory and
// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
// variable to have the engine search in a special directory in their distro.
// NNUE::init() tries to load a NNUE network at startup time, or when the engine
// receives a UCI command "setoption name EvalFile value nn-[a-z0-9]{12}.nnue"
// The name of the NNUE network is always retrieved from the EvalFile option.
// We search the given network in three locations: internally (the default
// network may be embedded in the binary), in the active working directory and
// in the engine directory. Distro packagers may define the DEFAULT_NNUE_DIRECTORY
// variable to have the engine search in a special directory in their distro.
void NNUE::init() {
void NNUE::init() {
std::string eval_file = std::string(Options["EvalFile"]);
if (eval_file.empty())
eval_file = EvalFileDefaultName;
#if defined(DEFAULT_NNUE_DIRECTORY)
std::vector<std::string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory , stringify(DEFAULT_NNUE_DIRECTORY) };
#else
std::vector<std::string> dirs = { "<internal>" , "" , CommandLine::binaryDirectory };
#endif
#if defined(DEFAULT_NNUE_DIRECTORY)
std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory,
stringify(DEFAULT_NNUE_DIRECTORY)};
#else
std::vector<std::string> dirs = {"<internal>", "", CommandLine::binaryDirectory};
#endif
for (const std::string& directory : dirs)
if (currentEvalFileName != eval_file)
@ -90,11 +91,16 @@ namespace Eval {
if (directory == "<internal>" && eval_file == EvalFileDefaultName)
{
// C++ way to prepare a buffer for a memory stream
class MemoryBuffer : public std::basic_streambuf<char> {
public: MemoryBuffer(char* p, size_t n) { setg(p, p, p + n); setp(p, p + n); }
class MemoryBuffer: public std::basic_streambuf<char> {
public:
MemoryBuffer(char* p, size_t n) {
setg(p, p, p + n);
setp(p, p + n);
}
};
MemoryBuffer buffer(const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
MemoryBuffer buffer(
const_cast<char*>(reinterpret_cast<const char*>(gEmbeddedNNUEData)),
size_t(gEmbeddedNNUESize));
(void) gEmbeddedNNUEEnd; // Silence warning on unused variable
@ -103,10 +109,10 @@ namespace Eval {
currentEvalFileName = eval_file;
}
}
}
}
// NNUE::verify() verifies that the last net used was loaded successfully
void NNUE::verify() {
// NNUE::verify() verifies that the last net used was loaded successfully
void NNUE::verify() {
std::string eval_file = std::string(Options["EvalFile"]);
if (eval_file.empty())
@ -115,10 +121,14 @@ namespace Eval {
if (currentEvalFileName != eval_file)
{
std::string msg1 = "Network evaluation parameters compatible with the engine must be available.";
std::string msg1 =
"Network evaluation parameters compatible with the engine must be available.";
std::string msg2 = "The network file " + eval_file + " was not loaded successfully.";
std::string msg3 = "The UCI option EvalFile might need to specify the full path, including the directory name, to the network file.";
std::string msg4 = "The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/" + std::string(EvalFileDefaultName);
std::string msg3 =
"The UCI option EvalFile might need to specify the full path, including the directory name, to the network file.";
std::string msg4 =
"The default net can be downloaded from: https://tests.stockfishchess.org/api/nn/"
+ std::string(EvalFileDefaultName);
std::string msg5 = "The engine will be terminated now.";
sync_cout << "info string ERROR: " << msg1 << sync_endl;
@ -131,7 +141,7 @@ namespace Eval {
}
sync_cout << "info string NNUE evaluation using " << eval_file << sync_endl;
}
}
}
@ -157,8 +167,7 @@ Value Eval::evaluate(const Position& pos) {
int shuffling = pos.rule50_count();
int simpleEval = simple_eval(pos, stm) + (int(pos.key() & 7) - 3);
bool lazy = abs(simpleEval) >= RookValue + KnightValue
+ 16 * shuffling * shuffling
bool lazy = abs(simpleEval) >= RookValue + KnightValue + 16 * shuffling * shuffling
+ abs(pos.this_thread()->bestValue)
+ abs(pos.this_thread()->rootSimpleEval);
@ -176,8 +185,7 @@ Value Eval::evaluate(const Position& pos) {
nnue -= nnue * (nnueComplexity + abs(simpleEval - nnue)) / 32768;
int npm = pos.non_pawn_material() / 64;
v = ( nnue * (915 + npm + 9 * pos.count<PAWN>())
+ optimism * (154 + npm )) / 1024;
v = (nnue * (915 + npm + 9 * pos.count<PAWN>()) + optimism * (154 + npm)) / 1024;
}
// Damp down the evaluation linearly when shuffling

View file

@ -29,24 +29,24 @@ class Position;
namespace Eval {
std::string trace(Position& pos);
std::string trace(Position& pos);
Value simple_eval(const Position& pos, Color c);
Value evaluate(const Position& pos);
Value simple_eval(const Position& pos, Color c);
Value evaluate(const Position& pos);
extern std::string currentEvalFileName;
extern std::string currentEvalFileName;
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-0000000000a0.nnue"
// The default net name MUST follow the format nn-[SHA256 first 12 digits].nnue
// for the build process (profile-build and fishtest) to work. Do not change the
// name of the macro, as it is used in the Makefile.
#define EvalFileDefaultName "nn-0000000000a0.nnue"
namespace NNUE {
namespace NNUE {
void init();
void verify();
void init();
void verify();
} // namespace NNUE
} // namespace NNUE
} // namespace Eval

View file

@ -19,30 +19,31 @@
#include "misc.h"
#ifdef _WIN32
#if _WIN32_WINNT < 0x0601
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
#endif
#if _WIN32_WINNT < 0x0601
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <windows.h>
// The needed Windows API for processor groups could be missed from old Windows
// versions, so instead of calling them directly (forcing the linker to resolve
// the calls at compile time), try to load them at runtime. To do this we need
// first to define the corresponding function pointers.
extern "C" {
using fun1_t = bool(*)(LOGICAL_PROCESSOR_RELATIONSHIP,
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD);
using fun2_t = bool(*)(USHORT, PGROUP_AFFINITY);
using fun3_t = bool(*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
using fun4_t = bool(*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
using fun5_t = WORD(*)();
using fun6_t = bool(*)(HANDLE, DWORD, PHANDLE);
using fun7_t = bool(*)(LPCSTR, LPCSTR, PLUID);
using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
using fun1_t = bool (*)(LOGICAL_PROCESSOR_RELATIONSHIP,
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX,
PDWORD);
using fun2_t = bool (*)(USHORT, PGROUP_AFFINITY);
using fun3_t = bool (*)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY);
using fun4_t = bool (*)(USHORT, PGROUP_AFFINITY, USHORT, PUSHORT);
using fun5_t = WORD (*)();
using fun6_t = bool (*)(HANDLE, DWORD, PHANDLE);
using fun7_t = bool (*)(LPCSTR, LPCSTR, PLUID);
using fun8_t = bool (*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES, PDWORD);
}
#endif
@ -59,12 +60,14 @@ using fun8_t = bool(*)(HANDLE, BOOL, PTOKEN_PRIVILEGES, DWORD, PTOKEN_PRIVILEGES
#include "types.h"
#if defined(__linux__) && !defined(__ANDROID__)
#include <sys/mman.h>
#include <sys/mman.h>
#endif
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) || defined(__e2k__)
#define POSIXALIGNEDALLOC
#include <stdlib.h>
#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) \
|| (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) \
|| defined(__e2k__)
#define POSIXALIGNEDALLOC
#include <stdlib.h>
#endif
namespace Stockfish {
@ -82,7 +85,9 @@ constexpr std::string_view version = "dev";
struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and cout
Tie(std::streambuf* b, std::streambuf* l) : buf(b), logBuf(l) {}
Tie(std::streambuf* b, std::streambuf* l) :
buf(b),
logBuf(l) {}
int sync() override { return logBuf->pubsync(), buf->pubsync(); }
int overflow(int c) override { return log(buf->sputc(char(c)), "<< "); }
@ -104,13 +109,15 @@ struct Tie: public std::streambuf { // MSVC requires split streambuf for cin and
class Logger {
Logger() : in(std::cin.rdbuf(), file.rdbuf()), out(std::cout.rdbuf(), file.rdbuf()) {}
Logger() :
in(std::cin.rdbuf(), file.rdbuf()),
out(std::cout.rdbuf(), file.rdbuf()) {}
~Logger() { start(""); }
std::ofstream file;
Tie in, out;
public:
public:
static void start(const std::string& fname) {
static Logger l;
@ -158,28 +165,28 @@ std::string engine_info(bool to_uci) {
if constexpr (version == "dev")
{
ss << "-";
#ifdef GIT_DATE
#ifdef GIT_DATE
ss << stringify(GIT_DATE);
#else
#else
constexpr std::string_view months("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec");
std::string month, day, year;
std::stringstream date(__DATE__); // From compiler, format is "Sep 21 2008"
date >> month >> day >> year;
ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4) << std::setw(2) << std::setfill('0') << day;
#endif
ss << year << std::setw(2) << std::setfill('0') << (1 + months.find(month) / 4)
<< std::setw(2) << std::setfill('0') << day;
#endif
ss << "-";
#ifdef GIT_SHA
#ifdef GIT_SHA
ss << stringify(GIT_SHA);
#else
#else
ss << "nogit";
#endif
#endif
}
ss << (to_uci ? "\nid author ": " by ")
<< "the Stockfish developers (see AUTHORS file)";
ss << (to_uci ? "\nid author " : " by ") << "the Stockfish developers (see AUTHORS file)";
return ss.str();
}
@ -189,31 +196,32 @@ std::string engine_info(bool to_uci) {
std::string compiler_info() {
#define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch)
#define make_version_string(major, minor, patch) \
stringify(major) "." stringify(minor) "." stringify(patch)
// Predefined macros hell:
//
// __GNUC__ Compiler is GCC, Clang or ICX
// __clang__ Compiler is Clang or ICX
// __INTEL_LLVM_COMPILER Compiler is ICX
// _MSC_VER Compiler is MSVC
// _WIN32 Building on Windows (any)
// _WIN64 Building on Windows 64 bit
// Predefined macros hell:
//
// __GNUC__ Compiler is GCC, Clang or ICX
// __clang__ Compiler is Clang or ICX
// __INTEL_LLVM_COMPILER Compiler is ICX
// _MSC_VER Compiler is MSVC
// _WIN32 Building on Windows (any)
// _WIN64 Building on Windows 64 bit
std::string compiler = "\nCompiled by : ";
#if defined(__INTEL_LLVM_COMPILER)
#if defined(__INTEL_LLVM_COMPILER)
compiler += "ICX ";
compiler += stringify(__INTEL_LLVM_COMPILER);
#elif defined(__clang__)
#elif defined(__clang__)
compiler += "clang++ ";
compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
#elif _MSC_VER
#elif _MSC_VER
compiler += "MSVC ";
compiler += "(version ";
compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
compiler += ")";
#elif defined(__e2k__) && defined(__LCC__)
#elif defined(__e2k__) && defined(__LCC__)
#define dot_ver2(n) \
compiler += char('.'); \
compiler += char('0' + (n) / 10); \
@ -222,82 +230,80 @@ std::string compiler_info() {
compiler += "MCST LCC ";
compiler += "(version ";
compiler += std::to_string(__LCC__ / 100);
dot_ver2(__LCC__ % 100)
dot_ver2(__LCC_MINOR__)
compiler += ")";
#elif __GNUC__
dot_ver2(__LCC__ % 100) dot_ver2(__LCC_MINOR__) compiler += ")";
#elif __GNUC__
compiler += "g++ (GNUC) ";
compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
#else
#else
compiler += "Unknown compiler ";
compiler += "(unknown version)";
#endif
#endif
#if defined(__APPLE__)
#if defined(__APPLE__)
compiler += " on Apple";
#elif defined(__CYGWIN__)
#elif defined(__CYGWIN__)
compiler += " on Cygwin";
#elif defined(__MINGW64__)
#elif defined(__MINGW64__)
compiler += " on MinGW64";
#elif defined(__MINGW32__)
#elif defined(__MINGW32__)
compiler += " on MinGW32";
#elif defined(__ANDROID__)
#elif defined(__ANDROID__)
compiler += " on Android";
#elif defined(__linux__)
#elif defined(__linux__)
compiler += " on Linux";
#elif defined(_WIN64)
#elif defined(_WIN64)
compiler += " on Microsoft Windows 64-bit";
#elif defined(_WIN32)
#elif defined(_WIN32)
compiler += " on Microsoft Windows 32-bit";
#else
#else
compiler += " on unknown system";
#endif
#endif
compiler += "\nCompilation architecture : ";
#if defined(ARCH)
#if defined(ARCH)
compiler += stringify(ARCH);
#else
#else
compiler += "(undefined architecture)";
#endif
#endif
compiler += "\nCompilation settings : ";
compiler += (Is64Bit ? "64bit" : "32bit");
#if defined(USE_VNNI)
#if defined(USE_VNNI)
compiler += " VNNI";
#endif
#if defined(USE_AVX512)
#endif
#if defined(USE_AVX512)
compiler += " AVX512";
#endif
#endif
compiler += (HasPext ? " BMI2" : "");
#if defined(USE_AVX2)
#if defined(USE_AVX2)
compiler += " AVX2";
#endif
#if defined(USE_SSE41)
#endif
#if defined(USE_SSE41)
compiler += " SSE41";
#endif
#if defined(USE_SSSE3)
#endif
#if defined(USE_SSSE3)
compiler += " SSSE3";
#endif
#if defined(USE_SSE2)
#endif
#if defined(USE_SSE2)
compiler += " SSE2";
#endif
#endif
compiler += (HasPopCnt ? " POPCNT" : "");
#if defined(USE_NEON_DOTPROD)
#if defined(USE_NEON_DOTPROD)
compiler += " NEON_DOTPROD";
#elif defined(USE_NEON)
#elif defined(USE_NEON)
compiler += " NEON";
#endif
#endif
#if !defined(NDEBUG)
#if !defined(NDEBUG)
compiler += " DEBUG";
#endif
#endif
compiler += "\nCompiler __VERSION__ macro : ";
#ifdef __VERSION__
#ifdef __VERSION__
compiler += __VERSION__;
#else
#else
compiler += "(undefined macro)";
#endif
#endif
compiler += "\n";
@ -312,7 +318,7 @@ namespace {
template<size_t N>
struct DebugInfo {
std::atomic<int64_t> data[N] = { 0 };
std::atomic<int64_t> data[N] = {0};
constexpr inline std::atomic<int64_t>& operator[](int index) { return data[index]; }
};
@ -362,37 +368,29 @@ void dbg_print() {
for (int i = 0; i < MaxDebugSlots; ++i)
if ((n = hit[i][0]))
std::cerr << "Hit #" << i
<< ": Total " << n << " Hits " << hit[i][1]
<< " Hit Rate (%) " << 100.0 * E(hit[i][1])
<< std::endl;
std::cerr << "Hit #" << i << ": Total " << n << " Hits " << hit[i][1]
<< " Hit Rate (%) " << 100.0 * E(hit[i][1]) << std::endl;
for (int i = 0; i < MaxDebugSlots; ++i)
if ((n = mean[i][0]))
{
std::cerr << "Mean #" << i
<< ": Total " << n << " Mean " << E(mean[i][1])
<< std::endl;
std::cerr << "Mean #" << i << ": Total " << n << " Mean " << E(mean[i][1]) << std::endl;
}
for (int i = 0; i < MaxDebugSlots; ++i)
if ((n = stdev[i][0]))
{
double r = sqrt(E(stdev[i][2]) - sqr(E(stdev[i][1])));
std::cerr << "Stdev #" << i
<< ": Total " << n << " Stdev " << r
<< std::endl;
std::cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r << std::endl;
}
for (int i = 0; i < MaxDebugSlots; ++i)
if ((n = correl[i][0]))
{
double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3]))
/ ( sqrt(E(correl[i][2]) - sqr(E(correl[i][1])))
/ (sqrt(E(correl[i][2]) - sqr(E(correl[i][1])))
* sqrt(E(correl[i][4]) - sqr(E(correl[i][3]))));
std::cerr << "Correl. #" << i
<< ": Total " << n << " Coefficient " << r
<< std::endl;
std::cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r << std::endl;
}
}
@ -429,11 +427,11 @@ void prefetch(void*) {}
void prefetch(void* addr) {
# if defined(_MSC_VER)
_mm_prefetch((char*)addr, _MM_HINT_T0);
# else
#if defined(_MSC_VER)
_mm_prefetch((char*) addr, _MM_HINT_T0);
#else
__builtin_prefetch(addr);
# endif
#endif
}
#endif
@ -446,7 +444,7 @@ void prefetch(void* addr) {
void* std_aligned_alloc(size_t alignment, size_t size) {
#if defined(POSIXALIGNEDALLOC)
void *mem;
void* mem;
return posix_memalign(&mem, alignment, size) ? nullptr : mem;
#elif defined(_WIN32) && !defined(_M_ARM) && !defined(_M_ARM64)
return _mm_malloc(size, alignment);
@ -480,8 +478,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize
return nullptr;
#else
HANDLE hProcessToken { };
LUID luid { };
HANDLE hProcessToken{};
LUID luid{};
void* mem = nullptr;
const size_t largePageSize = GetLargePageMinimum();
@ -495,13 +493,13 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize
if (!hAdvapi32)
hAdvapi32 = LoadLibrary(TEXT("advapi32.dll"));
auto fun6 = fun6_t((void(*)())GetProcAddress(hAdvapi32, "OpenProcessToken"));
auto fun6 = fun6_t((void (*)()) GetProcAddress(hAdvapi32, "OpenProcessToken"));
if (!fun6)
return nullptr;
auto fun7 = fun7_t((void(*)())GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"));
auto fun7 = fun7_t((void (*)()) GetProcAddress(hAdvapi32, "LookupPrivilegeValueA"));
if (!fun7)
return nullptr;
auto fun8 = fun8_t((void(*)())GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"));
auto fun8 = fun8_t((void (*)()) GetProcAddress(hAdvapi32, "AdjustTokenPrivileges"));
if (!fun8)
return nullptr;
@ -513,8 +511,8 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize
if (fun7( // LookupPrivilegeValue(nullptr, SE_LOCK_MEMORY_NAME, &luid)
nullptr, "SeLockMemoryPrivilege", &luid))
{
TOKEN_PRIVILEGES tp { };
TOKEN_PRIVILEGES prevTp { };
TOKEN_PRIVILEGES tp{};
TOKEN_PRIVILEGES prevTp{};
DWORD prevTpLen = 0;
tp.PrivilegeCount = 1;
@ -524,13 +522,13 @@ static void* aligned_large_pages_alloc_windows([[maybe_unused]] size_t allocSize
// Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
// we still need to query GetLastError() to ensure that the privileges were actually obtained.
if (fun8( // AdjustTokenPrivileges()
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
GetLastError() == ERROR_SUCCESS)
hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen)
&& GetLastError() == ERROR_SUCCESS)
{
// Round up size to full pages and allocate
allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
mem = VirtualAlloc(
nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
mem = VirtualAlloc(nullptr, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES,
PAGE_READWRITE);
// Privilege no longer needed, restore previous state
fun8( // AdjustTokenPrivileges ()
@ -561,18 +559,18 @@ void* aligned_large_pages_alloc(size_t allocSize) {
void* aligned_large_pages_alloc(size_t allocSize) {
#if defined(__linux__)
#if defined(__linux__)
constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size
#else
#else
constexpr size_t alignment = 4096; // assumed small page size
#endif
#endif
// round up to multiples of alignment
size_t size = ((allocSize + alignment - 1) / alignment) * alignment;
void *mem = std_aligned_alloc(alignment, size);
#if defined(MADV_HUGEPAGE)
void* mem = std_aligned_alloc(alignment, size);
#if defined(MADV_HUGEPAGE)
madvise(mem, size, MADV_HUGEPAGE);
#endif
#endif
return mem;
}
@ -588,8 +586,7 @@ void aligned_large_pages_free(void* mem) {
if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
{
DWORD err = GetLastError();
std::cerr << "Failed to free large page memory. Error code: 0x"
<< std::hex << err
std::cerr << "Failed to free large page memory. Error code: 0x" << std::hex << err
<< std::dec << std::endl;
exit(EXIT_FAILURE);
}
@ -597,9 +594,7 @@ 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) { std_aligned_free(mem); }
#endif
@ -626,7 +621,7 @@ static int best_node(size_t idx) {
// Early exit if the needed API is not available at runtime
HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx");
auto fun1 = (fun1_t) (void (*)()) GetProcAddress(k32, "GetLogicalProcessorInformationEx");
if (!fun1)
return -1;
@ -637,7 +632,7 @@ static int best_node(size_t idx) {
// Once we know returnLength, allocate the buffer
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr;
ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength);
ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) malloc(returnLength);
// Second call to GetLogicalProcessorInformationEx(), now we expect to succeed
if (!fun1(RelationAll, buffer, &returnLength))
@ -659,7 +654,7 @@ static int best_node(size_t idx) {
assert(ptr->Size);
byteOffset += ptr->Size;
ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size);
ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) (((char*) ptr) + ptr->Size);
}
free(buffer);
@ -696,10 +691,10 @@ void bindThisThread(size_t idx) {
// Early exit if the needed API are not available at runtime
HMODULE k32 = GetModuleHandle(TEXT("Kernel32.dll"));
auto fun2 = fun2_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"));
auto fun3 = fun3_t((void(*)())GetProcAddress(k32, "SetThreadGroupAffinity"));
auto fun4 = fun4_t((void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMask2"));
auto fun5 = fun5_t((void(*)())GetProcAddress(k32, "GetMaximumProcessorGroupCount"));
auto fun2 = fun2_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"));
auto fun3 = fun3_t((void (*)()) GetProcAddress(k32, "SetThreadGroupAffinity"));
auto fun4 = fun4_t((void (*)()) GetProcAddress(k32, "GetNumaNodeProcessorMask2"));
auto fun5 = fun5_t((void (*)()) GetProcAddress(k32, "GetMaximumProcessorGroupCount"));
if (!fun2 || !fun3)
return;
@ -716,9 +711,10 @@ void bindThisThread(size_t idx) {
// sized equal and we spread threads evenly across the groups.
USHORT elements, returnedElements;
elements = fun5(); // GetMaximumProcessorGroupCount
GROUP_AFFINITY *affinity = (GROUP_AFFINITY*)malloc(elements * sizeof(GROUP_AFFINITY));
GROUP_AFFINITY* affinity = (GROUP_AFFINITY*) malloc(elements * sizeof(GROUP_AFFINITY));
if (fun4(node, affinity, elements, &returnedElements)) // GetNumaNodeProcessorMask2
fun3(GetCurrentThread(), &affinity[idx % returnedElements], nullptr); // SetThreadGroupAffinity
fun3(GetCurrentThread(), &affinity[idx % returnedElements],
nullptr); // SetThreadGroupAffinity
free(affinity);
}
}
@ -728,11 +724,11 @@ void bindThisThread(size_t idx) {
} // namespace WinProcGroup
#ifdef _WIN32
#include <direct.h>
#define GETCWD _getcwd
#include <direct.h>
#define GETCWD _getcwd
#else
#include <unistd.h>
#define GETCWD getcwd
#include <unistd.h>
#define GETCWD getcwd
#endif
namespace CommandLine {

View file

@ -37,7 +37,8 @@ void prefetch(void* addr);
void start_logger(const std::string& fname);
void* std_aligned_alloc(size_t alignment, size_t size);
void std_aligned_free(void* ptr);
void* aligned_large_pages_alloc(size_t size); // memory aligned by page size, min alignment: 4096 bytes
void* aligned_large_pages_alloc(
size_t size); // memory aligned by page size, min alignment: 4096 bytes
void aligned_large_pages_free(void* mem); // nop if mem == nullptr
void dbg_hit_on(bool cond, int slot = 0);
@ -49,12 +50,16 @@ void dbg_print();
using TimePoint = std::chrono::milliseconds::rep; // A value in milliseconds
static_assert(sizeof(TimePoint) == sizeof(int64_t), "TimePoint should be 64 bits");
inline TimePoint now() {
return std::chrono::duration_cast<std::chrono::milliseconds>
(std::chrono::steady_clock::now().time_since_epoch()).count();
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
}
enum SyncCout { IO_LOCK, IO_UNLOCK };
enum SyncCout {
IO_LOCK,
IO_UNLOCK
};
std::ostream& operator<<(std::ostream&, SyncCout);
#define sync_cout std::cout << IO_LOCK
@ -64,32 +69,35 @@ std::ostream& operator<<(std::ostream&, SyncCout);
// align_ptr_up() : get the first aligned element of an array.
// ptr must point to an array of size at least `sizeof(T) * N + alignment` bytes,
// where N is the number of elements in the array.
template <uintptr_t Alignment, typename T>
T* align_ptr_up(T* ptr)
{
template<uintptr_t Alignment, typename T>
T* align_ptr_up(T* ptr) {
static_assert(alignof(T) < Alignment);
const uintptr_t ptrint = reinterpret_cast<uintptr_t>(reinterpret_cast<char*>(ptr));
return reinterpret_cast<T*>(reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
return reinterpret_cast<T*>(
reinterpret_cast<char*>((ptrint + (Alignment - 1)) / Alignment * Alignment));
}
// IsLittleEndian : true if and only if the binary is compiled on a little-endian machine
static inline const union { uint32_t i; char c[4]; } Le = { 0x01020304 };
static inline const union {
uint32_t i;
char c[4];
} Le = {0x01020304};
static inline const bool IsLittleEndian = (Le.c[0] == 4);
template <typename T, std::size_t MaxSize>
template<typename T, std::size_t MaxSize>
class ValueList {
public:
public:
std::size_t size() const { return size_; }
void push_back(const T& value) { values_[size_++] = value; }
const T* begin() const { return values_; }
const T* end() const { return values_ + size_; }
const T& operator[](int index) const { return values_[index]; }
private:
private:
T values_[MaxSize];
std::size_t size_ = 0;
};
@ -120,15 +128,23 @@ class PRNG {
return s * 2685821657736338717LL;
}
public:
PRNG(uint64_t seed) : s(seed) { assert(seed); }
public:
PRNG(uint64_t seed) :
s(seed) {
assert(seed);
}
template<typename T> T rand() { return T(rand64()); }
template<typename T>
T rand() {
return T(rand64());
}
// Special generator used to fast init magic numbers.
// Output values only have 1/8th of their bits set on average.
template<typename T> T sparse_rand()
{ return T(rand64() & rand64() & rand64()); }
template<typename T>
T sparse_rand() {
return T(rand64() & rand64() & rand64());
}
};
inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
@ -152,14 +168,14 @@ inline uint64_t mul_hi64(uint64_t a, uint64_t b) {
// Peter Österlund.
namespace WinProcGroup {
void bindThisThread(size_t idx);
void bindThisThread(size_t idx);
}
namespace CommandLine {
void init(int argc, char* argv[]);
void init(int argc, char* argv[]);
extern std::string binaryDirectory; // path of the executable directory
extern std::string workingDirectory; // path of the working directory
extern std::string binaryDirectory; // path of the executable directory
extern std::string workingDirectory; // path of the working directory
}
} // namespace Stockfish

View file

@ -28,8 +28,8 @@ namespace Stockfish {
namespace {
template<GenType Type, Direction D, bool Enemy>
ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
template<GenType Type, Direction D, bool Enemy>
ExtMove* make_promotions(ExtMove* moveList, [[maybe_unused]] Square to) {
if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
{
@ -50,11 +50,11 @@ namespace {
}
return moveList;
}
}
template<Color Us, GenType Type>
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
template<Color Us, GenType Type>
ExtMove* generate_pawn_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
constexpr Color Them = ~Us;
constexpr Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB);
@ -64,8 +64,7 @@ namespace {
constexpr Direction UpLeft = (Us == WHITE ? NORTH_WEST : SOUTH_EAST);
const Bitboard emptySquares = ~pos.pieces();
const Bitboard enemies = Type == EVASIONS ? pos.checkers()
: pos.pieces(Them);
const Bitboard enemies = Type == EVASIONS ? pos.checkers() : pos.pieces(Them);
Bitboard pawnsOn7 = pos.pieces(Us, PAWN) & TRank7BB;
Bitboard pawnsNotOn7 = pos.pieces(Us, PAWN) & ~TRank7BB;
@ -89,8 +88,8 @@ namespace {
// Discovered check promotion has been already generated amongst the captures.
Square ksq = pos.square<KING>(Them);
Bitboard dcCandidatePawns = pos.blockers_for_king(Them) & ~file_bb(ksq);
b1 &= pawn_attacks_bb(Them, ksq) | shift< Up>(dcCandidatePawns);
b2 &= pawn_attacks_bb(Them, ksq) | shift<Up+Up>(dcCandidatePawns);
b1 &= pawn_attacks_bb(Them, ksq) | shift<Up>(dcCandidatePawns);
b2 &= pawn_attacks_bb(Them, ksq) | shift<Up + Up>(dcCandidatePawns);
}
while (b1)
@ -110,8 +109,8 @@ namespace {
if (pawnsOn7)
{
Bitboard b1 = shift<UpRight>(pawnsOn7) & enemies;
Bitboard b2 = shift<UpLeft >(pawnsOn7) & enemies;
Bitboard b3 = shift<Up >(pawnsOn7) & emptySquares;
Bitboard b2 = shift<UpLeft>(pawnsOn7) & enemies;
Bitboard b3 = shift<Up>(pawnsOn7) & emptySquares;
if constexpr (Type == EVASIONS)
b3 &= target;
@ -130,7 +129,7 @@ namespace {
if constexpr (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS)
{
Bitboard b1 = shift<UpRight>(pawnsNotOn7) & enemies;
Bitboard b2 = shift<UpLeft >(pawnsNotOn7) & enemies;
Bitboard b2 = shift<UpLeft>(pawnsNotOn7) & enemies;
while (b1)
{
@ -162,11 +161,11 @@ namespace {
}
return moveList;
}
}
template<Color Us, PieceType Pt, bool Checks>
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
template<Color Us, PieceType Pt, bool Checks>
ExtMove* generate_moves(const Position& pos, ExtMove* moveList, Bitboard target) {
static_assert(Pt != KING && Pt != PAWN, "Unsupported piece type in generate_moves()");
@ -186,11 +185,11 @@ namespace {
}
return moveList;
}
}
template<Color Us, GenType Type>
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
template<Color Us, GenType Type>
ExtMove* generate_all(const Position& pos, ExtMove* moveList) {
static_assert(Type != LEGAL, "Unsupported type in generate_all()");
@ -202,9 +201,9 @@ namespace {
if (Type != EVASIONS || !more_than_one(pos.checkers()))
{
target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers()))
: Type == NON_EVASIONS ? ~pos.pieces( Us)
: Type == NON_EVASIONS ? ~pos.pieces(Us)
: Type == CAPTURES ? pos.pieces(~Us)
: ~pos.pieces( ); // QUIETS || QUIET_CHECKS
: ~pos.pieces(); // QUIETS || QUIET_CHECKS
moveList = generate_pawn_moves<Us, Type>(pos, moveList, target);
moveList = generate_moves<Us, KNIGHT, Checks>(pos, moveList, target);
@ -223,13 +222,13 @@ namespace {
*moveList++ = make_move(ksq, pop_lsb(b));
if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING))
for (CastlingRights cr : { Us & KING_SIDE, Us & QUEEN_SIDE } )
for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE})
if (!pos.castling_impeded(cr) && pos.can_castle(cr))
*moveList++ = make<CASTLING>(ksq, pos.castling_rook_square(cr));
}
return moveList;
}
}
} // namespace
@ -273,10 +272,10 @@ ExtMove* generate<LEGAL>(const Position& pos, ExtMove* moveList) {
Square ksq = pos.square<KING>(us);
ExtMove* cur = moveList;
moveList = pos.checkers() ? generate<EVASIONS >(pos, moveList)
: generate<NON_EVASIONS>(pos, moveList);
moveList =
pos.checkers() ? generate<EVASIONS>(pos, moveList) : generate<NON_EVASIONS>(pos, moveList);
while (cur != moveList)
if ( ((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT)
if (((pinned & from_sq(*cur)) || from_sq(*cur) == ksq || type_of(*cur) == EN_PASSANT)
&& !pos.legal(*cur))
*cur = (--moveList)->move;
else

View file

@ -49,9 +49,7 @@ struct ExtMove {
operator float() const = delete;
};
inline bool operator<(const ExtMove& f, const ExtMove& s) {
return f.value < s.value;
}
inline bool operator<(const ExtMove& f, const ExtMove& s) { return f.value < s.value; }
template<GenType>
ExtMove* generate(const Position& pos, ExtMove* moveList);
@ -62,15 +60,14 @@ ExtMove* generate(const Position& pos, ExtMove* moveList);
template<GenType T>
struct MoveList {
explicit MoveList(const Position& pos) : last(generate<T>(pos, moveList)) {}
explicit MoveList(const Position& pos) :
last(generate<T>(pos, moveList)) {}
const ExtMove* begin() const { return moveList; }
const ExtMove* end() const { return last; }
size_t size() const { return last - moveList; }
bool contains(Move move) const {
return std::find(begin(), end(), move) != end();
}
bool contains(Move move) const { return std::find(begin(), end(), move) != end(); }
private:
private:
ExtMove moveList[MAX_MOVES], *last;
};

View file

@ -30,16 +30,37 @@ namespace Stockfish {
namespace {
enum Stages {
MAIN_TT, CAPTURE_INIT, GOOD_CAPTURE, REFUTATION, QUIET_INIT, QUIET, BAD_CAPTURE,
EVASION_TT, EVASION_INIT, EVASION,
PROBCUT_TT, PROBCUT_INIT, PROBCUT,
QSEARCH_TT, QCAPTURE_INIT, QCAPTURE, QCHECK_INIT, QCHECK
};
enum Stages {
// generate main search moves
MAIN_TT,
CAPTURE_INIT,
GOOD_CAPTURE,
REFUTATION,
QUIET_INIT,
QUIET,
BAD_CAPTURE,
// partial_insertion_sort() sorts moves in descending order up to and including
// a given limit. The order of moves smaller than the limit is left unspecified.
void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
// generate evasion moves
EVASION_TT,
EVASION_INIT,
EVASION,
// generate probcut moves
PROBCUT_TT,
PROBCUT_INIT,
PROBCUT,
// generate qsearch moves
QSEARCH_TT,
QCAPTURE_INIT,
QCAPTURE,
QCHECK_INIT,
QCHECK
};
// partial_insertion_sort() sorts moves in descending order up to and including
// a given limit. The order of moves smaller than the limit is left unspecified.
void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) {
for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p)
if (p->value >= limit)
@ -50,7 +71,7 @@ namespace {
*q = *(q - 1);
*q = tmp;
}
}
}
} // namespace
@ -62,44 +83,57 @@ namespace {
// move ordering is at the current node.
// MovePicker constructor for the main search
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
MovePicker::MovePicker(const Position& p,
Move ttm,
Depth d,
const ButterflyHistory* mh,
const CapturePieceToHistory* cph,
const PieceToHistory** ch,
Move cm,
const Move* killers)
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch),
ttMove(ttm), refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}}, depth(d)
{
const Move* killers) :
pos(p),
mainHistory(mh),
captureHistory(cph),
continuationHistory(ch),
ttMove(ttm),
refutations{{killers[0], 0}, {killers[1], 0}, {cm, 0}},
depth(d) {
assert(d > 0);
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) +
!(ttm && pos.pseudo_legal(ttm));
stage = (pos.checkers() ? EVASION_TT : MAIN_TT) + !(ttm && pos.pseudo_legal(ttm));
}
// MovePicker constructor for quiescence search
MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh,
MovePicker::MovePicker(const Position& p,
Move ttm,
Depth d,
const ButterflyHistory* mh,
const CapturePieceToHistory* cph,
const PieceToHistory** ch,
Square rs)
: pos(p), mainHistory(mh), captureHistory(cph), continuationHistory(ch), ttMove(ttm), recaptureSquare(rs), depth(d)
{
Square rs) :
pos(p),
mainHistory(mh),
captureHistory(cph),
continuationHistory(ch),
ttMove(ttm),
recaptureSquare(rs),
depth(d) {
assert(d <= 0);
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) +
!( ttm
&& pos.pseudo_legal(ttm));
stage = (pos.checkers() ? EVASION_TT : QSEARCH_TT) + !(ttm && pos.pseudo_legal(ttm));
}
// MovePicker constructor for ProbCut: we generate captures with SEE greater
// than or equal to the given threshold.
MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph)
: pos(p), captureHistory(cph), ttMove(ttm), threshold(th)
{
MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) :
pos(p),
captureHistory(cph),
ttMove(ttm),
threshold(th) {
assert(!pos.checkers());
stage = PROBCUT_TT + !(ttm && pos.capture_stage(ttm)
&& pos.pseudo_legal(ttm)
&& pos.see_ge(ttm, threshold));
stage = PROBCUT_TT
+ !(ttm && pos.capture_stage(ttm) && pos.pseudo_legal(ttm) && pos.see_ge(ttm, threshold));
}
// MovePicker::score() assigns a numerical value to each move in a list, used
@ -110,13 +144,15 @@ void MovePicker::score() {
static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type");
[[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook, threatenedPieces;
[[maybe_unused]] Bitboard threatenedByPawn, threatenedByMinor, threatenedByRook,
threatenedPieces;
if constexpr (Type == QUIETS)
{
Color us = pos.side_to_move();
threatenedByPawn = pos.attacks_by<PAWN>(~us);
threatenedByMinor = pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
threatenedByMinor =
pos.attacks_by<KNIGHT>(~us) | pos.attacks_by<BISHOP>(~us) | threatenedByPawn;
threatenedByRook = pos.attacks_by<ROOK>(~us) | threatenedByMinor;
// Pieces threatened by pieces of lesser material value
@ -127,8 +163,10 @@ void MovePicker::score() {
for (auto& m : *this)
if constexpr (Type == CAPTURES)
m.value = (7 * int(PieceValue[pos.piece_on(to_sq(m))])
+ (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]) / 16;
m.value =
(7 * int(PieceValue[pos.piece_on(to_sq(m))])
+ (*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))])
/ 16;
else if constexpr (Type == QUIETS)
{
@ -149,30 +187,28 @@ void MovePicker::score() {
m.value += bool(pos.check_squares(pt) & to) * 16384;
// bonus for escaping from capture
m.value += threatenedPieces & from ?
(pt == QUEEN && !(to & threatenedByRook) ? 50000
m.value += threatenedPieces & from ? (pt == QUEEN && !(to & threatenedByRook) ? 50000
: pt == ROOK && !(to & threatenedByMinor) ? 25000
: !(to & threatenedByPawn) ? 15000
: 0 )
: 0 ;
: 0)
: 0;
// malus for putting piece en prise
m.value -= !(threatenedPieces & from) ?
(pt == QUEEN ? bool(to & threatenedByRook) * 50000
m.value -= !(threatenedPieces & from)
? (pt == QUEEN ? bool(to & threatenedByRook) * 50000
+ bool(to & threatenedByMinor) * 10000
+ bool(to & threatenedByPawn) * 20000
: pt == ROOK ? bool(to & threatenedByMinor) * 25000
+ bool(to & threatenedByPawn) * 10000
: pt != PAWN ? bool(to & threatenedByPawn) * 15000
: 0 )
: 0 ;
: 0)
: 0;
}
else // Type == EVASIONS
{
if (pos.capture_stage(m))
m.value = PieceValue[pos.piece_on(to_sq(m))]
- Value(type_of(pos.moved_piece(m)))
m.value = PieceValue[pos.piece_on(to_sq(m))] - Value(type_of(pos.moved_piece(m)))
+ (1 << 28);
else
m.value = (*mainHistory)[pos.side_to_move()][from_to(m)]
@ -204,18 +240,19 @@ Move MovePicker::select(Pred filter) {
Move MovePicker::next_move(bool skipQuiets) {
top:
switch (stage) {
switch (stage)
{
case MAIN_TT:
case EVASION_TT:
case QSEARCH_TT:
case PROBCUT_TT:
case MAIN_TT :
case EVASION_TT :
case QSEARCH_TT :
case PROBCUT_TT :
++stage;
return ttMove;
case CAPTURE_INIT:
case PROBCUT_INIT:
case QCAPTURE_INIT:
case CAPTURE_INIT :
case PROBCUT_INIT :
case QCAPTURE_INIT :
cur = endBadCaptures = moves;
endMoves = generate<CAPTURES>(pos, cur);
@ -224,11 +261,14 @@ top:
++stage;
goto top;
case GOOD_CAPTURE:
if (select<Next>([&](){
return pos.see_ge(*cur, Value(-cur->value)) ?
case GOOD_CAPTURE :
if (select<Next>([&]() {
return pos.see_ge(*cur, Value(-cur->value))
?
// Move losing capture to endBadCaptures to be tried later
true : (*endBadCaptures++ = *cur, false); }))
true
: (*endBadCaptures++ = *cur, false);
}))
return *(cur - 1);
// Prepare the pointers to loop over the refutations array
@ -236,22 +276,22 @@ top:
endMoves = std::end(refutations);
// If the countermove is the same as a killer, skip it
if ( refutations[0].move == refutations[2].move
if (refutations[0].move == refutations[2].move
|| refutations[1].move == refutations[2].move)
--endMoves;
++stage;
[[fallthrough]];
case REFUTATION:
if (select<Next>([&](){ return *cur != MOVE_NONE
&& !pos.capture_stage(*cur)
&& pos.pseudo_legal(*cur); }))
case REFUTATION :
if (select<Next>([&]() {
return *cur != MOVE_NONE && !pos.capture_stage(*cur) && pos.pseudo_legal(*cur);
}))
return *(cur - 1);
++stage;
[[fallthrough]];
case QUIET_INIT:
case QUIET_INIT :
if (!skipQuiets)
{
cur = endBadCaptures;
@ -264,11 +304,11 @@ top:
++stage;
[[fallthrough]];
case QUIET:
if ( !skipQuiets
&& select<Next>([&](){return *cur != refutations[0].move
&& *cur != refutations[1].move
&& *cur != refutations[2].move;}))
case QUIET :
if (!skipQuiets && select<Next>([&]() {
return *cur != refutations[0].move && *cur != refutations[1].move
&& *cur != refutations[2].move;
}))
return *(cur - 1);
// Prepare the pointers to loop over the bad captures
@ -278,10 +318,10 @@ top:
++stage;
[[fallthrough]];
case BAD_CAPTURE:
return select<Next>([](){ return true; });
case BAD_CAPTURE :
return select<Next>([]() { return true; });
case EVASION_INIT:
case EVASION_INIT :
cur = moves;
endMoves = generate<EVASIONS>(pos, cur);
@ -289,15 +329,15 @@ top:
++stage;
[[fallthrough]];
case EVASION:
return select<Best>([](){ return true; });
case EVASION :
return select<Best>([]() { return true; });
case PROBCUT:
return select<Next>([&](){ return pos.see_ge(*cur, threshold); });
case PROBCUT :
return select<Next>([&]() { return pos.see_ge(*cur, threshold); });
case QCAPTURE:
if (select<Next>([&](){ return depth > DEPTH_QS_RECAPTURES
|| to_sq(*cur) == recaptureSquare; }))
case QCAPTURE :
if (select<Next>(
[&]() { return depth > DEPTH_QS_RECAPTURES || to_sq(*cur) == recaptureSquare; }))
return *(cur - 1);
// If we did not find any move and we do not try checks, we have finished
@ -307,15 +347,15 @@ top:
++stage;
[[fallthrough]];
case QCHECK_INIT:
case QCHECK_INIT :
cur = moves;
endMoves = generate<QUIET_CHECKS>(pos, cur);
++stage;
[[fallthrough]];
case QCHECK:
return select<Next>([](){ return true; });
case QCHECK :
return select<Next>([]() { return true; });
}
assert(false);

View file

@ -41,7 +41,7 @@ class StatsEntry {
T entry;
public:
public:
void operator=(const T& v) { entry = v; }
T* operator&() { return &entry; }
T* operator->() { return &entry; }
@ -62,9 +62,8 @@ public:
// template parameter D limits the range of updates in [-D, D] when we update
// values with the << operator, while the last parameters (Size and Sizes)
// encode the dimensions of the array.
template <typename T, int D, int Size, int... Sizes>
struct Stats : public std::array<Stats<T, D, Sizes...>, Size>
{
template<typename T, int D, int Size, int... Sizes>
struct Stats: public std::array<Stats<T, D, Sizes...>, Size> {
using stats = Stats<T, D, Size, Sizes...>;
void fill(const T& v) {
@ -78,12 +77,17 @@ struct Stats : public std::array<Stats<T, D, Sizes...>, Size>
}
};
template <typename T, int D, int Size>
struct Stats<T, D, Size> : public std::array<StatsEntry<T, D>, Size> {};
template<typename T, int D, int Size>
struct Stats<T, D, Size>: public std::array<StatsEntry<T, D>, Size> {};
// In stats table, D=0 means that the template parameter is not used
enum StatsParams { NOT_USED = 0 };
enum StatsType { NoCaptures, Captures };
enum StatsParams {
NOT_USED = 0
};
enum StatsType {
NoCaptures,
Captures
};
// ButterflyHistory records how often quiet moves have been successful or
// unsuccessful during the current search, and is used for reduction and move
@ -117,26 +121,37 @@ using ContinuationHistory = Stats<PieceToHistory, NOT_USED, PIECE_NB, SQUARE_NB>
// likely to get a cut-off first.
class MovePicker {
enum PickType { Next, Best };
enum PickType {
Next,
Best
};
public:
public:
MovePicker(const MovePicker&) = delete;
MovePicker& operator=(const MovePicker&) = delete;
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
MovePicker(const Position&,
Move,
Depth,
const ButterflyHistory*,
const CapturePieceToHistory*,
const PieceToHistory**,
Move,
const Move*);
MovePicker(const Position&, Move, Depth, const ButterflyHistory*,
MovePicker(const Position&,
Move,
Depth,
const ButterflyHistory*,
const CapturePieceToHistory*,
const PieceToHistory**,
Square);
MovePicker(const Position&, Move, Value, const CapturePieceToHistory*);
Move next_move(bool skipQuiets = false);
private:
template<PickType T, typename Pred> Move select(Pred);
template<GenType> void score();
private:
template<PickType T, typename Pred>
Move select(Pred);
template<GenType>
void score();
ExtMove* begin() { return cur; }
ExtMove* end() { return endMoves; }

View file

@ -39,115 +39,123 @@
namespace Stockfish::Eval::NNUE {
// Input feature converter
LargePagePtr<FeatureTransformer> featureTransformer;
// Input feature converter
LargePagePtr<FeatureTransformer> featureTransformer;
// Evaluation function
AlignedPtr<Network> network[LayerStacks];
// Evaluation function
AlignedPtr<Network> network[LayerStacks];
// Evaluation function file name
std::string fileName;
std::string netDescription;
// Evaluation function file name
std::string fileName;
std::string netDescription;
namespace Detail {
namespace Detail {
// Initialize the evaluation function parameters
template <typename T>
void initialize(AlignedPtr<T>& pointer) {
// Initialize the evaluation function parameters
template<typename T>
void initialize(AlignedPtr<T>& pointer) {
pointer.reset(reinterpret_cast<T*>(std_aligned_alloc(alignof(T), sizeof(T))));
std::memset(pointer.get(), 0, sizeof(T));
}
}
template <typename T>
void initialize(LargePagePtr<T>& pointer) {
template<typename T>
void initialize(LargePagePtr<T>& pointer) {
static_assert(alignof(T) <= 4096, "aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
static_assert(alignof(T) <= 4096,
"aligned_large_pages_alloc() may fail for such a big alignment requirement of T");
pointer.reset(reinterpret_cast<T*>(aligned_large_pages_alloc(sizeof(T))));
std::memset(pointer.get(), 0, sizeof(T));
}
}
// Read evaluation function parameters
template <typename T>
bool read_parameters(std::istream& stream, T& reference) {
// Read evaluation function parameters
template<typename T>
bool read_parameters(std::istream& stream, T& reference) {
std::uint32_t header;
header = read_little_endian<std::uint32_t>(stream);
if (!stream || header != T::get_hash_value()) return false;
if (!stream || header != T::get_hash_value())
return false;
return reference.read_parameters(stream);
}
}
// Write evaluation function parameters
template <typename T>
bool write_parameters(std::ostream& stream, const T& reference) {
// Write evaluation function parameters
template<typename T>
bool write_parameters(std::ostream& stream, const T& reference) {
write_little_endian<std::uint32_t>(stream, T::get_hash_value());
return reference.write_parameters(stream);
}
}
} // namespace Detail
} // namespace Detail
// Initialize the evaluation function parameters
static void initialize() {
// Initialize the evaluation function parameters
static void initialize() {
Detail::initialize(featureTransformer);
for (std::size_t i = 0; i < LayerStacks; ++i)
Detail::initialize(network[i]);
}
}
// Read network header
static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc)
{
// Read network header
static bool read_header(std::istream& stream, std::uint32_t* hashValue, std::string* desc) {
std::uint32_t version, size;
version = read_little_endian<std::uint32_t>(stream);
*hashValue = read_little_endian<std::uint32_t>(stream);
size = read_little_endian<std::uint32_t>(stream);
if (!stream || version != Version) return false;
if (!stream || version != Version)
return false;
desc->resize(size);
stream.read(&(*desc)[0], size);
return !stream.fail();
}
}
// Write network header
static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc)
{
// Write network header
static bool write_header(std::ostream& stream, std::uint32_t hashValue, const std::string& desc) {
write_little_endian<std::uint32_t>(stream, Version);
write_little_endian<std::uint32_t>(stream, hashValue);
write_little_endian<std::uint32_t>(stream, (std::uint32_t)desc.size());
write_little_endian<std::uint32_t>(stream, (std::uint32_t) desc.size());
stream.write(&desc[0], desc.size());
return !stream.fail();
}
}
// Read network parameters
static bool read_parameters(std::istream& stream) {
// Read network parameters
static bool read_parameters(std::istream& stream) {
std::uint32_t hashValue;
if (!read_header(stream, &hashValue, &netDescription)) return false;
if (hashValue != HashValue) return false;
if (!Detail::read_parameters(stream, *featureTransformer)) return false;
if (!read_header(stream, &hashValue, &netDescription))
return false;
if (hashValue != HashValue)
return false;
if (!Detail::read_parameters(stream, *featureTransformer))
return false;
for (std::size_t i = 0; i < LayerStacks; ++i)
if (!Detail::read_parameters(stream, *(network[i]))) return false;
if (!Detail::read_parameters(stream, *(network[i])))
return false;
return stream && stream.peek() == std::ios::traits_type::eof();
}
}
// Write network parameters
static bool write_parameters(std::ostream& stream) {
// Write network parameters
static bool write_parameters(std::ostream& stream) {
if (!write_header(stream, HashValue, netDescription)) return false;
if (!Detail::write_parameters(stream, *featureTransformer)) return false;
if (!write_header(stream, HashValue, netDescription))
return false;
if (!Detail::write_parameters(stream, *featureTransformer))
return false;
for (std::size_t i = 0; i < LayerStacks; ++i)
if (!Detail::write_parameters(stream, *(network[i]))) return false;
if (!Detail::write_parameters(stream, *(network[i])))
return false;
return bool(stream);
}
}
void hint_common_parent_position(const Position& pos) {
void hint_common_parent_position(const Position& pos) {
featureTransformer->hint_common_access(pos);
}
}
// Evaluation function. Perform differential calculation.
Value evaluate(const Position& pos, bool adjusted, int* complexity) {
// Evaluation function. Perform differential calculation.
Value evaluate(const Position& pos, bool adjusted, int* complexity) {
// We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly.
@ -156,13 +164,13 @@ namespace Stockfish::Eval::NNUE {
constexpr int delta = 24;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType transformedFeaturesUnaligned[
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
TransformedFeatureType
transformedFeaturesUnaligned[FeatureTransformer::BufferSize
+ alignment / sizeof(TransformedFeatureType)];
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
#else
alignas(alignment)
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
#endif
ASSERT_ALIGNED(transformedFeatures, alignment);
@ -176,122 +184,128 @@ namespace Stockfish::Eval::NNUE {
// Give more value to positional evaluation when adjusted flag is set
if (adjusted)
return static_cast<Value>(((1024 - delta) * psqt + (1024 + delta) * positional) / (1024 * OutputScale));
return static_cast<Value>(((1024 - delta) * psqt + (1024 + delta) * positional)
/ (1024 * OutputScale));
else
return static_cast<Value>((psqt + positional) / OutputScale);
}
}
struct NnueEvalTrace {
struct NnueEvalTrace {
static_assert(LayerStacks == PSQTBuckets);
Value psqt[LayerStacks];
Value positional[LayerStacks];
std::size_t correctBucket;
};
};
static NnueEvalTrace trace_evaluate(const Position& pos) {
static NnueEvalTrace trace_evaluate(const Position& pos) {
// We manually align the arrays on the stack because with gcc < 9.3
// overaligning stack variables with alignas() doesn't work correctly.
constexpr uint64_t alignment = CacheLineSize;
#if defined(ALIGNAS_ON_STACK_VARIABLES_BROKEN)
TransformedFeatureType transformedFeaturesUnaligned[
FeatureTransformer::BufferSize + alignment / sizeof(TransformedFeatureType)];
TransformedFeatureType
transformedFeaturesUnaligned[FeatureTransformer::BufferSize
+ alignment / sizeof(TransformedFeatureType)];
auto* transformedFeatures = align_ptr_up<alignment>(&transformedFeaturesUnaligned[0]);
#else
alignas(alignment)
TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
alignas(alignment) TransformedFeatureType transformedFeatures[FeatureTransformer::BufferSize];
#endif
ASSERT_ALIGNED(transformedFeatures, alignment);
NnueEvalTrace t{};
t.correctBucket = (pos.count<ALL_PIECES>() - 1) / 4;
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket) {
for (IndexType bucket = 0; bucket < LayerStacks; ++bucket)
{
const auto materialist = featureTransformer->transform(pos, transformedFeatures, bucket);
const auto positional = network[bucket]->propagate(transformedFeatures);
t.psqt[bucket] = static_cast<Value>( materialist / OutputScale );
t.positional[bucket] = static_cast<Value>( positional / OutputScale );
t.psqt[bucket] = static_cast<Value>(materialist / OutputScale);
t.positional[bucket] = static_cast<Value>(positional / OutputScale);
}
return t;
}
}
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer.
// The buffer must have capacity for at least 5 chars.
static void format_cp_compact(Value v, char* buffer) {
// format_cp_compact() converts a Value into (centi)pawns and writes it in a buffer.
// The buffer must have capacity for at least 5 chars.
static void format_cp_compact(Value v, char* buffer) {
buffer[0] = (v < 0 ? '-' : v > 0 ? '+' : ' ');
int cp = std::abs(UCI::to_cp(v));
if (cp >= 10000)
{
buffer[1] = '0' + cp / 10000; cp %= 10000;
buffer[2] = '0' + cp / 1000; cp %= 1000;
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[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[1] = '0' + cp / 100;
cp %= 100;
buffer[2] = '.';
buffer[3] = '0' + cp / 10; cp %= 10;
buffer[3] = '0' + cp / 10;
cp %= 10;
buffer[4] = '0' + cp / 1;
}
}
}
// format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals
static void format_cp_aligned_dot(Value v, std::stringstream &stream) {
// format_cp_aligned_dot() converts a Value into pawns, always keeping two decimals
static void format_cp_aligned_dot(Value v, std::stringstream& stream) {
const double pawns = std::abs(0.01 * UCI::to_cp(v));
stream << (v < 0 ? '-' : v > 0 ? '+' : ' ')
<< std::setiosflags(std::ios::fixed)
<< std::setw(6)
<< std::setprecision(2)
<< pawns;
}
stream << (v < 0 ? '-'
: v > 0 ? '+'
: ' ')
<< std::setiosflags(std::ios::fixed) << std::setw(6) << std::setprecision(2) << pawns;
}
// trace() returns a string with the value of each piece on a board,
// and a table for (PSQT, Layers) values bucket by bucket.
std::string trace(Position& pos) {
// trace() returns a string with the value of each piece on a board,
// and a table for (PSQT, Layers) values bucket by bucket.
std::string trace(Position& pos) {
std::stringstream ss;
char board[3*8+1][8*8+2];
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';
for (int row = 0; row < 3 * 8 + 1; ++row)
board[row][8 * 8 + 1] = '\0';
// A lambda to output one box of the board
auto writeSquare = [&board](File file, Rank rank, Piece pc, Value value) {
const int x = int(file) * 8;
const int y = (7 - int(rank)) * 3;
for (int i = 1; i < 8; ++i)
board[y][x+i] = board[y+3][x+i] = '-';
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] = '+';
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];
board[y + 1][x + 4] = PieceToChar[pc];
if (value != VALUE_NONE)
format_cp_compact(value, &board[y+2][x+2]);
format_cp_compact(value, &board[y + 2][x + 2]);
};
// We estimate the value of each piece by doing a differential evaluation from
@ -327,7 +341,7 @@ namespace Stockfish::Eval::NNUE {
}
ss << " NNUE derived piece values:\n";
for (int row = 0; row < 3*8+1; ++row)
for (int row = 0; row < 3 * 8 + 1; ++row)
ss << board[row] << '\n';
ss << '\n';
@ -343,9 +357,15 @@ namespace Stockfish::Eval::NNUE {
for (std::size_t bucket = 0; bucket < LayerStacks; ++bucket)
{
ss << "| " << bucket << " ";
ss << " | "; format_cp_aligned_dot(t.psqt[bucket], ss); ss << " "
<< " | "; format_cp_aligned_dot(t.positional[bucket], ss); ss << " "
<< " | "; format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss); ss << " "
ss << " | ";
format_cp_aligned_dot(t.psqt[bucket], ss);
ss << " "
<< " | ";
format_cp_aligned_dot(t.positional[bucket], ss);
ss << " "
<< " | ";
format_cp_aligned_dot(t.psqt[bucket] + t.positional[bucket], ss);
ss << " "
<< " |";
if (bucket == t.correctBucket)
ss << " <-- this bucket is used";
@ -355,28 +375,28 @@ namespace Stockfish::Eval::NNUE {
ss << "+------------+------------+------------+------------+\n";
return ss.str();
}
}
// Load eval, from a file stream or a memory stream
bool load_eval(std::string name, std::istream& stream) {
// Load eval, from a file stream or a memory stream
bool load_eval(std::string name, std::istream& stream) {
initialize();
fileName = name;
return read_parameters(stream);
}
}
// Save eval, to a file stream or a memory stream
bool save_eval(std::ostream& stream) {
// Save eval, to a file stream or a memory stream
bool save_eval(std::ostream& stream) {
if (fileName.empty())
return false;
return write_parameters(stream);
}
}
// Save eval, to a file given by its name
bool save_eval(const std::optional<std::string>& filename) {
// Save eval, to a file given by its name
bool save_eval(const std::optional<std::string>& filename) {
std::string actualFilename;
std::string msg;
@ -387,7 +407,8 @@ namespace Stockfish::Eval::NNUE {
{
if (currentEvalFileName != EvalFileDefaultName)
{
msg = "Failed to export a net. A non-embedded net can only be saved if the filename is specified";
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;
@ -398,12 +419,11 @@ namespace Stockfish::Eval::NNUE {
std::ofstream stream(actualFilename, std::ios_base::binary);
bool saved = save_eval(stream);
msg = saved ? "Network saved successfully to " + actualFilename
: "Failed to export a net";
msg = saved ? "Network saved successfully to " + actualFilename : "Failed to export a net";
sync_cout << msg << sync_endl;
return saved;
}
}
} // namespace Stockfish::Eval::NNUE

View file

@ -32,47 +32,47 @@
#include "nnue_feature_transformer.h"
namespace Stockfish {
class Position;
enum Value : int;
class Position;
enum Value : int;
}
namespace Stockfish::Eval::NNUE {
// Hash value of evaluation function structure
constexpr std::uint32_t HashValue =
// Hash value of evaluation function structure
constexpr std::uint32_t HashValue =
FeatureTransformer::get_hash_value() ^ Network::get_hash_value();
// Deleter for automating release of memory area
template <typename T>
struct AlignedDeleter {
// Deleter for automating release of memory area
template<typename T>
struct AlignedDeleter {
void operator()(T* ptr) const {
ptr->~T();
std_aligned_free(ptr);
}
};
};
template <typename T>
struct LargePageDeleter {
template<typename T>
struct LargePageDeleter {
void operator()(T* ptr) const {
ptr->~T();
aligned_large_pages_free(ptr);
}
};
};
template <typename T>
using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
template<typename T>
using AlignedPtr = std::unique_ptr<T, AlignedDeleter<T>>;
template <typename T>
using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
template<typename T>
using LargePagePtr = std::unique_ptr<T, LargePageDeleter<T>>;
std::string trace(Position& pos);
Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr);
void hint_common_parent_position(const Position& pos);
std::string trace(Position& pos);
Value evaluate(const Position& pos, bool adjusted = false, int* complexity = nullptr);
void hint_common_parent_position(const Position& pos);
bool load_eval(std::string name, std::istream& stream);
bool save_eval(std::ostream& stream);
bool save_eval(const std::optional<std::string>& filename);
bool load_eval(std::string name, std::istream& stream);
bool save_eval(std::ostream& stream);
bool save_eval(const std::optional<std::string>& filename);
} // namespace Stockfish::Eval::NNUE

View file

@ -27,18 +27,16 @@
namespace Stockfish::Eval::NNUE::Features {
// Index of a feature for a given king position and another piece on some square
template<Color Perspective>
inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) {
return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc] + KingBuckets[Perspective][ksq]);
}
// Index of a feature for a given king position and another piece on some square
template<Color Perspective>
inline IndexType HalfKAv2_hm::make_index(Square s, Piece pc, Square ksq) {
return IndexType((int(s) ^ OrientTBL[Perspective][ksq]) + PieceSquareIndex[Perspective][pc]
+ KingBuckets[Perspective][ksq]);
}
// Get a list of indices for active features
template<Color Perspective>
void HalfKAv2_hm::append_active_indices(
const Position& pos,
IndexList& active
) {
// Get a list of indices for active features
template<Color Perspective>
void HalfKAv2_hm::append_active_indices(const Position& pos, IndexList& active) {
Square ksq = pos.square<KING>(Perspective);
Bitboard bb = pos.pieces();
while (bb)
@ -46,42 +44,43 @@ namespace Stockfish::Eval::NNUE::Features {
Square s = pop_lsb(bb);
active.push_back(make_index<Perspective>(s, pos.piece_on(s), ksq));
}
}
}
// Explicit template instantiations
template void HalfKAv2_hm::append_active_indices<WHITE>(const Position& pos, IndexList& active);
template void HalfKAv2_hm::append_active_indices<BLACK>(const Position& pos, IndexList& active);
// Explicit template instantiations
template void HalfKAv2_hm::append_active_indices<WHITE>(const Position& pos, IndexList& active);
template void HalfKAv2_hm::append_active_indices<BLACK>(const Position& pos, IndexList& active);
// append_changed_indices() : get a list of indices for recently changed features
template<Color Perspective>
void HalfKAv2_hm::append_changed_indices(
Square ksq,
// append_changed_indices() : get a list of indices for recently changed features
template<Color Perspective>
void HalfKAv2_hm::append_changed_indices(Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added
) {
for (int i = 0; i < dp.dirty_num; ++i) {
IndexList& added) {
for (int i = 0; i < dp.dirty_num; ++i)
{
if (dp.from[i] != SQ_NONE)
removed.push_back(make_index<Perspective>(dp.from[i], dp.piece[i], ksq));
if (dp.to[i] != SQ_NONE)
added.push_back(make_index<Perspective>(dp.to[i], dp.piece[i], ksq));
}
}
}
// Explicit template instantiations
template void HalfKAv2_hm::append_changed_indices<WHITE>(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
template void HalfKAv2_hm::append_changed_indices<BLACK>(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
// Explicit template instantiations
template void HalfKAv2_hm::append_changed_indices<WHITE>(Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added);
template void HalfKAv2_hm::append_changed_indices<BLACK>(Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added);
int HalfKAv2_hm::update_cost(const StateInfo* st) {
return st->dirtyPiece.dirty_num;
}
int HalfKAv2_hm::update_cost(const StateInfo* st) { return st->dirtyPiece.dirty_num; }
int HalfKAv2_hm::refresh_cost(const Position& pos) {
return pos.count<ALL_PIECES>();
}
int HalfKAv2_hm::refresh_cost(const Position& pos) { return pos.count<ALL_PIECES>(); }
bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
bool HalfKAv2_hm::requires_refresh(const StateInfo* st, Color perspective) {
return st->dirtyPiece.piece[0] == make_piece(perspective, KING);
}
}
} // namespace Stockfish::Eval::NNUE::Features

View file

@ -28,15 +28,15 @@
#include "../nnue_common.h"
namespace Stockfish {
struct StateInfo;
class Position;
struct StateInfo;
class Position;
}
namespace Stockfish::Eval::NNUE::Features {
// Feature HalfKAv2_hm: Combination of the position of own king
// and the position of pieces. Position mirrored such that king always on e..h files.
class HalfKAv2_hm {
// Feature HalfKAv2_hm: Combination of the position of own king
// and the position of pieces. Position mirrored such that king always on e..h files.
class HalfKAv2_hm {
// unique number for each piece type on each square
enum {
@ -58,11 +58,10 @@ namespace Stockfish::Eval::NNUE::Features {
static constexpr IndexType PieceSquareIndex[COLOR_NB][PIECE_NB] = {
// convention: W - us, B - them
// viewed from other side, W and B are reversed
{ PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE },
{ PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE }
};
{PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE},
{PS_NONE, PS_B_PAWN, PS_B_KNIGHT, PS_B_BISHOP, PS_B_ROOK, PS_B_QUEEN, PS_KING, PS_NONE,
PS_NONE, PS_W_PAWN, PS_W_KNIGHT, PS_W_BISHOP, PS_W_ROOK, PS_W_QUEEN, PS_KING, PS_NONE}};
// Index of a feature for a given king position and another piece on some square
template<Color Perspective>
@ -80,6 +79,7 @@ namespace Stockfish::Eval::NNUE::Features {
static_cast<IndexType>(SQUARE_NB) * static_cast<IndexType>(PS_NB) / 2;
#define B(v) (v * PS_NB)
// clang-format off
static constexpr int KingBuckets[COLOR_NB][SQUARE_NB] = {
{ B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28),
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
@ -98,8 +98,9 @@ namespace Stockfish::Eval::NNUE::Features {
B(24), B(25), B(26), B(27), B(27), B(26), B(25), B(24),
B(28), B(29), B(30), B(31), B(31), B(30), B(29), B(28) }
};
// clang-format on
#undef B
// clang-format off
// Orient a square according to perspective (rotates by 180 for black)
static constexpr int OrientTBL[COLOR_NB][SQUARE_NB] = {
{ SQ_H1, SQ_H1, SQ_H1, SQ_H1, SQ_A1, SQ_A1, SQ_A1, SQ_A1,
@ -119,6 +120,7 @@ namespace Stockfish::Eval::NNUE::Features {
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8,
SQ_H8, SQ_H8, SQ_H8, SQ_H8, SQ_A8, SQ_A8, SQ_A8, SQ_A8 }
};
// clang-format on
// Maximum number of simultaneously active features.
static constexpr IndexType MaxActiveDimensions = 32;
@ -126,18 +128,12 @@ namespace Stockfish::Eval::NNUE::Features {
// Get a list of indices for active features
template<Color Perspective>
static void append_active_indices(
const Position& pos,
IndexList& active);
static void append_active_indices(const Position& pos, IndexList& active);
// Get a list of indices for recently changed features
template<Color Perspective>
static void append_changed_indices(
Square ksq,
const DirtyPiece& dp,
IndexList& removed,
IndexList& added
);
static void
append_changed_indices(Square ksq, const DirtyPiece& dp, IndexList& removed, IndexList& added);
// Returns the cost of updating one perspective, the most costly one.
// Assumes no refresh needed.
@ -147,7 +143,7 @@ namespace Stockfish::Eval::NNUE::Features {
// Returns whether the change stored in this StateInfo means that
// a full accumulator refresh is required.
static bool requires_refresh(const StateInfo* st, Color perspective);
};
};
} // namespace Stockfish::Eval::NNUE::Features

View file

@ -42,33 +42,37 @@ namespace Stockfish::Eval::NNUE::Layers {
// Fallback implementation for older/other architectures.
// Requires the input to be padded to at least 16 values.
#if !defined(USE_SSSE3)
template <IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
static void affine_transform_non_ssse3(std::int32_t* output, const std::int8_t* weights, const std::int32_t* biases, const std::uint8_t* input)
{
# if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON)
# if defined(USE_SSE2)
template<IndexType InputDimensions, IndexType PaddedInputDimensions, IndexType OutputDimensions>
static void affine_transform_non_ssse3(std::int32_t* output,
const std::int8_t* weights,
const std::int32_t* biases,
const std::uint8_t* input) {
#if defined(USE_SSE2) || defined(USE_NEON_DOTPROD) || defined(USE_NEON)
#if defined(USE_SSE2)
// At least a multiple of 16, with SSE2.
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
const __m128i Zeros = _mm_setzero_si128();
const auto inputVector = reinterpret_cast<const __m128i*>(input);
# elif defined(USE_NEON_DOTPROD)
#elif defined(USE_NEON_DOTPROD)
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
const auto inputVector = reinterpret_cast<const int8x16_t*>(input);
# elif defined(USE_NEON)
#elif defined(USE_NEON)
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 16) / 16;
const auto inputVector = reinterpret_cast<const int8x8_t*>(input);
# endif
#endif
for (IndexType i = 0; i < OutputDimensions; ++i) {
for (IndexType i = 0; i < OutputDimensions; ++i)
{
const IndexType offset = i * PaddedInputDimensions;
# if defined(USE_SSE2)
#if defined(USE_SSE2)
__m128i sumLo = _mm_cvtsi32_si128(biases[i]);
__m128i sumHi = Zeros;
const auto row = reinterpret_cast<const __m128i*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j) {
for (IndexType j = 0; j < NumChunks; ++j)
{
__m128i row_j = _mm_load_si128(&row[j]);
__m128i input_j = _mm_load_si128(&inputVector[j]);
__m128i extendedRowLo = _mm_srai_epi16(_mm_unpacklo_epi8(row_j, row_j), 8);
@ -87,43 +91,46 @@ namespace Stockfish::Eval::NNUE::Layers {
sum = _mm_add_epi32(sum, sum_second_32);
output[i] = _mm_cvtsi128_si32(sum);
# elif defined(USE_NEON_DOTPROD)
#elif defined(USE_NEON_DOTPROD)
int32x4_t sum = {biases[i]};
const auto row = reinterpret_cast<const int8x16_t*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j) {
for (IndexType j = 0; j < NumChunks; ++j)
{
sum = vdotq_s32(sum, inputVector[j], row[j]);
}
output[i] = vaddvq_s32(sum);
# elif defined(USE_NEON)
#elif defined(USE_NEON)
int32x4_t sum = {biases[i]};
const auto row = reinterpret_cast<const int8x8_t*>(&weights[offset]);
for (IndexType j = 0; j < NumChunks; ++j) {
for (IndexType j = 0; j < NumChunks; ++j)
{
int16x8_t product = vmull_s8(inputVector[j * 2], row[j * 2]);
product = vmlal_s8(product, inputVector[j * 2 + 1], row[j * 2 + 1]);
sum = vpadalq_s16(sum, product);
}
output[i] = sum[0] + sum[1] + sum[2] + sum[3];
# endif
#endif
}
# else
#else
std::memcpy(output, biases, sizeof(std::int32_t) * OutputDimensions);
// Traverse weights in transpose order to take advantage of input sparsity
for (IndexType i = 0; i < InputDimensions; ++i)
if (input[i]) {
if (input[i])
{
const std::int8_t* w = &weights[i];
const int in = input[i];
for (IndexType j = 0; j < OutputDimensions; ++j)
output[j] += w[j * PaddedInputDimensions] * in;
}
# endif
}
#endif
}
#endif
template <IndexType InDims, IndexType OutDims>
class AffineTransform {
template<IndexType InDims, IndexType OutDims>
class AffineTransform {
public:
// Input/output type
using InputType = std::uint8_t;
@ -149,17 +156,13 @@ namespace Stockfish::Eval::NNUE::Layers {
return hashValue;
}
static constexpr IndexType get_weight_index_scrambled(IndexType i)
{
return
(i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4 +
i / PaddedInputDimensions * 4 +
i % 4;
static constexpr IndexType get_weight_index_scrambled(IndexType i) {
return (i / 4) % (PaddedInputDimensions / 4) * OutputDimensions * 4
+ i / PaddedInputDimensions * 4 + i % 4;
}
static constexpr IndexType get_weight_index(IndexType i)
{
#if defined (USE_SSSE3)
static constexpr IndexType get_weight_index(IndexType i) {
#if defined(USE_SSSE3)
return get_weight_index_scrambled(i);
#else
return i;
@ -185,36 +188,35 @@ namespace Stockfish::Eval::NNUE::Layers {
return !stream.fail();
}
// Forward propagation
void propagate(
const InputType* input, OutputType* output) const {
void propagate(const InputType* input, OutputType* output) const {
#if defined (USE_SSSE3)
#if defined(USE_SSSE3)
if constexpr (OutputDimensions > 1)
{
#if defined (USE_AVX512)
#if defined(USE_AVX512)
using vec_t = __m512i;
#define vec_setzero _mm512_setzero_si512
#define vec_set_32 _mm512_set1_epi32
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m512_add_dpbusd_epi32x2
#define vec_hadd Simd::m512_hadd
#elif defined (USE_AVX2)
#elif defined(USE_AVX2)
using vec_t = __m256i;
#define vec_setzero _mm256_setzero_si256
#define vec_set_32 _mm256_set1_epi32
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
#define vec_hadd Simd::m256_hadd
#elif defined (USE_SSSE3)
#elif defined(USE_SSSE3)
using vec_t = __m128i;
#define vec_setzero _mm_setzero_si128
#define vec_set_32 _mm_set1_epi32
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
#define vec_hadd Simd::m128_hadd
#endif
#endif
static constexpr IndexType OutputSimdWidth = sizeof(vec_t) / sizeof(OutputType);
@ -233,8 +235,10 @@ namespace Stockfish::Eval::NNUE::Layers {
{
const vec_t in0 = vec_set_32(input32[i + 0]);
const vec_t in1 = vec_set_32(input32[i + 1]);
const auto col0 = reinterpret_cast<const vec_t*>(&weights[(i + 0) * OutputDimensions * 4]);
const auto col1 = reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
const auto col0 =
reinterpret_cast<const vec_t*>(&weights[(i + 0) * OutputDimensions * 4]);
const auto col1 =
reinterpret_cast<const vec_t*>(&weights[(i + 1) * OutputDimensions * 4]);
for (IndexType k = 0; k < NumRegs; ++k)
vec_add_dpbusd_32x2(acc[k], in0, col0[k], in1, col1[k]);
}
@ -243,32 +247,31 @@ namespace Stockfish::Eval::NNUE::Layers {
for (IndexType k = 0; k < NumRegs; ++k)
outptr[k] = acc[k];
# undef vec_setzero
# undef vec_set_32
# undef vec_add_dpbusd_32
# undef vec_add_dpbusd_32x2
# undef vec_hadd
#undef vec_setzero
#undef vec_set_32
#undef vec_add_dpbusd_32
#undef vec_add_dpbusd_32x2
#undef vec_hadd
}
else if constexpr (OutputDimensions == 1)
{
// We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements.
#if defined (USE_AVX2)
// We cannot use AVX512 for the last layer because there's only 32 inputs and the buffer is not padded to 64 elements.
#if defined(USE_AVX2)
using vec_t = __m256i;
#define vec_setzero _mm256_setzero_si256
#define vec_set_32 _mm256_set1_epi32
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m256_add_dpbusd_epi32x2
#define vec_hadd Simd::m256_hadd
#elif defined (USE_SSSE3)
#elif defined(USE_SSSE3)
using vec_t = __m128i;
#define vec_setzero _mm_setzero_si128
#define vec_set_32 _mm_set1_epi32
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
#define vec_add_dpbusd_32x2 Simd::m128_add_dpbusd_epi32x2
#define vec_hadd Simd::m128_hadd
#endif
#endif
const auto inputVector = reinterpret_cast<const vec_t*>(input);
@ -287,19 +290,16 @@ namespace Stockfish::Eval::NNUE::Layers {
}
output[0] = vec_hadd(sum0, biases[0]);
# undef vec_setzero
# undef vec_set_32
# undef vec_add_dpbusd_32
# undef vec_add_dpbusd_32x2
# undef vec_hadd
#undef vec_setzero
#undef vec_set_32
#undef vec_add_dpbusd_32
#undef vec_add_dpbusd_32x2
#undef vec_hadd
}
#else
// Use old implementation for the other architectures.
affine_transform_non_ssse3<
InputDimensions,
PaddedInputDimensions,
OutputDimensions>(output, weights, biases, input);
affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
output, weights, biases, input);
#endif
}
@ -309,7 +309,7 @@ namespace Stockfish::Eval::NNUE::Layers {
alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
};
};
} // namespace Stockfish::Eval::NNUE::Layers

View file

@ -38,34 +38,38 @@
namespace Stockfish::Eval::NNUE::Layers {
#if (USE_SSSE3 | (USE_NEON >= 8))
alignas(CacheLineSize) static inline const std::array<std::array<std::uint16_t, 8>, 256> lookup_indices = [](){
alignas(CacheLineSize) static inline const
std::array<std::array<std::uint16_t, 8>, 256> lookup_indices = []() {
std::array<std::array<std::uint16_t, 8>, 256> v{};
for (unsigned i = 0; i < 256; ++i)
{
std::uint64_t j = i, k = 0;
while(j)
while (j)
v[i][k++] = pop_lsb(j);
}
return v;
}();
// Find indices of nonzero numbers in an int32_t array
template<const IndexType InputDimensions>
void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) {
#if defined (USE_SSSE3)
#if defined (USE_AVX512)
// Find indices of nonzero numbers in an int32_t array
template<const IndexType InputDimensions>
void find_nnz(const std::int32_t* input, std::uint16_t* out, IndexType& count_out) {
#if defined(USE_SSSE3)
#if defined(USE_AVX512)
using vec_t = __m512i;
#define vec_nnz(a) _mm512_cmpgt_epi32_mask(a, _mm512_setzero_si512())
#elif defined (USE_AVX2)
#elif defined(USE_AVX2)
using vec_t = __m256i;
#if defined(USE_VNNI) && !defined(USE_AVXVNNI)
#define vec_nnz(a) _mm256_cmpgt_epi32_mask(a, _mm256_setzero_si256())
#else
#define vec_nnz(a) _mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
#define vec_nnz(a) \
_mm256_movemask_ps( \
_mm256_castsi256_ps(_mm256_cmpgt_epi32(a, _mm256_setzero_si256())))
#endif
#elif defined (USE_SSSE3)
#elif defined(USE_SSSE3)
using vec_t = __m128i;
#define vec_nnz(a) _mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128())))
#define vec_nnz(a) \
_mm_movemask_ps(_mm_castsi128_ps(_mm_cmpgt_epi32(a, _mm_setzero_si128())))
#endif
using vec128_t = __m128i;
#define vec128_zero _mm_setzero_si128()
@ -73,7 +77,7 @@ namespace Stockfish::Eval::NNUE::Layers {
#define vec128_load(a) _mm_load_si128(a)
#define vec128_storeu(a, b) _mm_storeu_si128(a, b)
#define vec128_add(a, b) _mm_add_epi16(a, b)
#elif defined (USE_NEON)
#elif defined(USE_NEON)
using vec_t = uint32x4_t;
static const std::uint32_t Mask[4] = {1, 2, 4, 8};
#define vec_nnz(a) vaddvq_u32(vandq_u32(vtstq_u32(a, a), vld1q_u32(Mask)))
@ -83,7 +87,7 @@ namespace Stockfish::Eval::NNUE::Layers {
#define vec128_load(a) vld1q_u16(reinterpret_cast<const std::uint16_t*>(a))
#define vec128_storeu(a, b) vst1q_u16(reinterpret_cast<std::uint16_t*>(a), b)
#define vec128_add(a, b) vaddq_u16(a, b)
#endif
#endif
constexpr IndexType InputSimdWidth = sizeof(vec_t) / sizeof(std::int32_t);
// Inputs are processed InputSimdWidth at a time and outputs are processed 8 at a time so we process in chunks of max(InputSimdWidth, 8)
constexpr IndexType ChunkSize = std::max<IndexType>(InputSimdWidth, 8);
@ -107,25 +111,26 @@ namespace Stockfish::Eval::NNUE::Layers {
for (IndexType j = 0; j < OutputsPerChunk; ++j)
{
const auto lookup = (nnz >> (j * 8)) & 0xFF;
const auto offsets = vec128_load(reinterpret_cast<const vec128_t*>(&lookup_indices[lookup]));
const auto offsets =
vec128_load(reinterpret_cast<const vec128_t*>(&lookup_indices[lookup]));
vec128_storeu(reinterpret_cast<vec128_t*>(out + count), vec128_add(base, offsets));
count += popcount(lookup);
base = vec128_add(base, increment);
}
}
count_out = count;
}
# undef vec_nnz
# undef vec128_zero
# undef vec128_set_16
# undef vec128_load
# undef vec128_storeu
# undef vec128_add
}
#undef vec_nnz
#undef vec128_zero
#undef vec128_set_16
#undef vec128_load
#undef vec128_storeu
#undef vec128_add
#endif
// Sparse input implementation
template <IndexType InDims, IndexType OutDims>
class AffineTransformSparseInput {
// Sparse input implementation
template<IndexType InDims, IndexType OutDims>
class AffineTransformSparseInput {
public:
// Input/output type
using InputType = std::uint8_t;
@ -135,7 +140,8 @@ namespace Stockfish::Eval::NNUE::Layers {
static constexpr IndexType InputDimensions = InDims;
static constexpr IndexType OutputDimensions = OutDims;
static_assert(OutputDimensions % 16 == 0, "Only implemented for OutputDimensions divisible by 16.");
static_assert(OutputDimensions % 16 == 0,
"Only implemented for OutputDimensions divisible by 16.");
static constexpr IndexType PaddedInputDimensions =
ceil_to_multiple<IndexType>(InputDimensions, MaxSimdWidth);
@ -159,16 +165,12 @@ namespace Stockfish::Eval::NNUE::Layers {
return hashValue;
}
static constexpr IndexType get_weight_index_scrambled(IndexType i)
{
return
(i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize +
i / PaddedInputDimensions * ChunkSize +
i % ChunkSize;
static constexpr IndexType get_weight_index_scrambled(IndexType i) {
return (i / ChunkSize) % (PaddedInputDimensions / ChunkSize) * OutputDimensions * ChunkSize
+ i / PaddedInputDimensions * ChunkSize + i % ChunkSize;
}
static constexpr IndexType get_weight_index(IndexType i)
{
static constexpr IndexType get_weight_index(IndexType i) {
#if (USE_SSSE3 | (USE_NEON >= 8))
return get_weight_index_scrambled(i);
#else
@ -195,36 +197,35 @@ namespace Stockfish::Eval::NNUE::Layers {
return !stream.fail();
}
// Forward propagation
void propagate(
const InputType* input, OutputType* output) const {
void propagate(const InputType* input, OutputType* output) const {
#if (USE_SSSE3 | (USE_NEON >= 8))
#if defined (USE_AVX512)
#if defined(USE_AVX512)
using invec_t = __m512i;
using outvec_t = __m512i;
#define vec_set_32 _mm512_set1_epi32
#define vec_add_dpbusd_32 Simd::m512_add_dpbusd_epi32
#elif defined (USE_AVX2)
#elif defined(USE_AVX2)
using invec_t = __m256i;
using outvec_t = __m256i;
#define vec_set_32 _mm256_set1_epi32
#define vec_add_dpbusd_32 Simd::m256_add_dpbusd_epi32
#elif defined (USE_SSSE3)
#elif defined(USE_SSSE3)
using invec_t = __m128i;
using outvec_t = __m128i;
#define vec_set_32 _mm_set1_epi32
#define vec_add_dpbusd_32 Simd::m128_add_dpbusd_epi32
#elif defined (USE_NEON_DOTPROD)
#elif defined(USE_NEON_DOTPROD)
using invec_t = int8x16_t;
using outvec_t = int32x4_t;
#define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
#define vec_add_dpbusd_32 Simd::dotprod_m128_add_dpbusd_epi32
#elif defined (USE_NEON)
#elif defined(USE_NEON)
using invec_t = int8x16_t;
using outvec_t = int32x4_t;
#define vec_set_32(a) vreinterpretq_s8_u32(vdupq_n_u32(a))
#define vec_add_dpbusd_32 Simd::neon_m128_add_dpbusd_epi32
#endif
#endif
static constexpr IndexType OutputSimdWidth = sizeof(outvec_t) / sizeof(OutputType);
constexpr IndexType NumChunks = ceil_to_multiple<IndexType>(InputDimensions, 8) / ChunkSize;
@ -246,7 +247,8 @@ namespace Stockfish::Eval::NNUE::Layers {
{
const auto i = nnz[j];
const invec_t in = vec_set_32(input32[i]);
const auto col = reinterpret_cast<const invec_t*>(&weights[i * OutputDimensions * ChunkSize]);
const auto col =
reinterpret_cast<const invec_t*>(&weights[i * OutputDimensions * ChunkSize]);
for (IndexType k = 0; k < NumRegs; ++k)
vec_add_dpbusd_32(acc[k], in, col[k]);
}
@ -254,14 +256,12 @@ namespace Stockfish::Eval::NNUE::Layers {
outvec_t* outptr = reinterpret_cast<outvec_t*>(output);
for (IndexType k = 0; k < NumRegs; ++k)
outptr[k] = acc[k];
# undef vec_set_32
# undef vec_add_dpbusd_32
#undef vec_set_32
#undef vec_add_dpbusd_32
#else
// Use dense implementation for the other architectures.
affine_transform_non_ssse3<
InputDimensions,
PaddedInputDimensions,
OutputDimensions>(output, weights, biases, input);
affine_transform_non_ssse3<InputDimensions, PaddedInputDimensions, OutputDimensions>(
output, weights, biases, input);
#endif
}
@ -271,7 +271,7 @@ namespace Stockfish::Eval::NNUE::Layers {
alignas(CacheLineSize) BiasType biases[OutputDimensions];
alignas(CacheLineSize) WeightType weights[OutputDimensions * PaddedInputDimensions];
};
};
} // namespace Stockfish::Eval::NNUE::Layers

View file

@ -29,9 +29,9 @@
namespace Stockfish::Eval::NNUE::Layers {
// Clipped ReLU
template <IndexType InDims>
class ClippedReLU {
// Clipped ReLU
template<IndexType InDims>
class ClippedReLU {
public:
// Input/output type
using InputType = std::int32_t;
@ -53,58 +53,60 @@ namespace Stockfish::Eval::NNUE::Layers {
}
// Read network parameters
bool read_parameters(std::istream&) {
return true;
}
bool read_parameters(std::istream&) { return true; }
// Write network parameters
bool write_parameters(std::ostream&) const {
return true;
}
bool write_parameters(std::ostream&) const { return true; }
// Forward propagation
void propagate(
const InputType* input, OutputType* output) const {
void propagate(const InputType* input, OutputType* output) const {
#if defined(USE_AVX2)
if constexpr (InputDimensions % SimdWidth == 0) {
#if defined(USE_AVX2)
if constexpr (InputDimensions % SimdWidth == 0)
{
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
const __m256i Zero = _mm256_setzero_si256();
const __m256i Offsets = _mm256_set_epi32(7, 3, 6, 2, 5, 1, 4, 0);
const auto in = reinterpret_cast<const __m256i*>(input);
const auto out = reinterpret_cast<__m256i*>(output);
for (IndexType i = 0; i < NumChunks; ++i) {
const __m256i words0 = _mm256_srai_epi16(_mm256_packs_epi32(
_mm256_load_si256(&in[i * 4 + 0]),
_mm256_load_si256(&in[i * 4 + 1])), WeightScaleBits);
const __m256i words1 = _mm256_srai_epi16(_mm256_packs_epi32(
_mm256_load_si256(&in[i * 4 + 2]),
_mm256_load_si256(&in[i * 4 + 3])), WeightScaleBits);
_mm256_store_si256(&out[i], _mm256_permutevar8x32_epi32(_mm256_max_epi8(
_mm256_packs_epi16(words0, words1), Zero), Offsets));
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m256i words0 =
_mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 0]),
_mm256_load_si256(&in[i * 4 + 1])),
WeightScaleBits);
const __m256i words1 =
_mm256_srai_epi16(_mm256_packs_epi32(_mm256_load_si256(&in[i * 4 + 2]),
_mm256_load_si256(&in[i * 4 + 3])),
WeightScaleBits);
_mm256_store_si256(
&out[i], _mm256_permutevar8x32_epi32(
_mm256_max_epi8(_mm256_packs_epi16(words0, words1), Zero), Offsets));
}
} else {
}
else
{
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
const __m128i Zero = _mm_setzero_si128();
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < NumChunks; ++i) {
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 0]),
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 2]),
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m128i words0 = _mm_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
WeightScaleBits);
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i], _mm_max_epi8(packedbytes, Zero));
}
}
constexpr IndexType Start =
InputDimensions % SimdWidth == 0
constexpr IndexType Start = InputDimensions % SimdWidth == 0
? InputDimensions / SimdWidth * SimdWidth
: InputDimensions / (SimdWidth / 2) * (SimdWidth / 2);
#elif defined(USE_SSE2)
#elif defined(USE_SSE2)
constexpr IndexType NumChunks = InputDimensions / SimdWidth;
#ifdef USE_SSE41
@ -115,13 +117,14 @@ namespace Stockfish::Eval::NNUE::Layers {
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < NumChunks; ++i) {
const __m128i words0 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 0]),
_mm_load_si128(&in[i * 4 + 1])), WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(_mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 2]),
_mm_load_si128(&in[i * 4 + 3])), WeightScaleBits);
for (IndexType i = 0; i < NumChunks; ++i)
{
const __m128i words0 = _mm_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1])),
WeightScaleBits);
const __m128i words1 = _mm_srai_epi16(
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3])),
WeightScaleBits);
const __m128i packedbytes = _mm_packs_epi16(words0, words1);
_mm_store_si128(&out[i],
@ -135,12 +138,13 @@ namespace Stockfish::Eval::NNUE::Layers {
}
constexpr IndexType Start = NumChunks * SimdWidth;
#elif defined(USE_NEON)
#elif defined(USE_NEON)
constexpr IndexType NumChunks = InputDimensions / (SimdWidth / 2);
const int8x8_t Zero = {0};
const auto in = reinterpret_cast<const int32x4_t*>(input);
const auto out = reinterpret_cast<int8x8_t*>(output);
for (IndexType i = 0; i < NumChunks; ++i) {
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);
@ -148,16 +152,16 @@ namespace Stockfish::Eval::NNUE::Layers {
out[i] = vmax_s8(vqmovn_s16(shifted), Zero);
}
constexpr IndexType Start = NumChunks * (SimdWidth / 2);
#else
#else
constexpr IndexType Start = 0;
#endif
#endif
for (IndexType i = Start; i < InputDimensions; ++i) {
output[i] = static_cast<OutputType>(
std::clamp(input[i] >> WeightScaleBits, 0, 127));
for (IndexType i = Start; i < InputDimensions; ++i)
{
output[i] = static_cast<OutputType>(std::clamp(input[i] >> WeightScaleBits, 0, 127));
}
}
};
};
} // namespace Stockfish::Eval::NNUE::Layers

View file

@ -20,30 +20,30 @@
#define STOCKFISH_SIMD_H_INCLUDED
#if defined(USE_AVX2)
# include <immintrin.h>
#include <immintrin.h>
#elif defined(USE_SSE41)
# include <smmintrin.h>
#include <smmintrin.h>
#elif defined(USE_SSSE3)
# include <tmmintrin.h>
#include <tmmintrin.h>
#elif defined(USE_SSE2)
# include <emmintrin.h>
#include <emmintrin.h>
#elif defined(USE_NEON)
# include <arm_neon.h>
#include <arm_neon.h>
#endif
namespace Stockfish::Simd {
#if defined (USE_AVX512)
#if defined(USE_AVX512)
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
[[maybe_unused]] static int m512_hadd(__m512i sum, int bias) {
return _mm512_reduce_add_epi32(sum) + bias;
}
}
/*
/*
Parameters:
sum0 = [zmm0.i128[0], zmm0.i128[1], zmm0.i128[2], zmm0.i128[3]]
sum1 = [zmm1.i128[0], zmm1.i128[1], zmm1.i128[2], zmm1.i128[3]]
@ -58,8 +58,8 @@ namespace Stockfish::Simd {
reduce_add_epi32(zmm0.i128[3]), reduce_add_epi32(zmm1.i128[3]), reduce_add_epi32(zmm2.i128[3]), reduce_add_epi32(zmm3.i128[3])
]
*/
[[maybe_unused]] static __m512i m512_hadd128x16_interleave(
__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
[[maybe_unused]] static __m512i
m512_hadd128x16_interleave(__m512i sum0, __m512i sum1, __m512i sum2, __m512i sum3) {
__m512i sum01a = _mm512_unpacklo_epi32(sum0, sum1);
__m512i sum01b = _mm512_unpackhi_epi32(sum0, sum1);
@ -74,169 +74,147 @@ namespace Stockfish::Simd {
__m512i sum0123b = _mm512_unpackhi_epi64(sum01, sum23);
return _mm512_add_epi32(sum0123a, sum0123b);
}
}
[[maybe_unused]] static void m512_add_dpbusd_epi32(
__m512i& acc,
__m512i a,
__m512i b) {
[[maybe_unused]] static void m512_add_dpbusd_epi32(__m512i& acc, __m512i a, __m512i b) {
# if defined (USE_VNNI)
#if defined(USE_VNNI)
acc = _mm512_dpbusd_epi32(acc, a, b);
# else
#else
__m512i product0 = _mm512_maddubs_epi16(a, b);
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
acc = _mm512_add_epi32(acc, product0);
# endif
}
#endif
}
[[maybe_unused]] static void m512_add_dpbusd_epi32x2(
__m512i& acc,
__m512i a0, __m512i b0,
__m512i a1, __m512i b1) {
[[maybe_unused]] static void
m512_add_dpbusd_epi32x2(__m512i& acc, __m512i a0, __m512i b0, __m512i a1, __m512i b1) {
# if defined (USE_VNNI)
#if defined(USE_VNNI)
acc = _mm512_dpbusd_epi32(acc, a0, b0);
acc = _mm512_dpbusd_epi32(acc, a1, b1);
# else
#else
__m512i product0 = _mm512_maddubs_epi16(a0, b0);
__m512i product1 = _mm512_maddubs_epi16(a1, b1);
product0 = _mm512_madd_epi16(product0, _mm512_set1_epi16(1));
product1 = _mm512_madd_epi16(product1, _mm512_set1_epi16(1));
acc = _mm512_add_epi32(acc, _mm512_add_epi32(product0, product1));
# endif
}
#endif
}
#endif
#if defined (USE_AVX2)
#if defined(USE_AVX2)
[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
[[maybe_unused]] static int m256_hadd(__m256i sum, int bias) {
__m128i sum128 = _mm_add_epi32(_mm256_castsi256_si128(sum), _mm256_extracti128_si256(sum, 1));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_BADC));
sum128 = _mm_add_epi32(sum128, _mm_shuffle_epi32(sum128, _MM_PERM_CDAB));
return _mm_cvtsi128_si32(sum128) + bias;
}
}
[[maybe_unused]] static void m256_add_dpbusd_epi32(
__m256i& acc,
__m256i a,
__m256i b) {
[[maybe_unused]] static void m256_add_dpbusd_epi32(__m256i& acc, __m256i a, __m256i b) {
# if defined (USE_VNNI)
#if defined(USE_VNNI)
acc = _mm256_dpbusd_epi32(acc, a, b);
# else
#else
__m256i product0 = _mm256_maddubs_epi16(a, b);
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
acc = _mm256_add_epi32(acc, product0);
# endif
}
#endif
}
[[maybe_unused]] static void m256_add_dpbusd_epi32x2(
__m256i& acc,
__m256i a0, __m256i b0,
__m256i a1, __m256i b1) {
[[maybe_unused]] static void
m256_add_dpbusd_epi32x2(__m256i& acc, __m256i a0, __m256i b0, __m256i a1, __m256i b1) {
# if defined (USE_VNNI)
#if defined(USE_VNNI)
acc = _mm256_dpbusd_epi32(acc, a0, b0);
acc = _mm256_dpbusd_epi32(acc, a1, b1);
# else
#else
__m256i product0 = _mm256_maddubs_epi16(a0, b0);
__m256i product1 = _mm256_maddubs_epi16(a1, b1);
product0 = _mm256_madd_epi16(product0, _mm256_set1_epi16(1));
product1 = _mm256_madd_epi16(product1, _mm256_set1_epi16(1));
acc = _mm256_add_epi32(acc, _mm256_add_epi32(product0, product1));
# endif
}
#endif
}
#endif
#if defined (USE_SSSE3)
#if defined(USE_SSSE3)
[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
[[maybe_unused]] static int m128_hadd(__m128i sum, int bias) {
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0x4E)); //_MM_PERM_BADC
sum = _mm_add_epi32(sum, _mm_shuffle_epi32(sum, 0xB1)); //_MM_PERM_CDAB
return _mm_cvtsi128_si32(sum) + bias;
}
}
[[maybe_unused]] static void m128_add_dpbusd_epi32(
__m128i& acc,
__m128i a,
__m128i b) {
[[maybe_unused]] static void m128_add_dpbusd_epi32(__m128i& acc, __m128i a, __m128i b) {
__m128i product0 = _mm_maddubs_epi16(a, b);
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
acc = _mm_add_epi32(acc, product0);
}
}
[[maybe_unused]] static void m128_add_dpbusd_epi32x2(
__m128i& acc,
__m128i a0, __m128i b0,
__m128i a1, __m128i b1) {
[[maybe_unused]] static void
m128_add_dpbusd_epi32x2(__m128i& acc, __m128i a0, __m128i b0, __m128i a1, __m128i b1) {
__m128i product0 = _mm_maddubs_epi16(a0, b0);
__m128i product1 = _mm_maddubs_epi16(a1, b1);
product0 = _mm_madd_epi16(product0, _mm_set1_epi16(1));
product1 = _mm_madd_epi16(product1, _mm_set1_epi16(1));
acc = _mm_add_epi32(acc, _mm_add_epi32(product0, product1));
}
}
#endif
#if defined (USE_NEON_DOTPROD)
#if defined(USE_NEON_DOTPROD)
[[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2(
int32x4_t& acc,
int8x16_t a0, int8x16_t b0,
int8x16_t a1, int8x16_t b1) {
[[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32x2(
int32x4_t& acc, int8x16_t a0, int8x16_t b0, int8x16_t a1, int8x16_t b1) {
acc = vdotq_s32(acc, a0, b0);
acc = vdotq_s32(acc, a1, b1);
}
}
[[maybe_unused]] static void dotprod_m128_add_dpbusd_epi32(
int32x4_t& acc,
int8x16_t a, int8x16_t b) {
[[maybe_unused]] static void
dotprod_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
acc = vdotq_s32(acc, a, b);
}
}
#endif
#if defined (USE_NEON)
#if defined(USE_NEON)
[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
# if USE_NEON >= 8
[[maybe_unused]] static int neon_m128_reduce_add_epi32(int32x4_t s) {
#if USE_NEON >= 8
return vaddvq_s32(s);
# else
#else
return s[0] + s[1] + s[2] + s[3];
# endif
}
#endif
}
[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {
[[maybe_unused]] static int neon_m128_hadd(int32x4_t sum, int bias) {
return neon_m128_reduce_add_epi32(sum) + bias;
}
}
[[maybe_unused]] static void neon_m128_add_dpbusd_epi32x2(
int32x4_t& acc,
int8x8_t a0, int8x8_t b0,
int8x8_t a1, int8x8_t b1) {
[[maybe_unused]] static void
neon_m128_add_dpbusd_epi32x2(int32x4_t& acc, int8x8_t a0, int8x8_t b0, int8x8_t a1, int8x8_t b1) {
int16x8_t product = vmull_s8(a0, b0);
product = vmlal_s8(product, a1, b1);
acc = vpadalq_s16(acc, product);
}
}
#endif
#if USE_NEON >= 8
[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(
int32x4_t& acc,
int8x16_t a, int8x16_t b) {
[[maybe_unused]] static void neon_m128_add_dpbusd_epi32(int32x4_t& acc, int8x16_t a, int8x16_t b) {
int16x8_t product0 = vmull_s8(vget_low_s8(a), vget_low_s8(b));
int16x8_t product1 = vmull_high_s8(a, b);
int16x8_t sum = vpaddq_s16(product0, product1);
acc = vpadalq_s16(acc, sum);
}
}
#endif
}

View file

@ -29,9 +29,9 @@
namespace Stockfish::Eval::NNUE::Layers {
// Clipped ReLU
template <IndexType InDims>
class SqrClippedReLU {
// Clipped ReLU
template<IndexType InDims>
class SqrClippedReLU {
public:
// Input/output type
using InputType = std::int32_t;
@ -53,32 +53,26 @@ namespace Stockfish::Eval::NNUE::Layers {
}
// Read network parameters
bool read_parameters(std::istream&) {
return true;
}
bool read_parameters(std::istream&) { return true; }
// Write network parameters
bool write_parameters(std::ostream&) const {
return true;
}
bool write_parameters(std::ostream&) const { return true; }
// Forward propagation
void propagate(
const InputType* input, OutputType* output) const {
void propagate(const InputType* input, OutputType* output) const {
#if defined(USE_SSE2)
#if defined(USE_SSE2)
constexpr IndexType NumChunks = InputDimensions / 16;
static_assert(WeightScaleBits == 6);
const auto in = reinterpret_cast<const __m128i*>(input);
const auto out = reinterpret_cast<__m128i*>(output);
for (IndexType i = 0; i < NumChunks; ++i) {
__m128i words0 = _mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 0]),
_mm_load_si128(&in[i * 4 + 1]));
__m128i words1 = _mm_packs_epi32(
_mm_load_si128(&in[i * 4 + 2]),
_mm_load_si128(&in[i * 4 + 3]));
for (IndexType i = 0; i < NumChunks; ++i)
{
__m128i words0 =
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 0]), _mm_load_si128(&in[i * 4 + 1]));
__m128i words1 =
_mm_packs_epi32(_mm_load_si128(&in[i * 4 + 2]), _mm_load_si128(&in[i * 4 + 3]));
// We shift by WeightScaleBits * 2 = 12 and divide by 128
// which is an additional shift-right of 7, meaning 19 in total.
@ -90,18 +84,19 @@ namespace Stockfish::Eval::NNUE::Layers {
}
constexpr IndexType Start = NumChunks * 16;
#else
#else
constexpr IndexType Start = 0;
#endif
#endif
for (IndexType i = Start; i < InputDimensions; ++i) {
for (IndexType i = Start; i < InputDimensions; ++i)
{
output[i] = static_cast<OutputType>(
// 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 * WeightScaleBits + 7)));
}
}
};
};
} // namespace Stockfish::Eval::NNUE::Layers

View file

@ -28,12 +28,12 @@
namespace Stockfish::Eval::NNUE {
// Class that holds the result of affine transformation of input features
struct alignas(CacheLineSize) Accumulator {
// Class that holds the result of affine transformation of input features
struct alignas(CacheLineSize) Accumulator {
std::int16_t accumulation[2][TransformedFeatureDimensions];
std::int32_t psqtAccumulation[2][PSQTBuckets];
bool computed[2];
};
};
} // namespace Stockfish::Eval::NNUE

View file

@ -42,8 +42,7 @@ constexpr IndexType TransformedFeatureDimensions = 2560;
constexpr IndexType PSQTBuckets = 8;
constexpr IndexType LayerStacks = 8;
struct Network
{
struct Network {
static constexpr int FC_0_OUTPUTS = 15;
static constexpr int FC_1_OUTPUTS = 32;
@ -71,37 +70,29 @@ struct Network
// Read network parameters
bool read_parameters(std::istream& stream) {
return fc_0.read_parameters(stream)
&& ac_0.read_parameters(stream)
&& fc_1.read_parameters(stream)
&& ac_1.read_parameters(stream)
return fc_0.read_parameters(stream) && ac_0.read_parameters(stream)
&& fc_1.read_parameters(stream) && ac_1.read_parameters(stream)
&& fc_2.read_parameters(stream);
}
// Write network parameters
bool write_parameters(std::ostream& stream) const {
return fc_0.write_parameters(stream)
&& ac_0.write_parameters(stream)
&& fc_1.write_parameters(stream)
&& ac_1.write_parameters(stream)
return fc_0.write_parameters(stream) && ac_0.write_parameters(stream)
&& fc_1.write_parameters(stream) && ac_1.write_parameters(stream)
&& fc_2.write_parameters(stream);
}
std::int32_t propagate(const TransformedFeatureType* transformedFeatures)
{
struct alignas(CacheLineSize) Buffer
{
std::int32_t propagate(const TransformedFeatureType* transformedFeatures) {
struct alignas(CacheLineSize) Buffer {
alignas(CacheLineSize) decltype(fc_0)::OutputBuffer fc_0_out;
alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType ac_sqr_0_out[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2, 32)];
alignas(CacheLineSize) decltype(ac_sqr_0)::OutputType
ac_sqr_0_out[ceil_to_multiple<IndexType>(FC_0_OUTPUTS * 2, 32)];
alignas(CacheLineSize) decltype(ac_0)::OutputBuffer ac_0_out;
alignas(CacheLineSize) decltype(fc_1)::OutputBuffer fc_1_out;
alignas(CacheLineSize) decltype(ac_1)::OutputBuffer ac_1_out;
alignas(CacheLineSize) decltype(fc_2)::OutputBuffer fc_2_out;
Buffer()
{
std::memset(this, 0, sizeof(*this));
}
Buffer() { std::memset(this, 0, sizeof(*this)); }
};
#if defined(__clang__) && (__APPLE__)
@ -116,14 +107,16 @@ struct Network
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(decltype(ac_0)::OutputType));
std::memcpy(buffer.ac_sqr_0_out + FC_0_OUTPUTS, buffer.ac_0_out,
FC_0_OUTPUTS * sizeof(decltype(ac_0)::OutputType));
fc_1.propagate(buffer.ac_sqr_0_out, buffer.fc_1_out);
ac_1.propagate(buffer.fc_1_out, buffer.ac_1_out);
fc_2.propagate(buffer.ac_1_out, buffer.fc_2_out);
// buffer.fc_0_out[FC_0_OUTPUTS] is such that 1.0 is equal to 127*(1<<WeightScaleBits) in quantized form
// but we want 1.0 to be equal to 600*OutputScale
std::int32_t fwdOut = int(buffer.fc_0_out[FC_0_OUTPUTS]) * (600*OutputScale) / (127*(1<<WeightScaleBits));
std::int32_t fwdOut =
int(buffer.fc_0_out[FC_0_OUTPUTS]) * (600 * OutputScale) / (127 * (1 << WeightScaleBits));
std::int32_t outputValue = buffer.fc_2_out[0] + fwdOut;
return outputValue;

View file

@ -31,65 +31,65 @@
#include "../misc.h"
#if defined(USE_AVX2)
#include <immintrin.h>
#include <immintrin.h>
#elif defined(USE_SSE41)
#include <smmintrin.h>
#include <smmintrin.h>
#elif defined(USE_SSSE3)
#include <tmmintrin.h>
#include <tmmintrin.h>
#elif defined(USE_SSE2)
#include <emmintrin.h>
#include <emmintrin.h>
#elif defined(USE_NEON)
#include <arm_neon.h>
#include <arm_neon.h>
#endif
namespace Stockfish::Eval::NNUE {
// Version of the evaluation file
constexpr std::uint32_t Version = 0x7AF32F20u;
// Version of the evaluation file
constexpr std::uint32_t Version = 0x7AF32F20u;
// Constant used in evaluation value calculation
constexpr int OutputScale = 16;
constexpr int WeightScaleBits = 6;
// Constant used in evaluation value calculation
constexpr int OutputScale = 16;
constexpr int WeightScaleBits = 6;
// Size of cache line (in bytes)
constexpr std::size_t CacheLineSize = 64;
// Size of cache line (in bytes)
constexpr std::size_t CacheLineSize = 64;
constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128";
constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;
constexpr const char Leb128MagicString[] = "COMPRESSED_LEB128";
constexpr const std::size_t Leb128MagicStringSize = sizeof(Leb128MagicString) - 1;
// SIMD width (in bytes)
#if defined(USE_AVX2)
constexpr std::size_t SimdWidth = 32;
// SIMD width (in bytes)
#if defined(USE_AVX2)
constexpr std::size_t SimdWidth = 32;
#elif defined(USE_SSE2)
constexpr std::size_t SimdWidth = 16;
#elif defined(USE_SSE2)
constexpr std::size_t SimdWidth = 16;
#elif defined(USE_NEON)
constexpr std::size_t SimdWidth = 16;
#endif
#elif defined(USE_NEON)
constexpr std::size_t SimdWidth = 16;
#endif
constexpr std::size_t MaxSimdWidth = 32;
constexpr std::size_t MaxSimdWidth = 32;
// Type of input feature after conversion
using TransformedFeatureType = std::uint8_t;
using IndexType = std::uint32_t;
// Type of input feature after conversion
using TransformedFeatureType = std::uint8_t;
using IndexType = std::uint32_t;
// Round n up to be a multiple of base
template <typename IntType>
constexpr IntType ceil_to_multiple(IntType n, IntType base) {
// Round n up to be a multiple of base
template<typename IntType>
constexpr IntType ceil_to_multiple(IntType n, IntType base) {
return (n + base - 1) / base * base;
}
}
// read_little_endian() is our utility to read an integer (signed or unsigned, any size)
// from a stream in little-endian order. We swap the byte order after the read if
// necessary to return a result with the byte ordering of the compiling machine.
template <typename IntType>
inline IntType read_little_endian(std::istream& stream) {
// read_little_endian() is our utility to read an integer (signed or unsigned, any size)
// from a stream in little-endian order. We swap the byte order after the read if
// necessary to return a result with the byte ordering of the compiling machine.
template<typename IntType>
inline IntType read_little_endian(std::istream& stream) {
IntType result;
if (IsLittleEndian)
@ -107,15 +107,15 @@ namespace Stockfish::Eval::NNUE {
}
return result;
}
}
// write_little_endian() is our utility to write an integer (signed or unsigned, any size)
// to a stream in little-endian order. We swap the byte order before the write if
// necessary to always write in little endian order, independently of the byte
// ordering of the compiling machine.
template <typename IntType>
inline void write_little_endian(std::ostream& stream, IntType value) {
// write_little_endian() is our utility to write an integer (signed or unsigned, any size)
// to a stream in little-endian order. We swap the byte order before the write if
// necessary to always write in little endian order, independently of the byte
// ordering of the compiling machine.
template<typename IntType>
inline void write_little_endian(std::ostream& stream, IntType value) {
if (IsLittleEndian)
stream.write(reinterpret_cast<const char*>(&value), sizeof(IntType));
@ -130,46 +130,46 @@ namespace Stockfish::Eval::NNUE {
{
for (; i + 1 < sizeof(IntType); ++i)
{
u[i] = (std::uint8_t)v;
u[i] = (std::uint8_t) v;
v >>= 8;
}
}
u[i] = (std::uint8_t)v;
u[i] = (std::uint8_t) v;
stream.write(reinterpret_cast<char*>(u), sizeof(IntType));
}
}
}
// read_little_endian(s, out, N) : read integers in bulk from a little indian stream.
// This reads N integers from stream s and put them in array out.
template <typename IntType>
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
// read_little_endian(s, out, N) : read integers in bulk from a little indian stream.
// This reads N integers from stream s and put them in array out.
template<typename IntType>
inline void read_little_endian(std::istream& stream, IntType* out, std::size_t count) {
if (IsLittleEndian)
stream.read(reinterpret_cast<char*>(out), sizeof(IntType) * count);
else
for (std::size_t i = 0; i < count; ++i)
out[i] = read_little_endian<IntType>(stream);
}
}
// write_little_endian(s, values, N) : write integers in bulk to a little indian stream.
// This takes N integers from array values and writes them on stream s.
template <typename IntType>
inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
// write_little_endian(s, values, N) : write integers in bulk to a little indian stream.
// This takes N integers from array values and writes them on stream s.
template<typename IntType>
inline void write_little_endian(std::ostream& stream, const IntType* values, std::size_t count) {
if (IsLittleEndian)
stream.write(reinterpret_cast<const char*>(values), sizeof(IntType) * count);
else
for (std::size_t i = 0; i < count; ++i)
write_little_endian<IntType>(stream, values[i]);
}
}
// read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in
// the array out. The stream is assumed to be compressed using the signed LEB128 format.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template <typename IntType>
inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) {
// read_leb_128(s, out, N) : read N signed integers from the stream s, putting them in
// the array out. The stream is assumed to be compressed using the signed LEB128 format.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template<typename IntType>
inline void read_leb_128(std::istream& stream, IntType* out, std::size_t count) {
// Check the presence of our LEB128 magic string
char leb128MagicString[Leb128MagicStringSize];
@ -203,24 +203,24 @@ namespace Stockfish::Eval::NNUE {
if ((byte & 0x80) == 0)
{
out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0) ? result
out[i] = (sizeof(IntType) * 8 <= shift || (byte & 0x40) == 0)
? result
: result | ~((1 << shift) - 1);
break;
}
}
while (shift < sizeof(IntType) * 8);
} while (shift < sizeof(IntType) * 8);
}
assert(bytes_left == 0);
}
}
// write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression.
// This takes N integers from array values, compress them with the LEB128 algorithm and
// writes the result on the stream s.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template <typename IntType>
inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) {
// write_leb_128(s, values, N) : write signed integers to a stream with LEB128 compression.
// This takes N integers from array values, compress them with the LEB128 algorithm and
// writes the result on the stream s.
// See https://en.wikipedia.org/wiki/LEB128 for a description of the compression scheme.
template<typename IntType>
inline void write_leb_128(std::ostream& stream, const IntType* values, std::size_t count) {
// Write our LEB128 magic string
stream.write(Leb128MagicString, Leb128MagicStringSize);
@ -237,8 +237,7 @@ namespace Stockfish::Eval::NNUE {
byte = value & 0x7f;
value >>= 7;
++byte_count;
}
while ((byte & 0x40) == 0 ? value != 0 : value != -1);
} while ((byte & 0x40) == 0 ? value != 0 : value != -1);
}
write_little_endian(stream, byte_count);
@ -278,7 +277,7 @@ namespace Stockfish::Eval::NNUE {
}
flush();
}
}
} // namespace Stockfish::Eval::NNUE

View file

@ -36,120 +36,122 @@
namespace Stockfish::Eval::NNUE {
using BiasType = std::int16_t;
using WeightType = std::int16_t;
using PSQTWeightType = std::int32_t;
using BiasType = std::int16_t;
using WeightType = std::int16_t;
using PSQTWeightType = std::int32_t;
// If vector instructions are enabled, we update and refresh the
// accumulator tile by tile such that each tile fits in the CPU's
// vector registers.
#define VECTOR
// If vector instructions are enabled, we update and refresh the
// accumulator tile by tile such that each tile fits in the CPU's
// vector registers.
#define VECTOR
static_assert(PSQTBuckets % 8 == 0,
static_assert(PSQTBuckets % 8 == 0,
"Per feature PSQT values cannot be processed at granularity lower than 8 at a time.");
#ifdef USE_AVX512
using vec_t = __m512i;
using psqt_vec_t = __m256i;
#ifdef USE_AVX512
using vec_t = __m512i;
using psqt_vec_t = __m256i;
#define vec_load(a) _mm512_load_si512(a)
#define vec_store(a,b) _mm512_store_si512(a,b)
#define vec_add_16(a,b) _mm512_add_epi16(a,b)
#define vec_sub_16(a,b) _mm512_sub_epi16(a,b)
#define vec_mul_16(a,b) _mm512_mullo_epi16(a,b)
#define vec_store(a, b) _mm512_store_si512(a, b)
#define vec_add_16(a, b) _mm512_add_epi16(a, b)
#define vec_sub_16(a, b) _mm512_sub_epi16(a, b)
#define vec_mul_16(a, b) _mm512_mullo_epi16(a, b)
#define vec_zero() _mm512_setzero_epi32()
#define vec_set_16(a) _mm512_set1_epi16(a)
#define vec_max_16(a,b) _mm512_max_epi16(a,b)
#define vec_min_16(a,b) _mm512_min_epi16(a,b)
inline vec_t vec_msb_pack_16(vec_t a, vec_t b){
vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a,7),_mm512_srli_epi16(b,7));
#define vec_max_16(a, b) _mm512_max_epi16(a, b)
#define vec_min_16(a, b) _mm512_min_epi16(a, b)
inline vec_t vec_msb_pack_16(vec_t a, vec_t b) {
vec_t compacted = _mm512_packs_epi16(_mm512_srli_epi16(a, 7), _mm512_srli_epi16(b, 7));
return _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), compacted);
}
}
#define vec_load_psqt(a) _mm256_load_si256(a)
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
#define vec_store_psqt(a, b) _mm256_store_si256(a, b)
#define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
#define vec_zero_psqt() _mm256_setzero_si256()
#define NumRegistersSIMD 16
#define MaxChunkSize 64
#elif USE_AVX2
using vec_t = __m256i;
using psqt_vec_t = __m256i;
#elif USE_AVX2
using vec_t = __m256i;
using psqt_vec_t = __m256i;
#define vec_load(a) _mm256_load_si256(a)
#define vec_store(a,b) _mm256_store_si256(a,b)
#define vec_add_16(a,b) _mm256_add_epi16(a,b)
#define vec_sub_16(a,b) _mm256_sub_epi16(a,b)
#define vec_mul_16(a,b) _mm256_mullo_epi16(a,b)
#define vec_store(a, b) _mm256_store_si256(a, b)
#define vec_add_16(a, b) _mm256_add_epi16(a, b)
#define vec_sub_16(a, b) _mm256_sub_epi16(a, b)
#define vec_mul_16(a, b) _mm256_mullo_epi16(a, b)
#define vec_zero() _mm256_setzero_si256()
#define vec_set_16(a) _mm256_set1_epi16(a)
#define vec_max_16(a,b) _mm256_max_epi16(a,b)
#define vec_min_16(a,b) _mm256_min_epi16(a,b)
inline vec_t vec_msb_pack_16(vec_t a, vec_t b){
vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a,7), _mm256_srli_epi16(b,7));
#define vec_max_16(a, b) _mm256_max_epi16(a, b)
#define vec_min_16(a, b) _mm256_min_epi16(a, b)
inline vec_t vec_msb_pack_16(vec_t a, vec_t b) {
vec_t compacted = _mm256_packs_epi16(_mm256_srli_epi16(a, 7), _mm256_srli_epi16(b, 7));
return _mm256_permute4x64_epi64(compacted, 0b11011000);
}
}
#define vec_load_psqt(a) _mm256_load_si256(a)
#define vec_store_psqt(a,b) _mm256_store_si256(a,b)
#define vec_add_psqt_32(a,b) _mm256_add_epi32(a,b)
#define vec_sub_psqt_32(a,b) _mm256_sub_epi32(a,b)
#define vec_store_psqt(a, b) _mm256_store_si256(a, b)
#define vec_add_psqt_32(a, b) _mm256_add_epi32(a, b)
#define vec_sub_psqt_32(a, b) _mm256_sub_epi32(a, b)
#define vec_zero_psqt() _mm256_setzero_si256()
#define NumRegistersSIMD 16
#define MaxChunkSize 32
#elif USE_SSE2
using vec_t = __m128i;
using psqt_vec_t = __m128i;
#elif USE_SSE2
using vec_t = __m128i;
using psqt_vec_t = __m128i;
#define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) _mm_add_epi16(a,b)
#define vec_sub_16(a,b) _mm_sub_epi16(a,b)
#define vec_mul_16(a,b) _mm_mullo_epi16(a,b)
#define vec_store(a, b) *(a) = (b)
#define vec_add_16(a, b) _mm_add_epi16(a, b)
#define vec_sub_16(a, b) _mm_sub_epi16(a, b)
#define vec_mul_16(a, b) _mm_mullo_epi16(a, b)
#define vec_zero() _mm_setzero_si128()
#define vec_set_16(a) _mm_set1_epi16(a)
#define vec_max_16(a,b) _mm_max_epi16(a,b)
#define vec_min_16(a,b) _mm_min_epi16(a,b)
#define vec_msb_pack_16(a,b) _mm_packs_epi16(_mm_srli_epi16(a,7),_mm_srli_epi16(b,7))
#define vec_max_16(a, b) _mm_max_epi16(a, b)
#define vec_min_16(a, b) _mm_min_epi16(a, b)
#define vec_msb_pack_16(a, b) _mm_packs_epi16(_mm_srli_epi16(a, 7), _mm_srli_epi16(b, 7))
#define vec_load_psqt(a) (*(a))
#define vec_store_psqt(a,b) *(a)=(b)
#define vec_add_psqt_32(a,b) _mm_add_epi32(a,b)
#define vec_sub_psqt_32(a,b) _mm_sub_epi32(a,b)
#define vec_store_psqt(a, b) *(a) = (b)
#define vec_add_psqt_32(a, b) _mm_add_epi32(a, b)
#define vec_sub_psqt_32(a, b) _mm_sub_epi32(a, b)
#define vec_zero_psqt() _mm_setzero_si128()
#define NumRegistersSIMD (Is64Bit ? 16 : 8)
#define MaxChunkSize 16
#elif USE_NEON
using vec_t = int16x8_t;
using psqt_vec_t = int32x4_t;
#elif USE_NEON
using vec_t = int16x8_t;
using psqt_vec_t = int32x4_t;
#define vec_load(a) (*(a))
#define vec_store(a,b) *(a)=(b)
#define vec_add_16(a,b) vaddq_s16(a,b)
#define vec_sub_16(a,b) vsubq_s16(a,b)
#define vec_mul_16(a,b) vmulq_s16(a,b)
#define vec_zero() vec_t{0}
#define vec_store(a, b) *(a) = (b)
#define vec_add_16(a, b) vaddq_s16(a, b)
#define vec_sub_16(a, b) vsubq_s16(a, b)
#define vec_mul_16(a, b) vmulq_s16(a, b)
#define vec_zero() \
vec_t { 0 }
#define vec_set_16(a) vdupq_n_s16(a)
#define vec_max_16(a,b) vmaxq_s16(a,b)
#define vec_min_16(a,b) vminq_s16(a,b)
inline vec_t vec_msb_pack_16(vec_t a, vec_t b){
#define vec_max_16(a, b) vmaxq_s16(a, b)
#define vec_min_16(a, b) vminq_s16(a, b)
inline vec_t vec_msb_pack_16(vec_t a, vec_t b) {
const int8x8_t shifta = vshrn_n_s16(a, 7);
const int8x8_t shiftb = vshrn_n_s16(b, 7);
const int8x16_t compacted = vcombine_s8(shifta,shiftb);
return *reinterpret_cast<const vec_t*> (&compacted);
}
const int8x16_t compacted = vcombine_s8(shifta, shiftb);
return *reinterpret_cast<const vec_t*>(&compacted);
}
#define vec_load_psqt(a) (*(a))
#define vec_store_psqt(a,b) *(a)=(b)
#define vec_add_psqt_32(a,b) vaddq_s32(a,b)
#define vec_sub_psqt_32(a,b) vsubq_s32(a,b)
#define vec_zero_psqt() psqt_vec_t{0}
#define vec_store_psqt(a, b) *(a) = (b)
#define vec_add_psqt_32(a, b) vaddq_s32(a, b)
#define vec_sub_psqt_32(a, b) vsubq_s32(a, b)
#define vec_zero_psqt() \
psqt_vec_t { 0 }
#define NumRegistersSIMD 16
#define MaxChunkSize 16
#else
#else
#undef VECTOR
#endif
#endif
#ifdef VECTOR
#ifdef VECTOR
// Compute optimal SIMD register count for feature transformer accumulation.
@ -161,12 +163,8 @@ namespace Stockfish::Eval::NNUE {
#pragma GCC diagnostic ignored "-Wignored-attributes"
#endif
template <typename SIMDRegisterType,
typename LaneType,
int NumLanes,
int MaxRegisters>
static constexpr int BestRegisterCount()
{
template<typename SIMDRegisterType, typename LaneType, int NumLanes, int MaxRegisters>
static constexpr int BestRegisterCount() {
#define RegisterSize sizeof(SIMDRegisterType)
#define LaneSize sizeof(LaneType)
@ -187,30 +185,31 @@ namespace Stockfish::Eval::NNUE {
return divisor;
return 1;
}
}
static constexpr int NumRegs = BestRegisterCount<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
static constexpr int NumPsqtRegs = BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
static constexpr int NumRegs =
BestRegisterCount<vec_t, WeightType, TransformedFeatureDimensions, NumRegistersSIMD>();
static constexpr int NumPsqtRegs =
BestRegisterCount<psqt_vec_t, PSQTWeightType, PSQTBuckets, NumRegistersSIMD>();
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
#endif
#endif
// Input feature converter
class FeatureTransformer {
// Input feature converter
class FeatureTransformer {
private:
// Number of output dimensions for one side
static constexpr IndexType HalfDimensions = TransformedFeatureDimensions;
#ifdef VECTOR
#ifdef VECTOR
static constexpr IndexType TileHeight = NumRegs * sizeof(vec_t) / 2;
static constexpr IndexType PsqtTileHeight = NumPsqtRegs * sizeof(psqt_vec_t) / 4;
static_assert(HalfDimensions % TileHeight == 0, "TileHeight must divide HalfDimensions");
static_assert(PSQTBuckets % PsqtTileHeight == 0, "PsqtTileHeight must divide PSQTBuckets");
#endif
#endif
public:
// Output type
@ -221,8 +220,7 @@ namespace Stockfish::Eval::NNUE {
static constexpr IndexType OutputDimensions = HalfDimensions;
// Size of forward propagation buffer
static constexpr std::size_t BufferSize =
OutputDimensions * sizeof(OutputType);
static constexpr std::size_t BufferSize = OutputDimensions * sizeof(OutputType);
// Hash value embedded in the evaluation file
static constexpr std::uint32_t get_hash_value() {
@ -232,8 +230,8 @@ namespace Stockfish::Eval::NNUE {
// Read network parameters
bool read_parameters(std::istream& stream) {
read_leb_128<BiasType >(stream, biases , HalfDimensions );
read_leb_128<WeightType >(stream, weights , HalfDimensions * InputDimensions);
read_leb_128<BiasType>(stream, biases, HalfDimensions);
read_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
read_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
return !stream.fail();
@ -242,8 +240,8 @@ namespace Stockfish::Eval::NNUE {
// Write network parameters
bool write_parameters(std::ostream& stream) const {
write_leb_128<BiasType >(stream, biases , HalfDimensions );
write_leb_128<WeightType >(stream, weights , HalfDimensions * InputDimensions);
write_leb_128<BiasType>(stream, biases, HalfDimensions);
write_leb_128<WeightType>(stream, weights, HalfDimensions * InputDimensions);
write_leb_128<PSQTWeightType>(stream, psqtWeights, PSQTBuckets * InputDimensions);
return !stream.fail();
@ -258,10 +256,9 @@ namespace Stockfish::Eval::NNUE {
const auto& accumulation = pos.state()->accumulator.accumulation;
const auto& psqtAccumulation = pos.state()->accumulator.psqtAccumulation;
const auto psqt = (
psqtAccumulation[perspectives[0]][bucket]
- psqtAccumulation[perspectives[1]][bucket]
) / 2;
const auto psqt =
(psqtAccumulation[perspectives[0]][bucket] - psqtAccumulation[perspectives[1]][bucket])
/ 2;
for (IndexType p = 0; p < 2; ++p)
@ -278,8 +275,9 @@ namespace Stockfish::Eval::NNUE {
vec_t One = vec_set_16(127);
const vec_t* in0 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][0]));
const vec_t* in1 = reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][HalfDimensions / 2]));
vec_t* out = reinterpret_cast< vec_t*>(output + offset);
const vec_t* in1 =
reinterpret_cast<const vec_t*>(&(accumulation[perspectives[p]][HalfDimensions / 2]));
vec_t* out = reinterpret_cast<vec_t*>(output + offset);
for (IndexType j = 0; j < NumOutputChunks; j += 1)
{
@ -296,9 +294,11 @@ namespace Stockfish::Eval::NNUE {
#else
for (IndexType j = 0; j < HalfDimensions / 2; ++j) {
for (IndexType j = 0; j < HalfDimensions / 2; ++j)
{
BiasType sum0 = accumulation[static_cast<int>(perspectives[p])][j + 0];
BiasType sum1 = accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
BiasType sum1 =
accumulation[static_cast<int>(perspectives[p])][j + HalfDimensions / 2];
sum0 = std::clamp<BiasType>(sum0, 0, 127);
sum1 = std::clamp<BiasType>(sum1, 0, 127);
output[offset + j] = static_cast<OutputType>(unsigned(sum0 * sum1) / 128);
@ -317,7 +317,8 @@ namespace Stockfish::Eval::NNUE {
private:
template<Color Perspective>
[[nodiscard]] std::pair<StateInfo*, StateInfo*> try_find_computed_accumulator(const Position& pos) const {
[[nodiscard]] std::pair<StateInfo*, StateInfo*>
try_find_computed_accumulator(const Position& pos) const {
// Look for a usable accumulator of an earlier position. We keep track
// of the estimated gain in terms of features to be added/subtracted.
StateInfo *st = pos.state(), *next = nullptr;
@ -326,13 +327,13 @@ namespace Stockfish::Eval::NNUE {
{
// This governs when a full feature refresh is needed and how many
// updates are better than just one full refresh.
if ( FeatureSet::requires_refresh(st, Perspective)
if (FeatureSet::requires_refresh(st, Perspective)
|| (gain -= FeatureSet::update_cost(st) + 1) < 0)
break;
next = st;
st = st->previous;
}
return { st, next };
return {st, next};
}
// NOTE: The parameter states_to_update is an array of position states, ending with nullptr.
@ -340,16 +341,18 @@ namespace Stockfish::Eval::NNUE {
// by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr.
// computed_st must be reachable by repeatedly applying ->previous on states_to_update[0], if not nullptr.
template<Color Perspective, size_t N>
void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const {
void update_accumulator_incremental(const Position& pos,
StateInfo* computed_st,
StateInfo* states_to_update[N]) const {
static_assert(N > 0);
assert(states_to_update[N-1] == nullptr);
assert(states_to_update[N - 1] == nullptr);
#ifdef VECTOR
#ifdef VECTOR
// Gcc-10.2 unnecessarily spills AVX2 registers if this array
// is defined in the VECTOR code below, once in each branch
vec_t acc[NumRegs];
psqt_vec_t psqt[NumPsqtRegs];
#endif
#endif
if (states_to_update[0] == nullptr)
return;
@ -363,10 +366,12 @@ namespace Stockfish::Eval::NNUE {
// That might depend on the feature set and generally relies on the
// feature set's update cost calculation to be correct and never
// allow updates with more added/removed features than MaxActiveDimensions.
FeatureSet::IndexList removed[N-1], added[N-1];
FeatureSet::IndexList removed[N - 1], added[N - 1];
{
int i = N-2; // last potential state to update. Skip last element because it must be nullptr.
int i =
N
- 2; // last potential state to update. Skip last element because it must be nullptr.
while (states_to_update[i] == nullptr)
--i;
@ -379,8 +384,8 @@ namespace Stockfish::Eval::NNUE {
const StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1];
for (; st2 != end_state; st2 = st2->previous)
FeatureSet::append_changed_indices<Perspective>(
ksq, st2->dirtyPiece, removed[i], added[i]);
FeatureSet::append_changed_indices<Perspective>(ksq, st2->dirtyPiece,
removed[i], added[i]);
}
}
@ -389,14 +394,13 @@ namespace Stockfish::Eval::NNUE {
// Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
#ifdef VECTOR
if ( states_to_update[1] == nullptr
&& (removed[0].size() == 1 || removed[0].size() == 2)
if (states_to_update[1] == nullptr && (removed[0].size() == 1 || removed[0].size() == 2)
&& added[0].size() == 1)
{
assert(states_to_update[0]);
auto accIn = reinterpret_cast<const vec_t*>(
&st->accumulator.accumulation[Perspective][0]);
auto accIn =
reinterpret_cast<const vec_t*>(&st->accumulator.accumulation[Perspective][0]);
auto accOut = reinterpret_cast<vec_t*>(
&states_to_update[0]->accumulator.accumulation[Perspective][0]);
@ -407,7 +411,8 @@ namespace Stockfish::Eval::NNUE {
if (removed[0].size() == 1)
{
for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k)
for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t);
++k)
accOut[k] = vec_add_16(vec_sub_16(accIn[k], columnR0[k]), columnA[k]);
}
else
@ -415,9 +420,9 @@ namespace Stockfish::Eval::NNUE {
const IndexType offsetR1 = HalfDimensions * removed[0][1];
auto columnR1 = reinterpret_cast<const vec_t*>(&weights[offsetR1]);
for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t); ++k)
accOut[k] = vec_sub_16(
vec_add_16(accIn[k], columnA[k]),
for (IndexType k = 0; k < HalfDimensions * sizeof(std::int16_t) / sizeof(vec_t);
++k)
accOut[k] = vec_sub_16(vec_add_16(accIn[k], columnA[k]),
vec_add_16(columnR0[k], columnR1[k]));
}
@ -433,18 +438,20 @@ namespace Stockfish::Eval::NNUE {
if (removed[0].size() == 1)
{
for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k)
accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32(
accPsqtIn[k], columnPsqtR0[k]), columnPsqtA[k]);
for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t);
++k)
accPsqtOut[k] = vec_add_psqt_32(vec_sub_psqt_32(accPsqtIn[k], columnPsqtR0[k]),
columnPsqtA[k]);
}
else
{
const IndexType offsetPsqtR1 = PSQTBuckets * removed[0][1];
auto columnPsqtR1 = reinterpret_cast<const psqt_vec_t*>(&psqtWeights[offsetPsqtR1]);
for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t); ++k)
accPsqtOut[k] = vec_sub_psqt_32(
vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]),
for (std::size_t k = 0; k < PSQTBuckets * sizeof(std::int32_t) / sizeof(psqt_vec_t);
++k)
accPsqtOut[k] =
vec_sub_psqt_32(vec_add_psqt_32(accPsqtIn[k], columnPsqtA[k]),
vec_add_psqt_32(columnPsqtR0[k], columnPsqtR1[k]));
}
}
@ -516,7 +523,8 @@ namespace Stockfish::Eval::NNUE {
// Store accumulator
auto accTilePsqtOut = reinterpret_cast<psqt_vec_t*>(
&states_to_update[i]->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
&states_to_update[i]
->accumulator.psqtAccumulation[Perspective][j * PsqtTileHeight]);
for (std::size_t k = 0; k < NumPsqtRegs; ++k)
vec_store_psqt(&accTilePsqtOut[k], psqt[k]);
}
@ -530,7 +538,8 @@ namespace Stockfish::Eval::NNUE {
HalfDimensions * sizeof(BiasType));
for (std::size_t k = 0; k < PSQTBuckets; ++k)
states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] = st->accumulator.psqtAccumulation[Perspective][k];
states_to_update[i]->accumulator.psqtAccumulation[Perspective][k] =
st->accumulator.psqtAccumulation[Perspective][k];
st = states_to_update[i];
@ -543,7 +552,8 @@ namespace Stockfish::Eval::NNUE {
st->accumulator.accumulation[Perspective][j] -= weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
st->accumulator.psqtAccumulation[Perspective][k] -= psqtWeights[index * PSQTBuckets + k];
st->accumulator.psqtAccumulation[Perspective][k] -=
psqtWeights[index * PSQTBuckets + k];
}
// Difference calculation for the activated features
@ -555,7 +565,8 @@ namespace Stockfish::Eval::NNUE {
st->accumulator.accumulation[Perspective][j] += weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
st->accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
st->accumulator.psqtAccumulation[Perspective][k] +=
psqtWeights[index * PSQTBuckets + k];
}
}
#endif
@ -563,12 +574,12 @@ namespace Stockfish::Eval::NNUE {
template<Color Perspective>
void update_accumulator_refresh(const Position& pos) const {
#ifdef VECTOR
#ifdef VECTOR
// Gcc-10.2 unnecessarily spills AVX2 registers if this array
// is defined in the VECTOR code below, once in each branch
vec_t acc[NumRegs];
psqt_vec_t psqt[NumPsqtRegs];
#endif
#endif
// Refresh the accumulator
// Could be extracted to a separate function because it's done in 2 places,
@ -581,8 +592,7 @@ namespace Stockfish::Eval::NNUE {
#ifdef VECTOR
for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
{
auto biasesTile = reinterpret_cast<const vec_t*>(
&biases[j * TileHeight]);
auto biasesTile = reinterpret_cast<const vec_t*>(&biases[j * TileHeight]);
for (IndexType k = 0; k < NumRegs; ++k)
acc[k] = biasesTile[k];
@ -595,8 +605,8 @@ namespace Stockfish::Eval::NNUE {
acc[k] = vec_add_16(acc[k], column[k]);
}
auto accTile = reinterpret_cast<vec_t*>(
&accumulator.accumulation[Perspective][j * TileHeight]);
auto accTile =
reinterpret_cast<vec_t*>(&accumulator.accumulation[Perspective][j * TileHeight]);
for (unsigned k = 0; k < NumRegs; k++)
vec_store(&accTile[k], acc[k]);
}
@ -636,7 +646,8 @@ namespace Stockfish::Eval::NNUE {
accumulator.accumulation[Perspective][j] += weights[offset + j];
for (std::size_t k = 0; k < PSQTBuckets; ++k)
accumulator.psqtAccumulation[Perspective][k] += psqtWeights[index * PSQTBuckets + k];
accumulator.psqtAccumulation[Perspective][k] +=
psqtWeights[index * PSQTBuckets + k];
}
#endif
}
@ -658,7 +669,7 @@ namespace Stockfish::Eval::NNUE {
if (oldest_st->accumulator.computed[Perspective])
{
// Only update current position accumulator to minimize work.
StateInfo* states_to_update[2] = { pos.state(), nullptr };
StateInfo* states_to_update[2] = {pos.state(), nullptr};
update_accumulator_incremental<Perspective, 2>(pos, oldest_st, states_to_update);
}
else
@ -682,8 +693,8 @@ namespace Stockfish::Eval::NNUE {
// 1. for the current position
// 2. the next accumulator after the computed one
// The heuristic may change in the future.
StateInfo *states_to_update[3] =
{ next, next == pos.state() ? nullptr : pos.state(), nullptr };
StateInfo* states_to_update[3] = {next, next == pos.state() ? nullptr : pos.state(),
nullptr};
update_accumulator_incremental<Perspective, 3>(pos, oldest_st, states_to_update);
}
@ -696,7 +707,7 @@ namespace Stockfish::Eval::NNUE {
alignas(CacheLineSize) BiasType biases[HalfDimensions];
alignas(CacheLineSize) WeightType weights[HalfDimensions * InputDimensions];
alignas(CacheLineSize) PSQTWeightType psqtWeights[InputDimensions * PSQTBuckets];
};
};
} // namespace Stockfish::Eval::NNUE

View file

@ -46,18 +46,18 @@ namespace Stockfish {
namespace Zobrist {
Key psq[PIECE_NB][SQUARE_NB];
Key enpassant[FILE_NB];
Key castling[CASTLING_RIGHT_NB];
Key side;
Key psq[PIECE_NB][SQUARE_NB];
Key enpassant[FILE_NB];
Key castling[CASTLING_RIGHT_NB];
Key side;
}
namespace {
constexpr std::string_view PieceToChar(" PNBRQK pnbrqk");
constexpr Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING };
constexpr Piece Pieces[] = {W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING,
B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING};
} // namespace
@ -76,15 +76,13 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) {
}
os << " a b c d e f g h\n"
<< "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
<< std::setfill('0') << std::setw(16) << pos.key()
<< std::setfill(' ') << std::dec << "\nCheckers: ";
<< "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase << std::setfill('0')
<< std::setw(16) << pos.key() << std::setfill(' ') << std::dec << "\nCheckers: ";
for (Bitboard b = pos.checkers(); b; )
for (Bitboard b = pos.checkers(); b;)
os << UCI::square(pop_lsb(b)) << " ";
if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces())
&& !pos.can_castle(ANY_CASTLING))
if (int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))
{
StateInfo st;
ASSERT_ALIGNED(&st, Eval::NNUE::CacheLineSize);
@ -165,7 +163,7 @@ void Position::init() {
// this is assumed to be the responsibility of the GUI.
Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Thread* th) {
/*
/*
A FEN string defines a particular position using only the ASCII character set.
A FEN string contains six fields separated by a space. The fields are:
@ -220,7 +218,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
else if (token == '/')
sq += 2 * SOUTH;
else if ((idx = PieceToChar.find(token)) != string::npos) {
else if ((idx = PieceToChar.find(token)) != string::npos)
{
put_piece(Piece(idx), sq);
++sq;
}
@ -245,10 +244,12 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
token = char(toupper(token));
if (token == 'K')
for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq) {}
for (rsq = relative_square(c, SQ_H1); piece_on(rsq) != rook; --rsq)
{}
else if (token == 'Q')
for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq) {}
for (rsq = relative_square(c, SQ_A1); piece_on(rsq) != rook; ++rsq)
{}
else if (token >= 'A' && token <= 'H')
rsq = make_square(File(token - 'A'), relative_rank(c, RANK_1));
@ -263,7 +264,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
// Ignore if square is invalid or not on side to move relative rank 6.
bool enpassant = false;
if ( ((ss >> col) && (col >= 'a' && col <= 'h'))
if (((ss >> col) && (col >= 'a' && col <= 'h'))
&& ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3'))))
{
st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
@ -303,7 +304,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th
void Position::set_castling_right(Color c, Square rfrom) {
Square kfrom = square<KING>(c);
CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE: QUEEN_SIDE);
CastlingRights cr = c & (kfrom < rfrom ? KING_SIDE : QUEEN_SIDE);
st->castlingRights |= cr;
castlingRightsMask[kfrom] |= cr;
@ -313,8 +314,7 @@ void Position::set_castling_right(Color c, Square rfrom) {
Square kto = relative_square(c, cr & KING_SIDE ? SQ_G1 : SQ_C1);
Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto))
& ~(kfrom | rfrom);
castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto)) & ~(kfrom | rfrom);
}
@ -348,7 +348,7 @@ void Position::set_state() const {
set_check_info();
for (Bitboard b = pieces(); b; )
for (Bitboard b = pieces(); b;)
{
Square s = pop_lsb(b);
Piece pc = piece_on(s);
@ -380,16 +380,16 @@ Position& Position::set(const string& code, Color c, StateInfo* si) {
assert(code[0] == 'K');
string sides[] = { code.substr(code.find('K', 1)), // Weak
code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong
string sides[] = {code.substr(code.find('K', 1)), // Weak
code.substr(0, std::min(code.find('v'), code.find('K', 1)))}; // Strong
assert(sides[0].length() > 0 && sides[0].length() < 8);
assert(sides[1].length() > 0 && sides[1].length() < 8);
std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/"
+ sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + sides[1]
+ char(8 - sides[1].length() + '0') + "/8 w - - 0 10";
return set(fenStr, false, si, nullptr);
}
@ -424,13 +424,13 @@ string Position::fen() const {
ss << (sideToMove == WHITE ? " w " : " b ");
if (can_castle(WHITE_OO))
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO ))) : 'K');
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OO))) : 'K');
if (can_castle(WHITE_OOO))
ss << (chess960 ? char('A' + file_of(castling_rook_square(WHITE_OOO))) : 'Q');
if (can_castle(BLACK_OO))
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO ))) : 'k');
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OO))) : 'k');
if (can_castle(BLACK_OOO))
ss << (chess960 ? char('a' + file_of(castling_rook_square(BLACK_OOO))) : 'q');
@ -438,8 +438,8 @@ string Position::fen() const {
if (!can_castle(ANY_CASTLING))
ss << '-';
ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ")
<< st->rule50 << " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
ss << (ep_square() == SQ_NONE ? " - " : " " + UCI::square(ep_square()) + " ") << st->rule50
<< " " << 1 + (gamePly - (sideToMove == BLACK)) / 2;
return ss.str();
}
@ -455,8 +455,9 @@ void Position::update_slider_blockers(Color c) const {
st->pinners[~c] = 0;
// Snipers are sliders that attack 's' when a piece and other snipers are removed
Bitboard snipers = ( (attacks_bb< ROOK>(ksq) & pieces(QUEEN, ROOK))
| (attacks_bb<BISHOP>(ksq) & pieces(QUEEN, BISHOP))) & pieces(~c);
Bitboard snipers = ((attacks_bb<ROOK>(ksq) & pieces(QUEEN, ROOK))
| (attacks_bb<BISHOP>(ksq) & pieces(QUEEN, BISHOP)))
& pieces(~c);
Bitboard occupancy = pieces() ^ snipers;
while (snipers)
@ -482,7 +483,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN))
| (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN))
| (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
| (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN))
| (attacks_bb<ROOK>(s, occupied) & pieces(ROOK, QUEEN))
| (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
| (attacks_bb<KING>(s) & pieces(KING));
}
@ -515,7 +516,7 @@ bool Position::legal(Move m) const {
assert(piece_on(capsq) == make_piece(~us, PAWN));
assert(piece_on(to) == NO_PIECE);
return !(attacks_bb< ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
return !(attacks_bb<ROOK>(ksq, occupied) & pieces(~us, QUEEN, ROOK))
&& !(attacks_bb<BISHOP>(ksq, occupied) & pieces(~us, QUEEN, BISHOP));
}
@ -544,8 +545,7 @@ bool Position::legal(Move m) const {
// A non-king move is legal if and only if it is not pinned or it
// is moving along the ray towards or away from the king.
return !(blockers_for_king(us) & from)
|| aligned(from, to, square<KING>(us));
return !(blockers_for_king(us) & from) || aligned(from, to, square<KING>(us));
}
@ -563,7 +563,7 @@ bool Position::pseudo_legal(const Move m) const {
// Use a slower but simpler function for uncommon cases
// yet we skip the legality check of MoveList<LEGAL>().
if (type_of(m) != NORMAL)
return checkers() ? MoveList< EVASIONS>(*this).contains(m)
return checkers() ? MoveList<EVASIONS>(*this).contains(m)
: MoveList<NON_EVASIONS>(*this).contains(m);
// Is not a promotion, so the promotion piece must be empty
@ -586,12 +586,10 @@ bool Position::pseudo_legal(const Move m) const {
if ((Rank8BB | Rank1BB) & to)
return false;
if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
if (!(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
&& !((from + pawn_push(us) == to) && empty(to)) // Not a single push
&& !( (from + 2 * pawn_push(us) == to) // Not a double push
&& (relative_rank(us, from) == RANK_2)
&& empty(to)
&& empty(to - pawn_push(us))))
&& !((from + 2 * pawn_push(us) == to) // Not a double push
&& (relative_rank(us, from) == RANK_2) && empty(to) && empty(to - pawn_push(us))))
return false;
}
else if (!(attacks_bb(type_of(pc), from, pieces()) & to))
@ -638,30 +636,29 @@ bool Position::gives_check(Move m) const {
// Is there a discovered check?
if (blockers_for_king(~sideToMove) & from)
return !aligned(from, to, square<KING>(~sideToMove))
|| type_of(m) == CASTLING;
return !aligned(from, to, square<KING>(~sideToMove)) || type_of(m) == CASTLING;
switch (type_of(m))
{
case NORMAL:
case NORMAL :
return false;
case PROMOTION:
case PROMOTION :
return attacks_bb(promotion_type(m), to, pieces() ^ from) & square<KING>(~sideToMove);
// En passant capture with check? We have already handled the case
// of direct checks and ordinary discovered check, so the only case we
// need to handle is the unusual case of a discovered check through
// the captured pawn.
case EN_PASSANT:
{
case EN_PASSANT : {
Square capsq = make_square(file_of(to), rank_of(from));
Bitboard b = (pieces() ^ from ^ capsq) | to;
return (attacks_bb< ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
| (attacks_bb<BISHOP>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, BISHOP));
return (attacks_bb<ROOK>(square<KING>(~sideToMove), b) & pieces(sideToMove, QUEEN, ROOK))
| (attacks_bb<BISHOP>(square<KING>(~sideToMove), b)
& pieces(sideToMove, QUEEN, BISHOP));
}
default: //CASTLING
default : //CASTLING
{
// Castling is encoded as 'king captures the rook'
Square rto = relative_square(sideToMove, to > from ? SQ_F1 : SQ_D1);
@ -796,7 +793,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
if (type_of(pc) == PAWN)
{
// Set en passant square if the moved pawn can be captured
if ( (int(to) ^ int(from)) == 16
if ((int(to) ^ int(from)) == 16
&& (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
{
st->epSquare = to - pawn_push(us);
@ -822,8 +819,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) {
// Update hash keys
k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[promotion][to];
st->materialKey ^= Zobrist::psq[promotion][pieceCount[promotion]-1]
^ Zobrist::psq[pc][pieceCount[pc]];
st->materialKey ^=
Zobrist::psq[promotion][pieceCount[promotion] - 1] ^ Zobrist::psq[pc][pieceCount[pc]];
// Update material
st->nonPawnMaterial[us] += PieceValue[promotion];
@ -959,7 +956,8 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ
// Remove both pieces first since squares could overlap in Chess960
remove_piece(Do ? from : to);
remove_piece(Do ? rfrom : rto);
board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // remove_piece does not do this for us
board[Do ? from : to] = board[Do ? rfrom : rto] =
NO_PIECE; // remove_piece does not do this for us
put_piece(make_piece(us, KING), Do ? to : from);
put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
}
@ -1033,8 +1031,7 @@ Key Position::key_after(Move m) const {
k ^= Zobrist::psq[pc][to] ^ Zobrist::psq[pc][from];
return (captured || type_of(pc) == PAWN)
? k : adjust_key50<true>(k);
return (captured || type_of(pc) == PAWN) ? k : adjust_key50<true>(k);
}
@ -1131,7 +1128,7 @@ bool Position::see_ge(Move m, Value threshold) const {
occupied ^= least_significant_square_bb(bb);
attackers |= (attacks_bb<BISHOP>(to, occupied) & pieces(BISHOP, QUEEN))
| (attacks_bb<ROOK >(to, occupied) & pieces(ROOK , QUEEN));
| (attacks_bb<ROOK>(to, occupied) & pieces(ROOK, QUEEN));
}
else // KING
@ -1195,8 +1192,7 @@ bool Position::has_game_cycle(int ply) const {
stp = stp->previous->previous;
Key moveKey = originalKey ^ stp->key;
if ( (j = H1(moveKey), cuckoo[j] == moveKey)
|| (j = H2(moveKey), cuckoo[j] == moveKey))
if ((j = H1(moveKey), cuckoo[j] == moveKey) || (j = H2(moveKey), cuckoo[j] == moveKey))
{
Move move = cuckooMove[j];
Square s1 = from_sq(move);
@ -1267,30 +1263,23 @@ bool Position::pos_is_ok() const {
constexpr bool Fast = true; // Quick (default) or full check?
if ( (sideToMove != WHITE && sideToMove != BLACK)
|| piece_on(square<KING>(WHITE)) != W_KING
if ((sideToMove != WHITE && sideToMove != BLACK) || piece_on(square<KING>(WHITE)) != W_KING
|| piece_on(square<KING>(BLACK)) != B_KING
|| ( ep_square() != SQ_NONE
&& relative_rank(sideToMove, ep_square()) != RANK_6))
|| (ep_square() != SQ_NONE && relative_rank(sideToMove, ep_square()) != RANK_6))
assert(0 && "pos_is_ok: Default");
if (Fast)
return true;
if ( pieceCount[W_KING] != 1
|| pieceCount[B_KING] != 1
if (pieceCount[W_KING] != 1 || pieceCount[B_KING] != 1
|| attackers_to(square<KING>(~sideToMove)) & pieces(sideToMove))
assert(0 && "pos_is_ok: Kings");
if ( (pieces(PAWN) & (Rank1BB | Rank8BB))
|| pieceCount[W_PAWN] > 8
|| pieceCount[B_PAWN] > 8)
if ((pieces(PAWN) & (Rank1BB | Rank8BB)) || pieceCount[W_PAWN] > 8 || pieceCount[B_PAWN] > 8)
assert(0 && "pos_is_ok: Pawns");
if ( (pieces(WHITE) & pieces(BLACK))
|| (pieces(WHITE) | pieces(BLACK)) != pieces()
|| popcount(pieces(WHITE)) > 16
|| popcount(pieces(BLACK)) > 16)
if ((pieces(WHITE) & pieces(BLACK)) || (pieces(WHITE) | pieces(BLACK)) != pieces()
|| popcount(pieces(WHITE)) > 16 || popcount(pieces(BLACK)) > 16)
assert(0 && "pos_is_ok: Bitboards");
for (PieceType p1 = PAWN; p1 <= KING; ++p1)
@ -1300,17 +1289,17 @@ bool Position::pos_is_ok() const {
for (Piece pc : Pieces)
if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))
|| pieceCount[pc] != std::count(board, board + SQUARE_NB, pc))
assert(0 && "pos_is_ok: Pieces");
for (Color c : { WHITE, BLACK })
for (Color c : {WHITE, BLACK})
for (CastlingRights cr : {c & KING_SIDE, c & QUEEN_SIDE})
{
if (!can_castle(cr))
continue;
if ( piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)
if (piece_on(castlingRookSquare[cr]) != make_piece(c, ROOK)
|| castlingRightsMask[castlingRookSquare[cr]] != cr
|| (castlingRightsMask[square<KING>(c)] & cr) != cr)
assert(0 && "pos_is_ok: Castling");

View file

@ -75,7 +75,7 @@ using StateListPtr = std::unique_ptr<std::deque<StateInfo>>;
class Thread;
class Position {
public:
public:
static void init();
Position() = default;
@ -89,15 +89,20 @@ public:
// Position representation
Bitboard pieces(PieceType pt = ALL_PIECES) const;
template<typename ...PieceTypes> Bitboard pieces(PieceType pt, PieceTypes... pts) const;
template<typename... PieceTypes>
Bitboard pieces(PieceType pt, PieceTypes... pts) const;
Bitboard pieces(Color c) const;
template<typename ...PieceTypes> Bitboard pieces(Color c, PieceTypes... pts) const;
template<typename... PieceTypes>
Bitboard pieces(Color c, PieceTypes... pts) const;
Piece piece_on(Square s) const;
Square ep_square() const;
bool empty(Square s) const;
template<PieceType Pt> int count(Color c) const;
template<PieceType Pt> int count() const;
template<PieceType Pt> Square square(Color c) const;
template<PieceType Pt>
int count(Color c) const;
template<PieceType Pt>
int count() const;
template<PieceType Pt>
Square square(Color c) const;
// Castling
CastlingRights castling_rights(Color c) const;
@ -115,7 +120,8 @@ public:
Bitboard attackers_to(Square s) const;
Bitboard attackers_to(Square s, Bitboard occupied) const;
void update_slider_blockers(Color c) const;
template<PieceType Pt> Bitboard attacks_by(Color c) const;
template<PieceType Pt>
Bitboard attacks_by(Color c) const;
// Properties of moves
bool legal(Move m) const;
@ -163,7 +169,7 @@ public:
void put_piece(Piece pc, Square s);
void remove_piece(Square s);
private:
private:
// Initialization helpers (used while setting up a position)
void set_castling_right(Color c, Square rfrom);
void set_state() const;
@ -193,61 +199,50 @@ private:
std::ostream& operator<<(std::ostream& os, const Position& pos);
inline Color Position::side_to_move() const {
return sideToMove;
}
inline Color Position::side_to_move() const { return sideToMove; }
inline Piece Position::piece_on(Square s) const {
assert(is_ok(s));
return board[s];
}
inline bool Position::empty(Square s) const {
return piece_on(s) == NO_PIECE;
}
inline bool Position::empty(Square s) const { return piece_on(s) == NO_PIECE; }
inline Piece Position::moved_piece(Move m) const {
return piece_on(from_sq(m));
}
inline Piece Position::moved_piece(Move m) const { return piece_on(from_sq(m)); }
inline Bitboard Position::pieces(PieceType pt) const {
return byTypeBB[pt];
}
inline Bitboard Position::pieces(PieceType pt) const { return byTypeBB[pt]; }
template<typename ...PieceTypes>
template<typename... PieceTypes>
inline Bitboard Position::pieces(PieceType pt, PieceTypes... pts) const {
return pieces(pt) | pieces(pts...);
}
inline Bitboard Position::pieces(Color c) const {
return byColorBB[c];
}
inline Bitboard Position::pieces(Color c) const { return byColorBB[c]; }
template<typename ...PieceTypes>
template<typename... PieceTypes>
inline Bitboard Position::pieces(Color c, PieceTypes... pts) const {
return pieces(c) & pieces(pts...);
}
template<PieceType Pt> inline int Position::count(Color c) const {
template<PieceType Pt>
inline int Position::count(Color c) const {
return pieceCount[make_piece(c, Pt)];
}
template<PieceType Pt> inline int Position::count() const {
template<PieceType Pt>
inline int Position::count() const {
return count<Pt>(WHITE) + count<Pt>(BLACK);
}
template<PieceType Pt> inline Square Position::square(Color c) const {
template<PieceType Pt>
inline Square Position::square(Color c) const {
assert(count<Pt>(c) == 1);
return lsb(pieces(c, Pt));
}
inline Square Position::ep_square() const {
return st->epSquare;
}
inline Square Position::ep_square() const { return st->epSquare; }
inline bool Position::can_castle(CastlingRights cr) const {
return st->castlingRights & cr;
}
inline bool Position::can_castle(CastlingRights cr) const { return st->castlingRights & cr; }
inline CastlingRights Position::castling_rights(Color c) const {
return c & CastlingRights(st->castlingRights);
@ -265,9 +260,7 @@ inline Square Position::castling_rook_square(CastlingRights cr) const {
return castlingRookSquare[cr];
}
inline Bitboard Position::attackers_to(Square s) const {
return attackers_to(s, pieces());
}
inline Bitboard Position::attackers_to(Square s) const { return attackers_to(s, pieces()); }
template<PieceType Pt>
inline Bitboard Position::attacks_by(Color c) const {
@ -285,61 +278,38 @@ inline Bitboard Position::attacks_by(Color c) const {
}
}
inline Bitboard Position::checkers() const {
return st->checkersBB;
}
inline Bitboard Position::checkers() const { return st->checkersBB; }
inline Bitboard Position::blockers_for_king(Color c) const {
return st->blockersForKing[c];
}
inline Bitboard Position::blockers_for_king(Color c) const { return st->blockersForKing[c]; }
inline Bitboard Position::pinners(Color c) const {
return st->pinners[c];
}
inline Bitboard Position::pinners(Color c) const { return st->pinners[c]; }
inline Bitboard Position::check_squares(PieceType pt) const {
return st->checkSquares[pt];
}
inline Bitboard Position::check_squares(PieceType pt) const { return st->checkSquares[pt]; }
inline Key Position::key() const {
return adjust_key50<false>(st->key);
}
inline Key Position::key() const { return adjust_key50<false>(st->key); }
template<bool AfterMove>
inline Key Position::adjust_key50(Key k) const
{
return st->rule50 < 14 - AfterMove
? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
inline Key Position::adjust_key50(Key k) const {
return st->rule50 < 14 - AfterMove ? k : k ^ make_key((st->rule50 - (14 - AfterMove)) / 8);
}
inline Key Position::material_key() const {
return st->materialKey;
}
inline Key Position::material_key() const { return st->materialKey; }
inline Value Position::non_pawn_material(Color c) const {
return st->nonPawnMaterial[c];
}
inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; }
inline Value Position::non_pawn_material() const {
return non_pawn_material(WHITE) + non_pawn_material(BLACK);
}
inline int Position::game_ply() const {
return gamePly;
}
inline int Position::game_ply() const { return gamePly; }
inline int Position::rule50_count() const {
return st->rule50;
}
inline int Position::rule50_count() const { return st->rule50; }
inline bool Position::is_chess960() const {
return chess960;
}
inline bool Position::is_chess960() const { return chess960; }
inline bool Position::capture(Move m) const {
assert(is_ok(m));
return (!empty(to_sq(m)) && type_of(m) != CASTLING)
|| type_of(m) == EN_PASSANT;
return (!empty(to_sq(m)) && type_of(m) != CASTLING) || type_of(m) == EN_PASSANT;
}
// Returns true if a move is generated from the capture stage, having also
@ -350,13 +320,9 @@ inline bool Position::capture_stage(Move m) const {
return capture(m) || promotion_type(m) == QUEEN;
}
inline Piece Position::captured_piece() const {
return st->capturedPiece;
}
inline Piece Position::captured_piece() const { return st->capturedPiece; }
inline Thread* Position::this_thread() const {
return thisThread;
}
inline Thread* Position::this_thread() const { return thisThread; }
inline void Position::put_piece(Piece pc, Square s) {
@ -389,14 +355,9 @@ inline void Position::move_piece(Square from, Square to) {
board[to] = pc;
}
inline void Position::do_move(Move m, StateInfo& newSt) {
do_move(m, newSt, gives_check(m));
}
inline void Position::do_move(Move m, StateInfo& newSt) { do_move(m, newSt, gives_check(m)); }
inline StateInfo* Position::state() const {
return st;
}
inline StateInfo* Position::state() const { return st; }
} // namespace Stockfish

File diff suppressed because it is too large Load diff

View file

@ -61,12 +61,12 @@ struct Stack {
struct RootMove {
explicit RootMove(Move m) : pv(1, m) {}
explicit RootMove(Move m) :
pv(1, m) {}
bool extract_ponder_from_tt(Position& pos);
bool operator==(const Move& m) const { return pv[0] == m; }
bool operator<(const RootMove& m) const { // Sort in descending order
return m.score != score ? m.score < score
: m.previousScore < previousScore;
return m.score != score ? m.score < score : m.previousScore < previousScore;
}
Value score = -VALUE_INFINITE;
@ -95,9 +95,7 @@ struct LimitsType {
nodes = 0;
}
bool use_time_management() const {
return time[WHITE] || time[BLACK];
}
bool use_time_management() const { return time[WHITE] || time[BLACK]; }
std::vector<Move> searchmoves;
TimePoint time[COLOR_NB], inc[COLOR_NB], npmsec, movetime, startTime;

View file

@ -45,15 +45,15 @@
#include "../uci.h"
#ifndef _WIN32
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#else
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
# define NOMINMAX // Disable macros min() and max()
#endif
#include <windows.h>
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX // Disable macros min() and max()
#endif
#include <windows.h>
#endif
using namespace Stockfish::Tablebases;
@ -65,13 +65,27 @@ namespace Stockfish {
namespace {
constexpr int TBPIECES = 7; // Max number of supported pieces
constexpr int MAX_DTZ = 1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit.
constexpr int MAX_DTZ =
1 << 18; // Max DTZ supported, large enough to deal with the syzygy TB limit.
enum { BigEndian, LittleEndian };
enum TBType { WDL, DTZ }; // Used as template parameter
enum {
BigEndian,
LittleEndian
};
enum TBType {
WDL,
DTZ
}; // Used as template parameter
// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables
enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 };
enum TBFlag {
STM = 1,
Mapped = 2,
WinPlies = 4,
LossPlies = 8,
Wide = 16,
SingleValue = 128
};
inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }
inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
@ -91,33 +105,28 @@ int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D]
bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; }
int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); }
constexpr Value WDL_to_value[] = {
-VALUE_MATE + MAX_PLY + 1,
VALUE_DRAW - 2,
VALUE_DRAW,
VALUE_DRAW + 2,
VALUE_MATE - MAX_PLY - 1
};
constexpr Value WDL_to_value[] = {-VALUE_MATE + MAX_PLY + 1, VALUE_DRAW - 2, VALUE_DRAW,
VALUE_DRAW + 2, VALUE_MATE - MAX_PLY - 1};
template<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>
inline void swap_endian(T& x)
{
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;
uint8_t tmp, *c = (uint8_t*) &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&) {}
template<>
inline void swap_endian<uint8_t>(uint8_t&) {}
template<typename T, int LE> T number(void* addr)
{
template<typename T, int LE>
T number(void* addr) {
T v;
if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare)
std::memcpy(&v, addr, sizeof(T));
else
v = *((T*)addr);
v = *((T*) addr);
if (LE != IsLittleEndian)
swap_endian(v);
@ -128,14 +137,16 @@ template<typename T, int LE> T number(void* addr)
// like captures and pawn moves but we can easily recover the correct dtz of the
// previous move if we know the position's WDL score.
int dtz_before_zeroing(WDLScore wdl) {
return wdl == WDLWin ? 1 :
wdl == WDLCursedWin ? 101 :
wdl == WDLBlessedLoss ? -101 :
wdl == WDLLoss ? -1 : 0;
return wdl == WDLWin ? 1
: wdl == WDLCursedWin ? 101
: wdl == WDLBlessedLoss ? -101
: wdl == WDLLoss ? -1
: 0;
}
// Return the sign of a number (-1, 0, 1)
template <typename T> int sign_of(T val) {
template<typename T>
int sign_of(T val) {
return (T(0) < val) - (val < T(0));
}
@ -150,15 +161,19 @@ static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");
using Sym = uint16_t; // Huffman symbol
struct LR {
enum Side { Left, Right };
enum Side {
Left,
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.
template<Side S>
Sym get() {
return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] :
S == Right ? (lr[2] << 4) | (lr[1] >> 4) : (assert(false), Sym(-1));
return S == Left ? ((lr[1] & 0xF) << 8) | lr[0]
: S == Right ? (lr[2] << 4) | (lr[1] >> 4)
: (assert(false), Sym(-1));
}
};
@ -173,11 +188,11 @@ static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes");
// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are
// memory mapped for best performance. Files are mapped at first access: at init
// time only existence of the file is checked.
class TBFile : public std::ifstream {
class TBFile: public std::ifstream {
std::string fname;
public:
public:
// Look for and open the file among the Paths directories where the .rtbw
// and .rtbz files can be found. Multiple directories are separated by ";"
// on Windows and by ":" on Unix-based operating systems.
@ -227,9 +242,9 @@ public:
*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)
@ -273,10 +288,9 @@ public:
exit(EXIT_FAILURE);
}
#endif
uint8_t* data = (uint8_t*)*baseAddress;
uint8_t* data = (uint8_t*) *baseAddress;
constexpr uint8_t Magics[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 },
{ 0x71, 0xE8, 0x23, 0x5D } };
constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}};
if (memcmp(data, Magics[type == WDL], 4))
{
@ -294,7 +308,7 @@ public:
munmap(baseAddress, mapping);
#else
UnmapViewOfFile(baseAddress);
CloseHandle((HANDLE)mapping);
CloseHandle((HANDLE) mapping);
#endif
}
};
@ -318,11 +332,13 @@ struct PairsData {
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> 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
std::vector<uint64_t>
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)
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)
};
@ -348,11 +364,11 @@ struct TBTable {
uint8_t 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];
}
PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; }
TBTable() : ready(false), baseAddress(nullptr) {}
TBTable() :
ready(false),
baseAddress(nullptr) {}
explicit TBTable(const std::string& code);
explicit TBTable(const TBTable<WDL>& wdl);
@ -363,7 +379,8 @@ struct TBTable {
};
template<>
TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
TBTable<WDL>::TBTable(const std::string& code) :
TBTable() {
StateInfo st;
Position pos;
@ -373,7 +390,7 @@ TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
hasPawns = pos.pieces(PAWN);
hasUniquePieces = false;
for (Color c : { WHITE, BLACK })
for (Color c : {WHITE, BLACK})
for (PieceType pt = PAWN; pt < KING; ++pt)
if (popcount(pos.pieces(c, pt)) == 1)
hasUniquePieces = true;
@ -381,8 +398,7 @@ TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
// Set the leading color. In case both sides have pawns the leading color
// is the side with fewer pawns because this leads to better compression.
bool c = !pos.count<PAWN>(BLACK)
|| ( pos.count<PAWN>(WHITE)
&& pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));
|| (pos.count<PAWN>(WHITE) && pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));
pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);
pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);
@ -391,7 +407,8 @@ TBTable<WDL>::TBTable(const std::string& code) : TBTable() {
}
template<>
TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) : TBTable() {
TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) :
TBTable() {
// Use the corresponding WDL table to avoid recalculating all from scratch
key = wdl.key;
@ -408,15 +425,14 @@ TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) : TBTable() {
// at init time, accessed at probe time.
class TBTables {
struct Entry
{
struct Entry {
Key key;
TBTable<WDL>* wdl;
TBTable<DTZ>* dtz;
template <TBType Type>
template<TBType Type>
TBTable<Type>* get() const {
return (TBTable<Type>*)(Type == WDL ? (void*)wdl : (void*)dtz);
return (TBTable<Type>*) (Type == WDL ? (void*) wdl : (void*) dtz);
}
};
@ -430,12 +446,14 @@ class TBTables {
void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
uint32_t homeBucket = uint32_t(key) & (Size - 1);
Entry entry{ key, wdl, dtz };
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 (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)
{
Key otherKey = hashTable[bucket].key;
if (otherKey == key || !hashTable[bucket].get<WDL>()) {
if (otherKey == key || !hashTable[bucket].get<WDL>())
{
hashTable[bucket] = entry;
return;
}
@ -443,7 +461,8 @@ 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);
if (otherHomeBucket > homeBucket) {
if (otherHomeBucket > homeBucket)
{
std::swap(entry, hashTable[bucket]);
key = otherKey;
homeBucket = otherHomeBucket;
@ -453,10 +472,11 @@ class TBTables {
exit(EXIT_FAILURE);
}
public:
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[uint32_t(key) & (Size - 1)];; ++entry)
{
if (entry->key == key || !entry->get<Type>())
return entry->get<Type>();
}
@ -495,7 +515,7 @@ void TBTables::add(const std::vector<PieceType>& pieces) {
dtzTable.emplace_back(wdlTable.back());
// Insert into the hash keys for both colors: KRvK with KR white and black
insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back());
insert(wdlTable.back().key, &wdlTable.back(), &dtzTable.back());
insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back());
}
@ -560,12 +580,13 @@ 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));
uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(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); ptr += 2;
uint64_t buf64 = number<uint64_t, BigEndian>(ptr);
ptr += 2;
int buf64Size = 64;
Sym sym;
@ -598,7 +619,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
buf64 <<= len; // Consume the just processed symbol
buf64Size -= len;
if (buf64Size <= 32) { // Refill the buffer
if (buf64Size <= 32)
{ // Refill the buffer
buf64Size += 32;
buf64 |= uint64_t(number<uint32_t, BigEndian>(ptr++)) << (64 - buf64Size);
}
@ -618,7 +640,8 @@ int decompress_pairs(PairsData* d, uint64_t idx) {
// the left side because in Recursive Pairing child symbols are adjacent.
if (offset < d->symlen[left] + 1)
sym = left;
else {
else
{
offset -= d->symlen[left] + 1;
sym = d->btree[sym].get<LR::Right>();
}
@ -632,8 +655,7 @@ bool check_dtz_stm(TBTable<WDL>*, int, File) { return true; }
bool check_dtz_stm(TBTable<DTZ>* entry, int stm, File f) {
auto flags = entry->get(stm, f)->flags;
return (flags & TBFlag::STM) == stm
|| ((entry->key == entry->key2) && !entry->hasPawns);
return (flags & TBFlag::STM) == stm || ((entry->key == entry->key2) && !entry->hasPawns);
}
// DTZ scores are sorted by frequency of occurrence and then assigned the
@ -644,24 +666,24 @@ WDLScore map_score(TBTable<WDL>*, File, int value, WDLScore) { return WDLScore(v
int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {
constexpr int WDLMap[] = { 1, 3, 0, 2, 0 };
constexpr int WDLMap[] = {1, 3, 0, 2, 0};
auto flags = entry->get(0, f)->flags;
uint8_t* map = entry->map;
uint16_t* idx = entry->get(0, f)->map_idx;
if (flags & TBFlag::Mapped) {
if (flags & TBFlag::Mapped)
{
if (flags & TBFlag::Wide)
value = ((uint16_t *)map)[idx[WDLMap[wdl + 2]] + value];
value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value];
else
value = map[idx[WDLMap[wdl + 2]] + value];
}
// DTZ tables store distance to zero in number of moves or plies. We
// want to return plies, so we have to convert to plies when needed.
if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies))
|| (wdl == WDLLoss && !(flags & TBFlag::LossPlies))
|| wdl == WDLCursedWin
if ((wdl == WDLWin && !(flags & TBFlag::WinPlies))
|| (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin
|| wdl == WDLBlessedLoss)
value *= 2;
@ -704,7 +726,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
// For pawns, TB files store 4 separate tables according if leading pawn is on
// file a, b, c or d after reordering. The leading pawn is the one with maximum
// MapPawns[] value, that is the one most toward the edges and with lowest rank.
if (entry->hasPawns) {
if (entry->hasPawns)
{
// In all the 4 tables, pawns are at the beginning of the piece sequence and
// their color is the reference one. So we just pick the first one.
@ -733,7 +756,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
// Now we are ready to get all the position pieces (but the lead pawns) and
// directly map them to the correct color and square.
b = pos.pieces() ^ leadPawns;
do {
do
{
Square s = pop_lsb(b);
squares[size] = s ^ flipSquares;
pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);
@ -762,7 +786,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
// Encode leading pawns starting with the one with minimum MapPawns[] and
// proceeding in ascending order.
if (entry->hasPawns) {
if (entry->hasPawns)
{
idx = LeadPawnIdx[leadPawnsCnt][squares[0]];
std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp);
@ -781,7 +806,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
// Look for the first piece of the leading group not on the A1-D4 diagonal
// and ensure it is mapped below the diagonal.
for (int i = 0; i < d->groupLen[0]; ++i) {
for (int i = 0; i < d->groupLen[0]; ++i)
{
if (!off_A1H8(squares[i]))
continue;
@ -818,7 +844,8 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
//
// In case we have at least 3 unique pieces (including kings) we encode them
// together.
if (entry->hasUniquePieces) {
if (entry->hasUniquePieces)
{
int adjust1 = squares[1] > squares[0];
int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]);
@ -827,32 +854,26 @@ Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* resu
// triangle to 0...5. There are 63 squares for second piece and and 62
// (mapped to 0...61) for the third.
if (off_A1H8(squares[0]))
idx = ( MapA1D1D4[squares[0]] * 63
+ (squares[1] - adjust1)) * 62
+ squares[2] - adjust2;
idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2;
// First piece is on a1-h8 diagonal, second below: map this occurrence to
// 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal
// to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27.
else if (off_A1H8(squares[1]))
idx = ( 6 * 63 + rank_of(squares[0]) * 28
+ MapB1H1H7[squares[1]]) * 62
+ squares[2] - adjust2;
idx = (6 * 63 + rank_of(squares[0]) * 28 + MapB1H1H7[squares[1]]) * 62 + squares[2]
- adjust2;
// First two pieces are on a1-h8 diagonal, third below
else if (off_A1H8(squares[2]))
idx = 6 * 63 * 62 + 4 * 28 * 62
+ rank_of(squares[0]) * 7 * 28
+ (rank_of(squares[1]) - adjust1) * 28
+ MapB1H1H7[squares[2]];
idx = 6 * 63 * 62 + 4 * 28 * 62 + rank_of(squares[0]) * 7 * 28
+ (rank_of(squares[1]) - adjust1) * 28 + MapB1H1H7[squares[2]];
// All 3 pieces on the diagonal a1-h8
else
idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28
+ rank_of(squares[0]) * 7 * 6
+ (rank_of(squares[1]) - adjust1) * 6
+ (rank_of(squares[2]) - adjust2);
} else
idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + rank_of(squares[0]) * 7 * 6
+ (rank_of(squares[1]) - adjust1) * 6 + (rank_of(squares[2]) - adjust2);
}
else
// We don't have at least 3 unique pieces, like in KRRvKBB, just map
// the kings.
idx = MapKK[MapA1D1D4[squares[0]]][squares[1]];
@ -933,8 +954,7 @@ void set_groups(T& e, PairsData* d, int order[], File f) {
if (k == order[0]) // Leading pawns or pieces
{
d->groupIdx[0] = idx;
idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f]
: e.hasUniquePieces ? 31332 : 462;
idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] : e.hasUniquePieces ? 31332 : 462;
}
else if (k == order[1]) // Remaining pawns
{
@ -977,7 +997,8 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
d->flags = *data++;
if (d->flags & TBFlag::SingleValue) {
if (d->flags & TBFlag::SingleValue)
{
d->blocksNum = d->blockLengthSize = 0;
d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init
d->minSymLen = *data++; // Here we store the single value
@ -992,12 +1013,13 @@ uint8_t* set_sizes(PairsData* d, uint8_t* 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->blocksNum = number<uint32_t, LittleEndian>(data);
data += sizeof(uint32_t);
d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
// does not point out of range.
d->maxSymLen = *data++;
d->minSymLen = *data++;
d->lowestSym = (Sym*)data;
d->lowestSym = (Sym*) data;
d->base64.resize(d->maxSymLen - d->minSymLen + 1);
// See https://en.wikipedia.org/wiki/Huffman_coding
@ -1012,11 +1034,13 @@ uint8_t* set_sizes(PairsData* d, uint8_t* data) {
// avoiding unsigned overflow warnings.
int base64_size = static_cast<int>(d->base64.size());
for (int i = base64_size - 2; i >= 0; --i) {
for (int i = base64_size - 2; i >= 0; --i)
{
d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])
- number<Sym, LittleEndian>(&d->lowestSym[i + 1])) / 2;
- number<Sym, LittleEndian>(&d->lowestSym[i + 1]))
/ 2;
assert(d->base64[i] * 2 >= d->base64[i+1]);
assert(d->base64[i] * 2 >= d->base64[i + 1]);
}
// Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more
@ -1027,8 +1051,9 @@ 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->btree = (LR*)data;
d->symlen.resize(number<uint16_t, LittleEndian>(data));
data += sizeof(uint16_t);
d->btree = (LR*) data;
// The compression scheme used is "Recursive Pairing", that replaces the most
// frequent adjacent pair of symbols in the source message by a new symbol,
@ -1050,18 +1075,24 @@ uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {
e.map = data;
for (File f = FILE_A; f <= maxFile; ++f) {
for (File f = FILE_A; f <= maxFile; ++f)
{
auto flags = e.get(0, f)->flags;
if (flags & TBFlag::Mapped) {
if (flags & TBFlag::Wide) {
if (flags & TBFlag::Mapped)
{
if (flags & TBFlag::Wide)
{
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);
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;
}
}
else {
for (int i = 0; i < 4; ++i) {
else
{
for (int i = 0; i < 4; ++i)
{
e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1);
data += *data + 1;
}
@ -1079,7 +1110,10 @@ void set(T& e, uint8_t* data) {
PairsData* d;
enum { Split = 1, HasPawns = 2 };
enum {
Split = 1,
HasPawns = 2
};
assert(e.hasPawns == bool(*data & HasPawns));
assert((e.key != e.key2) == bool(*data & Split));
@ -1093,13 +1127,14 @@ void set(T& e, uint8_t* data) {
assert(!pp || e.pawnCount[0]);
for (File f = FILE_A; f <= maxFile; ++f) {
for (File f = FILE_A; f <= maxFile; ++f)
{
for (int i = 0; i < sides; i++)
*e.get(i, f) = PairsData();
int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF },
{ *data >> 4, pp ? *(data + 1) >> 4 : 0xF } };
int order[][2] = {{*data & 0xF, pp ? *(data + 1) & 0xF : 0xF},
{*data >> 4, pp ? *(data + 1) >> 4 : 0xF}};
data += 1 + pp;
for (int k = 0; k < e.pieceCount; ++k, ++data)
@ -1119,20 +1154,23 @@ void set(T& e, uint8_t* data) {
data = set_dtz_map(e, data, maxFile);
for (File f = FILE_A; f <= maxFile; ++f)
for (int i = 0; i < sides; i++) {
(d = e.get(i, f))->sparseIndex = (SparseEntry*)data;
for (int i = 0; i < sides; i++)
{
(d = e.get(i, f))->sparseIndex = (SparseEntry*) data;
data += d->sparseIndexSize * sizeof(SparseEntry);
}
for (File f = FILE_A; f <= maxFile; ++f)
for (int i = 0; i < sides; i++) {
(d = e.get(i, f))->blockLength = (uint16_t*)data;
for (int i = 0; i < sides; i++)
{
(d = e.get(i, f))->blockLength = (uint16_t*) data;
data += d->blockLengthSize * sizeof(uint16_t);
}
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
for (int i = 0; i < sides; i++)
{
data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment
(d = e.get(i, f))->data = data;
data += d->blocksNum * d->sizeofBlock;
}
@ -1159,13 +1197,14 @@ void* mapped(TBTable<Type>& e, const Position& pos) {
// Pieces strings in decreasing order for each color, like ("KPP","KR")
std::string fname, w, b;
for (PieceType pt = KING; pt >= PAWN; --pt) {
for (PieceType pt = KING; pt >= PAWN; --pt)
{
w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]);
b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]);
}
fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w)
+ (Type == WDL ? ".rtbw" : ".rtbz");
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);
@ -1214,8 +1253,7 @@ WDLScore search(Position& pos, ProbeState* result) {
for (const Move move : moveList)
{
if ( !pos.capture(move)
&& (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
if (!pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
continue;
moveCount++;
@ -1259,8 +1297,7 @@ WDLScore search(Position& pos, ProbeState* result) {
// DTZ stores a "don't care" value if bestValue is a win
if (bestValue >= value)
return *result = ( bestValue > WDLDraw
|| noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue;
return *result = (bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue;
return *result = OK, value;
}
@ -1333,8 +1370,8 @@ void Tablebases::init(const std::string& paths) {
for (int n = 1; n < 64; n++) // Squares
for (int k = 0; k < 6 && k <= n; ++k) // Pieces
Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0)
+ (k < n ? Binomial[k ][n - 1] : 0);
Binomial[k][n] =
(k > 0 ? Binomial[k - 1][n - 1] : 0) + (k < n ? Binomial[k][n - 1] : 0);
// MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible
// available squares when the leading one is in 's'. Moreover the pawn with
@ -1375,20 +1412,24 @@ void Tablebases::init(const std::string& paths) {
}
// Add entries in TB tables if the corresponding ".rtbw" file exists
for (PieceType p1 = PAWN; p1 < KING; ++p1) {
for (PieceType p1 = PAWN; p1 < KING; ++p1)
{
TBTables.add({KING, p1, KING});
for (PieceType p2 = PAWN; p2 <= p1; ++p2) {
for (PieceType p2 = PAWN; p2 <= p1; ++p2)
{
TBTables.add({KING, p1, p2, KING});
TBTables.add({KING, p1, KING, p2});
for (PieceType p3 = PAWN; p3 < KING; ++p3)
TBTables.add({KING, p1, p2, KING, p3});
for (PieceType p3 = PAWN; p3 <= p2; ++p3) {
for (PieceType p3 = PAWN; p3 <= p2; ++p3)
{
TBTables.add({KING, p1, p2, p3, KING});
for (PieceType p4 = PAWN; p4 <= p3; ++p4) {
for (PieceType p4 = PAWN; p4 <= p3; ++p4)
{
TBTables.add({KING, p1, p2, p3, p4, KING});
for (PieceType p5 = PAWN; p5 <= p4; ++p5)
@ -1398,7 +1439,8 @@ void Tablebases::init(const std::string& paths) {
TBTables.add({KING, p1, p2, p3, p4, KING, p5});
}
for (PieceType p4 = PAWN; p4 < KING; ++p4) {
for (PieceType p4 = PAWN; p4 < KING; ++p4)
{
TBTables.add({KING, p1, p2, p3, KING, p4});
for (PieceType p5 = PAWN; p5 <= p4; ++p5)
@ -1491,8 +1533,7 @@ int Tablebases::probe_dtz(Position& pos, ProbeState* result) {
// otherwise we will get the dtz of the next move sequence. Search the
// position after the move to get the score sign (because even in a
// winning position we could make a losing capture or go for a draw).
dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result))
: -probe_dtz(pos, result);
dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result)) : -probe_dtz(pos, result);
// If the move mates, force minDTZ to 1
if (dtz == 1 && pos.checkers() && MoveList<LEGAL>(pos).size() == 0)
@ -1557,14 +1598,11 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
{
// Otherwise, take dtz for the new position and correct by 1 ply
dtz = -probe_dtz(pos, &result);
dtz = dtz > 0 ? dtz + 1
: dtz < 0 ? dtz - 1 : dtz;
dtz = dtz > 0 ? dtz + 1 : dtz < 0 ? dtz - 1 : dtz;
}
// Make sure that a mating move is assigned a dtz value of 1
if ( pos.checkers()
&& dtz == 2
&& MoveList<LEGAL>(pos).size() == 0)
if (pos.checkers() && dtz == 2 && MoveList<LEGAL>(pos).size() == 0)
dtz = 1;
pos.undo_move(m.pv[0]);
@ -1583,7 +1621,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
// 1 cp to cursed wins and let it grow to 49 cp as the positions gets
// closer to a real win.
m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 1
: r > 0 ? Value((std::max( 3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200)
: r > 0 ? Value((std::max(3, r - (MAX_DTZ - 200)) * int(PawnValue)) / 200)
: r == 0 ? VALUE_DRAW
: r > -bound ? Value((std::min(-3, r + (MAX_DTZ - 200)) * int(PawnValue)) / 200)
: -VALUE_MATE + MAX_PLY + 1;
@ -1599,7 +1637,7 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves) {
// A return value false indicates that not all probes were successful.
bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
static const int WDL_to_rank[] = { -MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ };
static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ};
ProbeState result = OK;
StateInfo st;
@ -1625,8 +1663,7 @@ bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves) {
m.tbRank = WDL_to_rank[wdl + 2];
if (!rule50)
wdl = wdl > WDLDraw ? WDLWin
: wdl < WDLDraw ? WDLLoss : WDLDraw;
wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw;
m.tbScore = WDL_to_value[wdl + 2];
}

View file

@ -43,7 +43,9 @@ ThreadPool Threads; // Global object
// Thread constructor launches the thread and waits until it goes to sleep
// in idle_loop(). Note that 'searching' and 'exit' should be already set.
Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) {
Thread::Thread(size_t n) :
idx(n),
stdThread(&Thread::idle_loop, this) {
wait_for_search_finished();
}
@ -70,8 +72,8 @@ void Thread::clear() {
mainHistory.fill(0);
captureHistory.fill(0);
for (bool inCheck : { false, true })
for (StatsType c : { NoCaptures, Captures })
for (bool inCheck : {false, true})
for (StatsType c : {NoCaptures, Captures})
for (auto& to : continuationHistory[inCheck][c])
for (auto& h : to)
h->fill(-71);
@ -94,7 +96,7 @@ void Thread::start_searching() {
void Thread::wait_for_search_finished() {
std::unique_lock<std::mutex> lk(mutex);
cv.wait(lk, [&]{ return !searching; });
cv.wait(lk, [&] { return !searching; });
}
@ -116,7 +118,7 @@ void Thread::idle_loop() {
std::unique_lock<std::mutex> lk(mutex);
searching = false;
cv.notify_one(); // Wake up anyone waiting for search finished
cv.wait(lk, [&]{ return searching; });
cv.wait(lk, [&] { return searching; });
if (exit)
return;
@ -175,8 +177,10 @@ void ThreadPool::clear() {
// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and
// returns immediately. Main thread will wake up other threads and start the search.
void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
const Search::LimitsType& limits, bool ponderMode) {
void ThreadPool::start_thinking(Position& pos,
StateListPtr& states,
const Search::LimitsType& limits,
bool ponderMode) {
main()->wait_for_search_finished();
@ -187,7 +191,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states,
Search::RootMoves rootMoves;
for (const auto& m : MoveList<LEGAL>(pos))
if ( limits.searchmoves.empty()
if (limits.searchmoves.empty()
|| std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m))
rootMoves.emplace_back(m);
@ -226,7 +230,7 @@ Thread* ThreadPool::get_best_thread() const {
Value minScore = VALUE_NONE;
// Find the minimum score of all threads
for (Thread* th: threads)
for (Thread* th : threads)
minScore = std::min(minScore, th->rootMoves[0].score);
// Vote according to score and depth, and select the best thread
@ -244,12 +248,13 @@ Thread* ThreadPool::get_best_thread() const {
if (th->rootMoves[0].score > bestThread->rootMoves[0].score)
bestThread = th;
}
else if ( th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|| ( th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
&& ( votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]
|| ( votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]]
else if (th->rootMoves[0].score >= VALUE_TB_WIN_IN_MAX_PLY
|| (th->rootMoves[0].score > VALUE_TB_LOSS_IN_MAX_PLY
&& (votes[th->rootMoves[0].pv[0]] > votes[bestThread->rootMoves[0].pv[0]]
|| (votes[th->rootMoves[0].pv[0]] == votes[bestThread->rootMoves[0].pv[0]]
&& thread_value(th) * int(th->rootMoves[0].pv.size() > 2)
> thread_value(bestThread) * int(bestThread->rootMoves[0].pv.size() > 2)))))
> thread_value(bestThread)
* int(bestThread->rootMoves[0].pv.size() > 2)))))
bestThread = th;
return bestThread;

View file

@ -47,7 +47,7 @@ class Thread {
bool exit = false, searching = true; // Set before starting std::thread
NativeThread stdThread;
public:
public:
explicit Thread(size_t);
virtual ~Thread();
virtual void search();
@ -77,7 +77,7 @@ public:
// MainThread is a derived class specific for main thread
struct MainThread : public Thread {
struct MainThread: public Thread {
using Thread::Thread;
@ -120,11 +120,11 @@ struct ThreadPool {
auto size() const noexcept { return threads.size(); }
auto empty() const noexcept { return threads.empty(); }
private:
private:
StateListPtr setupStates;
std::vector<Thread*> threads;
uint64_t accumulate(std::atomic<uint64_t> Thread::* member) const {
uint64_t accumulate(std::atomic<uint64_t> Thread::*member) const {
uint64_t sum = 0;
for (Thread* th : threads)

View file

@ -29,15 +29,14 @@
#if defined(__APPLE__) || defined(__MINGW32__) || defined(__MINGW64__) || defined(USE_PTHREADS)
#include <pthread.h>
#include <pthread.h>
namespace Stockfish {
static const size_t TH_STACK_SIZE = 8 * 1024 * 1024;
template <class T, class P = std::pair<T*, void(T::*)()>>
void* start_routine(void* ptr)
{
template<class T, class P = std::pair<T*, void (T::*)()>>
void* start_routine(void* ptr) {
P* p = reinterpret_cast<P*>(ptr);
(p->first->*(p->second))(); // Call member function pointer
delete p;
@ -48,9 +47,9 @@ class NativeThread {
pthread_t thread;
public:
template<class T, class P = std::pair<T*, void(T::*)()>>
explicit NativeThread(void(T::*fun)(), T* obj) {
public:
template<class T, class P = std::pair<T*, void (T::*)()>>
explicit NativeThread(void (T::*fun)(), T* obj) {
pthread_attr_t attr_storage, *attr = &attr_storage;
pthread_attr_init(attr);
pthread_attr_setstacksize(attr, TH_STACK_SIZE);

View file

@ -69,8 +69,8 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
int mtg = limits.movestogo ? std::min(limits.movestogo, 50) : 50;
// Make sure timeLeft is > 0 since we may use it as a divisor
TimePoint timeLeft = std::max(TimePoint(1),
limits.time[us] + limits.inc[us] * (mtg - 1) - moveOverhead * (2 + mtg));
TimePoint timeLeft = std::max(TimePoint(1), limits.time[us] + limits.inc[us] * (mtg - 1)
- moveOverhead * (2 + mtg));
// Use extra time with larger increments
double optExtra = std::clamp(1.0 + 12.0 * limits.inc[us] / limits.time[us], 1.0, 1.12);
@ -93,14 +93,14 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) {
// x moves in y seconds (+ z increment)
else
{
optScale = std::min((0.88 + ply / 116.4) / mtg,
0.88 * limits.time[us] / double(timeLeft));
optScale = std::min((0.88 + ply / 116.4) / mtg, 0.88 * limits.time[us] / double(timeLeft));
maxScale = std::min(6.3, 1.5 + 0.11 * mtg);
}
// Never use more than 80% of the available time for this move
optimumTime = TimePoint(optScale * timeLeft);
maximumTime = TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
maximumTime =
TimePoint(std::min(0.8 * limits.time[us] - moveOverhead, maxScale * optimumTime)) - 10;
if (Options["Ponder"])
optimumTime += optimumTime / 4;

View file

@ -32,16 +32,17 @@ namespace Stockfish {
// the maximum available time, the game move number, and other parameters.
class TimeManagement {
public:
public:
void init(Search::LimitsType& limits, Color us, int ply);
TimePoint optimum() const { return optimumTime; }
TimePoint maximum() const { return maximumTime; }
TimePoint elapsed() const { return Search::Limits.npmsec ?
TimePoint(Threads.nodes_searched()) : now() - startTime; }
TimePoint elapsed() const {
return Search::Limits.npmsec ? TimePoint(Threads.nodes_searched()) : now() - startTime;
}
int64_t availableNodes; // When in 'nodes as time' mode
private:
private:
TimePoint startTime;
TimePoint optimumTime;
TimePoint maximumTime;

View file

@ -43,9 +43,7 @@ void TTEntry::save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev)
move16 = uint16_t(m);
// Overwrite less valuable entries (cheapest checks first)
if ( b == BOUND_EXACT
|| uint16_t(k) != key16
|| d - DEPTH_OFFSET + 2 * pv > depth8 - 4)
if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_OFFSET + 2 * pv > depth8 - 4)
{
assert(d > DEPTH_OFFSET);
assert(d < 256 + DEPTH_OFFSET);
@ -74,8 +72,7 @@ void TranspositionTable::resize(size_t mbSize) {
table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));
if (!table)
{
std::cerr << "Failed to allocate " << mbSize
<< "MB for transposition table." << std::endl;
std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl;
exit(EXIT_FAILURE);
}
@ -93,7 +90,6 @@ void TranspositionTable::clear() {
for (size_t idx = 0; idx < size_t(Options["Threads"]); ++idx)
{
threads.emplace_back([this, idx]() {
// Thread binding gives faster search on systems with a first-touch policy
if (Options["Threads"] > 8)
WinProcGroup::bindThisThread(idx);
@ -101,8 +97,8 @@ void TranspositionTable::clear() {
// Each thread will zero its part of the hash table
const size_t stride = size_t(clusterCount / Options["Threads"]),
start = size_t(stride * idx),
len = idx != size_t(Options["Threads"]) - 1 ?
stride : clusterCount - start;
len =
idx != size_t(Options["Threads"]) - 1 ? stride : clusterCount - start;
std::memset(&table[start], 0, len * sizeof(Cluster));
});
@ -128,7 +124,8 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
for (int i = 0; i < ClusterSize; ++i)
if (tte[i].key16 == key16 || !tte[i].depth8)
{
tte[i].genBound8 = uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh
tte[i].genBound8 =
uint8_t(generation8 | (tte[i].genBound8 & (GENERATION_DELTA - 1))); // Refresh
return found = bool(tte[i].depth8), &tte[i];
}
@ -141,8 +138,10 @@ TTEntry* TranspositionTable::probe(const Key key, bool& found) const {
// is needed to keep the unrelated lowest n bits from affecting
// the result) to calculate the entry age correctly even after
// generation8 overflows into the next cycle.
if ( replace->depth8 - ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK)
> tte[i].depth8 - ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK))
if (replace->depth8
- ((GENERATION_CYCLE + generation8 - replace->genBound8) & GENERATION_MASK)
> tte[i].depth8
- ((GENERATION_CYCLE + generation8 - tte[i].genBound8) & GENERATION_MASK))
replace = &tte[i];
return found = false, replace;
@ -157,7 +156,8 @@ int TranspositionTable::hashfull() const {
int cnt = 0;
for (int i = 0; i < 1000; ++i)
for (int j = 0; j < ClusterSize; ++j)
cnt += table[i].entry[j].depth8 && (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
cnt += table[i].entry[j].depth8
&& (table[i].entry[j].genBound8 & GENERATION_MASK) == generation8;
return cnt / ClusterSize;
}

View file

@ -40,15 +40,15 @@ namespace Stockfish {
struct TTEntry {
Move move() const { return Move (move16); }
Move move() const { return Move(move16); }
Value value() const { return Value(value16); }
Value eval() const { return Value(eval16); }
Depth depth() const { return Depth(depth8 + DEPTH_OFFSET); }
bool is_pv() const { return bool (genBound8 & 0x4); }
bool is_pv() const { return bool(genBound8 & 0x4); }
Bound bound() const { return Bound(genBound8 & 0x3); }
void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev);
private:
private:
friend class TranspositionTable;
uint16_t key16;
@ -79,11 +79,13 @@ class TranspositionTable {
// Constants used to refresh the hash table periodically
static constexpr unsigned GENERATION_BITS = 3; // nb of bits reserved for other things
static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS); // increment for generation field
static constexpr int GENERATION_DELTA =
(1 << GENERATION_BITS); // increment for generation field
static constexpr int GENERATION_CYCLE = 255 + (1 << GENERATION_BITS); // cycle length
static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number
static constexpr int GENERATION_MASK =
(0xFF << GENERATION_BITS) & 0xFF; // mask to pull out generation number
public:
public:
~TranspositionTable() { aligned_large_pages_free(table); }
void new_search() { generation8 += GENERATION_DELTA; } // Lower bits are used for other things
TTEntry* probe(const Key key, bool& found) const;
@ -95,7 +97,7 @@ public:
return &table[mul_hi64(key, clusterCount)].entry[0];
}
private:
private:
friend struct TTEntry;
size_t clusterCount;

View file

@ -42,7 +42,8 @@ string Tune::next(string& names, bool pop) {
string name;
do {
do
{
string token = names.substr(0, names.find(','));
if (pop)
@ -51,8 +52,7 @@ string Tune::next(string& names, bool pop) {
std::stringstream ws(token);
name += (ws >> token, token); // Remove trailing whitespace
} while ( std::count(name.begin(), name.end(), '(')
- std::count(name.begin(), name.end(), ')'));
} while (std::count(name.begin(), name.end(), '(') - std::count(name.begin(), name.end(), ')'));
return name;
}
@ -76,31 +76,40 @@ static void make_option(const string& n, int v, const SetRange& r) {
LastOption = &Options[n];
// Print formatted parameters, ready to be copy-pasted in Fishtest
std::cout << n << ","
<< v << ","
<< r(v).first << "," << r(v).second << ","
std::cout << n << "," << v << "," << r(v).first << "," << r(v).second << ","
<< (r(v).second - r(v).first) / 20.0 << ","
<< "0.0020"
<< std::endl;
<< "0.0020" << std::endl;
}
template<> void Tune::Entry<int>::init_option() { make_option(name, value, range); }
template<>
void Tune::Entry<int>::init_option() {
make_option(name, value, range);
}
template<> void Tune::Entry<int>::read_option() {
template<>
void Tune::Entry<int>::read_option() {
if (Options.count(name))
value = int(Options[name]);
}
template<> void Tune::Entry<Value>::init_option() { make_option(name, value, range); }
template<>
void Tune::Entry<Value>::init_option() {
make_option(name, value, range);
}
template<> void Tune::Entry<Value>::read_option() {
template<>
void Tune::Entry<Value>::read_option() {
if (Options.count(name))
value = Value(int(Options[name]));
}
// Instead of a variable here we have a PostUpdate function: just call it
template<> void Tune::Entry<Tune::PostUpdate>::init_option() {}
template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
template<>
void Tune::Entry<Tune::PostUpdate>::init_option() {}
template<>
void Tune::Entry<Tune::PostUpdate>::read_option() {
value();
}
} // namespace Stockfish
@ -117,9 +126,7 @@ template<> void Tune::Entry<Tune::PostUpdate>::read_option() { value(); }
namespace Stockfish {
void Tune::read_results() {
/* ...insert your values here... */
void Tune::read_results() { /* ...insert your values here... */
}
} // namespace Stockfish

View file

@ -30,16 +30,17 @@ namespace Stockfish {
enum Value : int;
using Range = std::pair<int, int>; // Option's min-max values
using RangeFun = Range (int);
using RangeFun = Range(int);
// Default Range function, to calculate Option's min-max values
inline Range default_range(int v) {
return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0);
}
inline Range default_range(int v) { return v > 0 ? Range(0, 2 * v) : Range(2 * v, 0); }
struct SetRange {
explicit SetRange(RangeFun f) : fun(f) {}
SetRange(int min, int max) : fun(nullptr), range(min, max) {}
explicit SetRange(RangeFun f) :
fun(f) {}
SetRange(int min, int max) :
fun(nullptr),
range(min, max) {}
Range operator()(int v) const { return fun ? fun(v) : range; }
RangeFun* fun;
@ -76,14 +77,17 @@ struct SetRange {
class Tune {
using PostUpdate = void (); // Post-update function
using PostUpdate = void(); // Post-update function
Tune() { read_results(); }
Tune(const Tune&) = delete;
void operator=(const Tune&) = delete;
void read_results();
static Tune& instance() { static Tune t; return t; } // Singleton
static Tune& instance() {
static Tune t;
return t;
} // Singleton
// Use polymorphism to accommodate Entry of different types in the same vector
struct EntryBase {
@ -93,15 +97,18 @@ class Tune {
};
template<typename T>
struct Entry : public EntryBase {
struct Entry: public EntryBase {
static_assert(!std::is_const_v<T>, "Parameter cannot be const!");
static_assert( std::is_same_v<T, int>
|| std::is_same_v<T, Value>
|| std::is_same_v<T, PostUpdate>, "Parameter type not supported!");
static_assert(std::is_same_v<T, int> || std::is_same_v<T, Value>
|| std::is_same_v<T, PostUpdate>,
"Parameter type not supported!");
Entry(const std::string& n, T& v, const SetRange& r) : name(n), value(v), range(r) {}
Entry(const std::string& n, T& v, const SetRange& r) :
name(n),
value(v),
range(r) {}
void operator=(const Entry&) = delete; // Because 'value' is a reference
void init_option() override;
void read_option() override;
@ -140,19 +147,27 @@ class Tune {
std::vector<std::unique_ptr<EntryBase>> list;
public:
public:
template<typename... Args>
static int add(const std::string& names, Args&&... args) {
return instance().add(SetDefaultRange, names.substr(1, names.size() - 2), args...); // Remove trailing parenthesis
return instance().add(SetDefaultRange, names.substr(1, names.size() - 2),
args...); // Remove trailing parenthesis
}
static void init() {
for (auto& e : instance().list)
e->init_option();
read_options();
} // Deferred, due to UCI::Options access
static void read_options() {
for (auto& e : instance().list)
e->read_option();
}
static void init() { for (auto& e : instance().list) e->init_option(); read_options(); } // Deferred, due to UCI::Options access
static void read_options() { for (auto& e : instance().list) e->read_option(); }
static bool update_on_last;
};
// Some macro magic :-) we define a dummy int variable that the compiler initializes calling Tune::add()
#define STRINGIFY(x) #x
#define UNIQUE2(x, y) x ## y
#define UNIQUE2(x, y) x##y
#define UNIQUE(x, y) UNIQUE2(x, y) // Two indirection levels to expand __LINE__
#define TUNE(...) int UNIQUE(p, __LINE__) = Tune::add(STRINGIFY((__VA_ARGS__)), __VA_ARGS__)

View file

@ -17,7 +17,7 @@
*/
#ifndef TYPES_H_INCLUDED
#define TYPES_H_INCLUDED
#define TYPES_H_INCLUDED
// When compiling with provided Makefile (e.g. for Linux and OSX), configuration
// is done automatically. To get started type 'make help'.
@ -36,15 +36,15 @@
// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works
// | only in 64-bit mode and requires hardware with pext support.
#include <cassert>
#include <cstdint>
#include <cassert>
#include <cstdint>
#if defined(_MSC_VER)
// Disable some silly and noisy warnings from MSVC compiler
#pragma warning(disable: 4127) // Conditional expression is constant
#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type
#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false'
#endif
#if defined(_MSC_VER)
// Disable some silly and noisy warnings from MSVC compiler
#pragma warning(disable: 4127) // Conditional expression is constant
#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type
#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false'
#endif
// Predefined macros hell:
//
@ -55,51 +55,52 @@
// _WIN32 Building on Windows (any)
// _WIN64 Building on Windows 64 bit
#if defined(__GNUC__ ) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) && defined(_WIN32) && !defined(__clang__)
#define ALIGNAS_ON_STACK_VARIABLES_BROKEN
#endif
#if defined(__GNUC__) && (__GNUC__ < 9 || (__GNUC__ == 9 && __GNUC_MINOR__ <= 2)) \
&& defined(_WIN32) && !defined(__clang__)
#define ALIGNAS_ON_STACK_VARIABLES_BROKEN
#endif
#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
#define ASSERT_ALIGNED(ptr, alignment) assert(reinterpret_cast<uintptr_t>(ptr) % alignment == 0)
#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
# include <intrin.h> // Microsoft header for _BitScanForward64()
# define IS_64BIT
#endif
#if defined(_WIN64) && defined(_MSC_VER) // No Makefile used
#include <intrin.h> // Microsoft header for _BitScanForward64()
#define IS_64BIT
#endif
#if defined(USE_POPCNT) && defined(_MSC_VER)
# include <nmmintrin.h> // Microsoft header for _mm_popcnt_u64()
#endif
#if defined(USE_POPCNT) && defined(_MSC_VER)
#include <nmmintrin.h> // Microsoft header for _mm_popcnt_u64()
#endif
#if !defined(NO_PREFETCH) && defined(_MSC_VER)
# include <xmmintrin.h> // Microsoft header for _mm_prefetch()
#endif
#if !defined(NO_PREFETCH) && defined(_MSC_VER)
#include <xmmintrin.h> // Microsoft header for _mm_prefetch()
#endif
#if defined(USE_PEXT)
# include <immintrin.h> // Header for _pext_u64() intrinsic
# define pext(b, m) _pext_u64(b, m)
#else
# define pext(b, m) 0
#endif
#if defined(USE_PEXT)
#include <immintrin.h> // Header for _pext_u64() intrinsic
#define pext(b, m) _pext_u64(b, m)
#else
#define pext(b, m) 0
#endif
namespace Stockfish {
#ifdef USE_POPCNT
#ifdef USE_POPCNT
constexpr bool HasPopCnt = true;
#else
#else
constexpr bool HasPopCnt = false;
#endif
#endif
#ifdef USE_PEXT
#ifdef USE_PEXT
constexpr bool HasPext = true;
#else
#else
constexpr bool HasPext = false;
#endif
#endif
#ifdef IS_64BIT
#ifdef IS_64BIT
constexpr bool Is64Bit = true;
#else
#else
constexpr bool Is64Bit = false;
#endif
#endif
using Key = uint64_t;
using Bitboard = uint64_t;
@ -132,7 +133,9 @@ enum MoveType {
};
enum Color {
WHITE, BLACK, COLOR_NB = 2
WHITE,
BLACK,
COLOR_NB = 2
};
enum CastlingRights {
@ -180,6 +183,7 @@ enum Value : int {
QueenValue = 2538,
};
// clang-format off
enum PieceType {
NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING,
ALL_PIECES = 0,
@ -192,9 +196,11 @@ enum Piece {
B_PAWN = PAWN + 8, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING,
PIECE_NB = 16
};
// clang-format on
constexpr Value PieceValue[PIECE_NB] = { VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO,
VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO };
constexpr Value PieceValue[PIECE_NB] = {
VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO,
VALUE_ZERO, PawnValue, KnightValue, BishopValue, RookValue, QueenValue, VALUE_ZERO, VALUE_ZERO};
using Depth = int;
@ -208,6 +214,7 @@ enum : int {
DEPTH_OFFSET = -7 // value used only for TT entry occupancy check
};
// clang-format off
enum Square : int {
SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1,
SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2,
@ -222,6 +229,7 @@ enum Square : int {
SQUARE_ZERO = 0,
SQUARE_NB = 64
};
// clang-format on
enum Direction : int {
NORTH = 8,
@ -236,11 +244,27 @@ enum Direction : int {
};
enum File : int {
FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB
FILE_A,
FILE_B,
FILE_C,
FILE_D,
FILE_E,
FILE_F,
FILE_G,
FILE_H,
FILE_NB
};
enum Rank : int {
RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB
RANK_1,
RANK_2,
RANK_3,
RANK_4,
RANK_5,
RANK_6,
RANK_7,
RANK_8,
RANK_NB
};
// Keep track of what a move changes on the board (used by NNUE)
@ -259,25 +283,25 @@ struct DirtyPiece {
Square to[3];
};
#define ENABLE_BASE_OPERATORS_ON(T) \
constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \
constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \
constexpr T operator-(T d) { return T(-int(d)); } \
inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \
inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; }
#define ENABLE_BASE_OPERATORS_ON(T) \
constexpr T operator+(T d1, int d2) { return T(int(d1) + d2); } \
constexpr T operator-(T d1, int d2) { return T(int(d1) - d2); } \
constexpr T operator-(T d) { return T(-int(d)); } \
inline T& operator+=(T& d1, int d2) { return d1 = d1 + d2; } \
inline T& operator-=(T& d1, int d2) { return d1 = d1 - d2; }
#define ENABLE_INCR_OPERATORS_ON(T) \
inline T& operator++(T& d) { return d = T(int(d) + 1); } \
inline T& operator--(T& d) { return d = T(int(d) - 1); }
#define ENABLE_INCR_OPERATORS_ON(T) \
inline T& operator++(T& d) { return d = T(int(d) + 1); } \
inline T& operator--(T& d) { return d = T(int(d) - 1); }
#define ENABLE_FULL_OPERATORS_ON(T) \
ENABLE_BASE_OPERATORS_ON(T) \
constexpr T operator*(int i, T d) { return T(i * int(d)); } \
constexpr T operator*(T d, int i) { return T(int(d) * i); } \
constexpr T operator/(T d, int i) { return T(int(d) / i); } \
constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \
inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \
inline T& operator/=(T& d, int i) { return d = T(int(d) / i); }
#define ENABLE_FULL_OPERATORS_ON(T) \
ENABLE_BASE_OPERATORS_ON(T) \
constexpr T operator*(int i, T d) { return T(i * int(d)); } \
constexpr T operator*(T d, int i) { return T(int(d) * i); } \
constexpr T operator/(T d, int i) { return T(int(d) / i); } \
constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \
inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \
inline T& operator/=(T& d, int i) { return d = T(int(d) / i); }
ENABLE_FULL_OPERATORS_ON(Value)
ENABLE_FULL_OPERATORS_ON(Direction)
@ -287,9 +311,9 @@ ENABLE_INCR_OPERATORS_ON(Square)
ENABLE_INCR_OPERATORS_ON(File)
ENABLE_INCR_OPERATORS_ON(Rank)
#undef ENABLE_FULL_OPERATORS_ON
#undef ENABLE_INCR_OPERATORS_ON
#undef ENABLE_BASE_OPERATORS_ON
#undef ENABLE_FULL_OPERATORS_ON
#undef ENABLE_INCR_OPERATORS_ON
#undef ENABLE_BASE_OPERATORS_ON
// Additional operators to add a Direction to a Square
constexpr Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); }
@ -317,62 +341,36 @@ 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 mate_in(int ply) { return VALUE_MATE - ply; }
constexpr Value mated_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 Square make_square(File f, Rank r) { return Square((r << 3) + f); }
constexpr Piece make_piece(Color c, PieceType pt) {
return Piece((c << 3) + pt);
}
constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); }
constexpr PieceType type_of(Piece pc) {
return PieceType(pc & 7);
}
constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); }
inline Color color_of(Piece pc) {
assert(pc != NO_PIECE);
return Color(pc >> 3);
}
constexpr bool is_ok(Move m) {
return m != MOVE_NONE && m != MOVE_NULL;
}
constexpr bool is_ok(Move m) { return m != MOVE_NONE && m != MOVE_NULL; }
constexpr bool is_ok(Square s) {
return s >= SQ_A1 && s <= SQ_H8;
}
constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; }
constexpr File file_of(Square s) {
return File(s & 7);
}
constexpr File file_of(Square s) { return File(s & 7); }
constexpr Rank rank_of(Square s) {
return Rank(s >> 3);
}
constexpr Rank rank_of(Square s) { return Rank(s >> 3); }
constexpr Square relative_square(Color c, Square s) {
return Square(s ^ (c * 56));
}
constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); }
constexpr Rank relative_rank(Color c, Rank r) {
return Rank(r ^ (c * 7));
}
constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); }
constexpr Rank relative_rank(Color c, Square s) {
return relative_rank(c, rank_of(s));
}
constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); }
constexpr Direction pawn_push(Color c) {
return c == WHITE ? NORTH : SOUTH;
}
constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; }
constexpr Square from_sq(Move m) {
assert(is_ok(m));
@ -384,21 +382,13 @@ constexpr Square to_sq(Move m) {
return Square(m & 0x3F);
}
constexpr int from_to(Move m) {
return m & 0xFFF;
}
constexpr int from_to(Move m) { return m & 0xFFF; }
constexpr MoveType type_of(Move m) {
return MoveType(m & (3 << 14));
}
constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); }
constexpr PieceType promotion_type(Move m) {
return PieceType(((m >> 12) & 3) + KNIGHT);
}
constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); }
constexpr Move make_move(Square from, Square to) {
return Move((from << 6) + to);
}
constexpr Move make_move(Square from, Square to) { return Move((from << 6) + to); }
template<MoveType T>
constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) {

View file

@ -45,16 +45,16 @@ namespace Stockfish {
namespace {
// FEN string for the initial position in standard chess
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
// FEN string for the initial position in standard chess
const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
// position() is called when the engine receives the "position" UCI command.
// It sets up the position that is described in the given FEN string ("fen") or
// the initial position ("startpos") and then makes the moves given in the following
// move list ("moves").
// position() is called when the engine receives the "position" UCI command.
// It sets up the position that is described in the given FEN string ("fen") or
// the initial position ("startpos") and then makes the moves given in the following
// move list ("moves").
void position(Position& pos, std::istringstream& is, StateListPtr& states) {
void position(Position& pos, std::istringstream& is, StateListPtr& states) {
Move m;
std::string token, fen;
@ -81,12 +81,12 @@ namespace {
states->emplace_back();
pos.do_move(m, states->back());
}
}
}
// trace_eval() prints the evaluation of the current position, consistent with
// the UCI options set so far.
// trace_eval() prints the evaluation of the current position, consistent with
// the UCI options set so far.
void trace_eval(Position& pos) {
void trace_eval(Position& pos) {
StateListPtr states(new std::deque<StateInfo>(1));
Position p;
@ -95,13 +95,13 @@ namespace {
Eval::NNUE::verify();
sync_cout << "\n" << Eval::trace(p) << sync_endl;
}
}
// setoption() is called when the engine receives the "setoption" UCI command.
// The function updates the UCI option ("name") to the given value ("value").
// setoption() is called when the engine receives the "setoption" UCI command.
// The function updates the UCI option ("name") to the given value ("value").
void setoption(std::istringstream& is) {
void setoption(std::istringstream& is) {
Threads.main()->wait_for_search_finished();
@ -121,14 +121,14 @@ namespace {
Options[name] = value;
else
sync_cout << "No such option: " << name << sync_endl;
}
}
// go() is called when the engine receives the "go" UCI command. The function
// sets the thinking time and other parameters from the input string, then starts
// with a search.
// go() is called when the engine receives the "go" UCI command. The function
// sets the thinking time and other parameters from the input string, then starts
// with a search.
void go(Position& pos, std::istringstream& is, StateListPtr& states) {
void go(Position& pos, std::istringstream& is, StateListPtr& states) {
Search::LimitsType limits;
std::string token;
@ -141,34 +141,47 @@ namespace {
while (is >> token)
limits.searchmoves.push_back(UCI::to_move(pos, token));
else if (token == "wtime") is >> limits.time[WHITE];
else if (token == "btime") is >> limits.time[BLACK];
else if (token == "winc") is >> limits.inc[WHITE];
else if (token == "binc") is >> limits.inc[BLACK];
else if (token == "movestogo") is >> limits.movestogo;
else if (token == "depth") is >> limits.depth;
else if (token == "nodes") is >> limits.nodes;
else if (token == "movetime") is >> limits.movetime;
else if (token == "mate") is >> limits.mate;
else if (token == "perft") is >> limits.perft;
else if (token == "infinite") limits.infinite = 1;
else if (token == "ponder") ponderMode = true;
else if (token == "wtime")
is >> limits.time[WHITE];
else if (token == "btime")
is >> limits.time[BLACK];
else if (token == "winc")
is >> limits.inc[WHITE];
else if (token == "binc")
is >> limits.inc[BLACK];
else if (token == "movestogo")
is >> limits.movestogo;
else if (token == "depth")
is >> limits.depth;
else if (token == "nodes")
is >> limits.nodes;
else if (token == "movetime")
is >> limits.movetime;
else if (token == "mate")
is >> limits.mate;
else if (token == "perft")
is >> limits.perft;
else if (token == "infinite")
limits.infinite = 1;
else if (token == "ponder")
ponderMode = true;
Threads.start_thinking(pos, states, limits, ponderMode);
}
}
// bench() is called when the engine receives the "bench" command.
// First, a list of UCI commands is set up according to the bench
// parameters, then it is run one by one, printing a summary at the end.
// bench() is called when the engine receives the "bench" command.
// First, a list of UCI commands is set up according to the bench
// parameters, then it is run one by one, printing a summary at the end.
void bench(Position& pos, std::istream& args, StateListPtr& states) {
void bench(Position& pos, std::istream& args, StateListPtr& states) {
std::string token;
uint64_t num, nodes = 0, cnt = 1;
std::vector<std::string> list = setup_bench(pos, args);
num = count_if(list.begin(), list.end(), [](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; });
num = count_if(list.begin(), list.end(),
[](const std::string& s) { return s.find("go ") == 0 || s.find("eval") == 0; });
TimePoint elapsed = now();
@ -179,7 +192,8 @@ namespace {
if (token == "go" || token == "eval")
{
std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << std::endl;
std::cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")"
<< std::endl;
if (token == "go")
{
go(pos, is, states);
@ -189,9 +203,15 @@ namespace {
else
trace_eval(pos);
}
else if (token == "setoption") setoption(is);
else if (token == "position") position(pos, is, states);
else if (token == "ucinewgame") { Search::clear(); elapsed = now(); } // Search::clear() may take a while
else if (token == "setoption")
setoption(is);
else if (token == "position")
position(pos, is, states);
else if (token == "ucinewgame")
{
Search::clear();
elapsed = now();
} // Search::clear() may take a while
}
elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero'
@ -199,14 +219,13 @@ namespace {
dbg_print();
std::cerr << "\n==========================="
<< "\nTotal time (ms) : " << elapsed
<< "\nNodes searched : " << nodes
<< "\nTotal time (ms) : " << elapsed << "\nNodes searched : " << nodes
<< "\nNodes/second : " << 1000 * nodes / elapsed << std::endl;
}
}
// The win rate model returns the probability of winning (in per mille units) given an
// eval and a game ply. It fits the LTC fishtest statistics rather accurately.
int win_rate_model(Value v, int ply) {
// The win rate model returns the probability of winning (in per mille units) given an
// eval and a game ply. It fits the LTC fishtest statistics rather accurately.
int win_rate_model(Value v, int ply) {
// The model only captures up to 240 plies, so limit the input and then rescale
double m = std::min(240, ply) / 64.0;
@ -214,8 +233,8 @@ namespace {
// The coefficients of a third-order polynomial fit is based on the fishtest data
// for two parameters that need to transform eval to the argument of a logistic
// function.
constexpr double as[] = { 0.38036525, -2.82015070, 23.17882135, 307.36768407};
constexpr double bs[] = { -2.29434733, 13.27689788, -14.26828904, 63.45318330 };
constexpr double as[] = {0.38036525, -2.82015070, 23.17882135, 307.36768407};
constexpr double bs[] = {-2.29434733, 13.27689788, -14.26828904, 63.45318330};
// Enforce that NormalizeToPawnValue corresponds to a 50% win rate at ply 64
static_assert(UCI::NormalizeToPawnValue == int(as[0] + as[1] + as[2] + as[3]));
@ -228,7 +247,7 @@ namespace {
// Return the win rate in per mille units, rounded to the nearest integer
return int(0.5 + 1000 / (1 + std::exp((a - x) / b)));
}
}
} // namespace
@ -250,8 +269,10 @@ void UCI::loop(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i)
cmd += std::string(argv[i]) + " ";
do {
if (argc == 1 && !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
do
{
if (argc == 1
&& !getline(std::cin, cmd)) // Wait for an input or an end-of-file (EOF) indication
cmd = "quit";
std::istringstream is(cmd);
@ -259,8 +280,7 @@ void UCI::loop(int argc, char* argv[]) {
token.clear(); // Avoid a stale if getline() returns nothing or a blank line
is >> std::skipws >> token;
if ( token == "quit"
|| token == "stop")
if (token == "quit" || token == "stop")
Threads.stop = true;
// The GUI sends 'ponderhit' to tell that the user has played the expected move.
@ -271,23 +291,32 @@ void UCI::loop(int argc, char* argv[]) {
Threads.main()->ponder = false; // Switch to the normal search
else if (token == "uci")
sync_cout << "id name " << engine_info(true)
<< "\n" << Options
<< "\nuciok" << sync_endl;
sync_cout << "id name " << engine_info(true) << "\n"
<< Options << "\nuciok" << sync_endl;
else if (token == "setoption") setoption(is);
else if (token == "go") go(pos, is, states);
else if (token == "position") position(pos, is, states);
else if (token == "ucinewgame") Search::clear();
else if (token == "isready") sync_cout << "readyok" << sync_endl;
else if (token == "setoption")
setoption(is);
else if (token == "go")
go(pos, is, states);
else if (token == "position")
position(pos, is, states);
else if (token == "ucinewgame")
Search::clear();
else if (token == "isready")
sync_cout << "readyok" << sync_endl;
// Add custom non-UCI commands, mainly for debugging purposes.
// These commands must not be used during a search!
else if (token == "flip") pos.flip();
else if (token == "bench") bench(pos, is, states);
else if (token == "d") sync_cout << pos << sync_endl;
else if (token == "eval") trace_eval(pos);
else if (token == "compiler") sync_cout << compiler_info() << sync_endl;
else if (token == "flip")
pos.flip();
else if (token == "bench")
bench(pos, is, states);
else if (token == "d")
sync_cout << pos << sync_endl;
else if (token == "eval")
trace_eval(pos);
else if (token == "compiler")
sync_cout << compiler_info() << sync_endl;
else if (token == "export_net")
{
std::optional<std::string> filename;
@ -297,14 +326,17 @@ void UCI::loop(int argc, char* argv[]) {
Eval::NNUE::save_eval(filename);
}
else if (token == "--help" || token == "help" || token == "--license" || token == "license")
sync_cout << "\nStockfish is a powerful chess engine for playing and analyzing."
sync_cout
<< "\nStockfish is a powerful chess engine for playing and analyzing."
"\nIt is released as free software licensed under the GNU GPLv3 License."
"\nStockfish is normally used with a graphical user interface (GUI) and implements"
"\nthe Universal Chess Interface (UCI) protocol to communicate with a GUI, an API, etc."
"\nFor any further information, visit https://github.com/official-stockfish/Stockfish#readme"
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n" << sync_endl;
"\nor read the corresponding README.md and Copying.txt files distributed along with this program.\n"
<< sync_endl;
else if (!token.empty() && token[0] != '#')
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." << sync_endl;
sync_cout << "Unknown command: '" << cmd << "'. Type help for more information."
<< sync_endl;
} while (token != "quit" && argc == 1); // The command-line arguments are one-shot
}
@ -312,10 +344,7 @@ void UCI::loop(int argc, char* argv[]) {
// Turns a Value to an integer centipawn number,
// without treatment of mate and similar special scores.
int UCI::to_cp(Value v) {
return 100 * v / UCI::NormalizeToPawnValue;
}
int UCI::to_cp(Value v) { return 100 * v / UCI::NormalizeToPawnValue; }
// UCI::value() converts a Value to a string by adhering to the UCI protocol specification:
//
@ -350,7 +379,7 @@ std::string UCI::wdl(Value v, int ply) {
std::stringstream ss;
int wdl_w = win_rate_model( v, ply);
int wdl_w = win_rate_model(v, ply);
int wdl_l = win_rate_model(-v, ply);
int wdl_d = 1000 - wdl_w - wdl_l;
ss << " wdl " << wdl_w << " " << wdl_d << " " << wdl_l;
@ -362,7 +391,7 @@ std::string UCI::wdl(Value v, int ply) {
// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.)
std::string UCI::square(Square s) {
return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) };
return std::string{char('a' + file_of(s)), char('1' + rank_of(s))};
}

View file

@ -43,7 +43,7 @@ class Option;
// Define a custom comparator, because the UCI options should be case-insensitive
struct CaseInsensitiveLess {
bool operator() (const std::string&, const std::string&) const;
bool operator()(const std::string&, const std::string&) const;
};
// The options container is defined as a std::map
@ -54,7 +54,7 @@ class Option {
using OnChange = void (*)(const Option&);
public:
public:
Option(OnChange = nullptr);
Option(bool v, OnChange = nullptr);
Option(const char* v, OnChange = nullptr);
@ -67,7 +67,7 @@ public:
operator std::string() const;
bool operator==(const char*) const;
private:
private:
friend std::ostream& operator<<(std::ostream&, const OptionsMap&);
std::string defaultValue, currentValue, type;

View file

@ -53,7 +53,7 @@ static void on_tb_path(const Option& o) { Tablebases::init(o); }
static void on_eval_file(const Option&) { Eval::NNUE::init(); }
// Our case insensitive less() function as required by UCI protocol
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
bool CaseInsensitiveLess::operator()(const string& s1, const string& s2) const {
return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(),
[](char c1, char c2) { return tolower(c1) < tolower(c2); });
@ -105,9 +105,8 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
os << " default " << o.defaultValue;
if (o.type == "spin")
os << " default " << int(stof(o.defaultValue))
<< " min " << o.min
<< " max " << o.max;
os << " default " << int(stof(o.defaultValue)) << " min " << o.min << " max "
<< o.max;
break;
}
@ -118,20 +117,44 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) {
// Option class constructors and conversion operators
Option::Option(const char* v, OnChange f) : type("string"), min(0), max(0), on_change(f)
{ defaultValue = currentValue = v; }
Option::Option(const char* v, OnChange f) :
type("string"),
min(0),
max(0),
on_change(f) {
defaultValue = currentValue = v;
}
Option::Option(bool v, OnChange f) : type("check"), min(0), max(0), on_change(f)
{ defaultValue = currentValue = (v ? "true" : "false"); }
Option::Option(bool v, OnChange f) :
type("check"),
min(0),
max(0),
on_change(f) {
defaultValue = currentValue = (v ? "true" : "false");
}
Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f)
{}
Option::Option(OnChange f) :
type("button"),
min(0),
max(0),
on_change(f) {}
Option::Option(double v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f)
{ defaultValue = currentValue = std::to_string(v); }
Option::Option(double v, int minv, int maxv, OnChange f) :
type("spin"),
min(minv),
max(maxv),
on_change(f) {
defaultValue = currentValue = std::to_string(v);
}
Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f)
{ defaultValue = v; currentValue = cur; }
Option::Option(const char* v, const char* cur, OnChange f) :
type("combo"),
min(0),
max(0),
on_change(f) {
defaultValue = v;
currentValue = cur;
}
Option::operator int() const {
assert(type == "check" || type == "spin");
@ -145,8 +168,7 @@ Option::operator std::string() const {
bool Option::operator==(const char* s) const {
assert(type == "combo");
return !CaseInsensitiveLess()(currentValue, s)
&& !CaseInsensitiveLess()(s, currentValue);
return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue);
}
@ -169,7 +191,7 @@ Option& Option::operator=(const string& v) {
assert(!type.empty());
if ( (type != "button" && type != "string" && v.empty())
if ((type != "button" && type != "string" && v.empty())
|| (type == "check" && v != "true" && v != "false")
|| (type == "spin" && (stof(v) < min || stof(v) > max)))
return *this;