From e17725f445e52544f749ef65107cda8ac93d0449 Mon Sep 17 00:00:00 2001 From: anematode Date: Sun, 26 Apr 2026 09:17:57 +0200 Subject: [PATCH] Constexpr attacks (pext only) LLR: 2.97 (-2.94,2.94) <0.00,2.00> Total: 38400 W: 10057 L: 9749 D: 18594 Ptnml(0-2): 93, 4110, 10498, 4394, 105 https://tests.stockfishchess.org/tests/view/69d2073261a12cebe17edc04 compile time generation leads to sharing tables through the binary, resulting in gain on fishtest, as well as reduced memory usage. The downside is slightly longer compilation time. Using pdep reduces table size 4x, and gains a little outside of fishtest as well. closes https://github.com/official-stockfish/Stockfish/pull/6706 No functional change --- src/Makefile | 10 ++-- src/bitboard.cpp | 146 +++++++++++++++++++++++++++++++---------------- src/bitboard.h | 32 +++++++---- src/types.h | 3 +- 4 files changed, 123 insertions(+), 68 deletions(-) diff --git a/src/Makefile b/src/Makefile index 9657b9e39..0d31c9f5c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -506,14 +506,14 @@ ifeq ($(COMP),mingw) CXX=i686-w64-mingw32-c++-posix endif endif - CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations + CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations -fconstexpr-ops-limit=500000000 endif ifeq ($(COMP),icx) comp=icx CXX=icpx CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \ - -Wconditional-uninitialized -Wabi -Wdeprecated + -Wconditional-uninitialized -Wabi -Wdeprecated -fconstexpr-steps=500000000 endif ifeq ($(COMP),clang) @@ -524,7 +524,7 @@ ifeq ($(COMP),clang) endif CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \ - -Wconditional-uninitialized -flax-vector-conversions=none + -Wconditional-uninitialized -flax-vector-conversions=none -fconstexpr-steps=500000000 ifeq ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),) ifeq ($(target_windows),) @@ -632,7 +632,7 @@ ifeq ($(COMP),gcc) profile_make = clang-profile-make profile_use = clang-profile-use else - CXXFLAGS += -Wstack-usage=128000 -fno-ipa-cp-clone + CXXFLAGS += -Wstack-usage=128000 -fno-ipa-cp-clone -fconstexpr-ops-limit=500000000 endif endif @@ -838,7 +838,7 @@ endif ifeq ($(pext),yes) CXXFLAGS += -DUSE_PEXT ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) - CXXFLAGS += -mbmi2 + CXXFLAGS += -mbmi2 -DUSE_COMPTIME_ATTACKS endif endif diff --git a/src/bitboard.cpp b/src/bitboard.cpp index 0861222cf..a194f97e6 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -35,13 +35,11 @@ Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; alignas(64) Magic Magics[SQUARE_NB][2]; -namespace { - -Bitboard RookTable[0x19000]; // To store rook attacks -Bitboard BishopTable[0x1480]; // To store bishop attacks - -void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]); -} +#ifdef USE_PEXT +using MagicMask = uint16_t; +#else +using MagicMask = Bitboard; +#endif // Returns an ASCII representation of a bitboard suitable // to be printed to standard output. Useful for debugging. @@ -64,45 +62,33 @@ std::string Bitboards::pretty(Bitboard b) { return s; } - -// Initializes various bitboard tables. It is called at -// startup and relies on global objects to be already zero-initialized. -void Bitboards::init() { - - for (unsigned i = 0; i < (1 << 16); ++i) - PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); - - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); - - init_magics(ROOK, RookTable, Magics); - init_magics(BISHOP, BishopTable, Magics); - - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) +namespace { +[[maybe_unused]] constexpr Bitboard constexpr_pext(Bitboard b, Bitboard m) { + Bitboard result = 0, bit = 0; + while (m) { - for (PieceType pt : {BISHOP, ROOK}) - for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - { - if (PseudoAttacks[pt][s1] & s2) - { - LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; - BetweenBB[s1][s2] = - (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); - RayPassBB[s1][s2] = - attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2); - } - BetweenBB[s1][s2] |= s2; - } + Bitboard last = m & -m; + result |= bool(b & last) << bit++; + m ^= last; } + return result; } -namespace { -// Computes all rook and bishop attacks at startup. Magic +// Computes all rook and bishop attacks at startup or optionally, compile time. Magic // bitboards are used to look up attacks of sliding pieces. As a reference see // https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use // the so called "fancy" approach. -void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { +#ifdef USE_COMPTIME_ATTACKS +constexpr +#endif + void + init_magics(PieceType pt, + MagicMask table[], + Magic magics[][2], + [[maybe_unused]] bool tableAlreadyInit) { +#if !defined(USE_COMPTIME_ATTACKS) + tableAlreadyInit = false; +#endif #ifndef USE_PEXT // Optimal PRNG seeds to pick the correct magics in the shortest time @@ -111,9 +97,9 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { Bitboard occupancy[4096]; int epoch[4096] = {}, cnt = 0; + Bitboard reference[4096] = {}; #endif - Bitboard reference[4096]; - int size = 0; + int size = 0; for (Square s = SQ_A1; s <= SQ_H8; ++s) { @@ -125,9 +111,12 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { // all the attacks for each possible subset of the mask and so is 2 power // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. - Magic& m = magics[s][pt - BISHOP]; - m.mask = Bitboards::sliding_attack(pt, s, 0) & ~edges; -#ifndef USE_PEXT + Magic& m = magics[s][pt - BISHOP]; + Bitboard attacks = Bitboards::sliding_attack(pt, s, 0); + m.mask = attacks & ~edges; +#ifdef USE_PEXT + m.pseudoAttacks = attacks; +#else m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); #endif // Set the offset for the attacks table of the square. We have individual @@ -137,16 +126,22 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { // Use Carry-Rippler trick to enumerate all subsets of masks[s] and // store the corresponding sliding attack bitboard in reference[]. - Bitboard b = 0; + Bitboard b = 0; + [[maybe_unused]] Bitboard prevSliding = -1; do { -#ifndef USE_PEXT +#ifdef USE_PEXT + if (!tableAlreadyInit) + { + Bitboard sliding = Bitboards::sliding_attack(pt, s, b); + m.attacks[size] = + sliding != prevSliding ? constexpr_pext(sliding, attacks) : m.attacks[size - 1]; + prevSliding = sliding; + } +#else occupancy[size] = b; -#endif reference[size] = Bitboards::sliding_attack(pt, s, b); - - if (HasPext) - m.attacks[pext(b, m.mask)] = reference[size]; +#endif size++; b = (b - m.mask) & m.mask; @@ -184,6 +179,57 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) { #endif } } + +#if defined(USE_COMPTIME_ATTACKS) && defined(USE_PEXT) +constexpr auto RookTable = []() { + std::array result{}; + Magic magics[64][2] = {}; + init_magics(ROOK, result.data(), magics, false); + return result; +}(); +constexpr auto BishopTable = []() { + std::array result{}; + Magic magics[64][2] = {}; + init_magics(BISHOP, result.data(), magics, false); + return result; +}(); +#else +std::array RookTable; +std::array BishopTable; +#endif +} + + +// Initializes various bitboard tables. It is called at +// startup and relies on global objects to be already zero-initialized. +void Bitboards::init() { + + for (unsigned i = 0; i < (1 << 16); ++i) + PopCnt16[i] = uint8_t(std::bitset<16>(i).count()); + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + SquareDistance[s1][s2] = std::max(distance(s1, s2), distance(s1, s2)); + + init_magics(ROOK, const_cast(RookTable.data()), Magics, true); + init_magics(BISHOP, const_cast(BishopTable.data()), Magics, true); + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + for (PieceType pt : {BISHOP, ROOK}) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { + if (PseudoAttacks[pt][s1] & s2) + { + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = + (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); + RayPassBB[s1][s2] = + attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2); + } + BetweenBB[s1][s2] |= s2; + } + } } } // namespace Stockfish diff --git a/src/bitboard.h b/src/bitboard.h index 0347c8d44..d41552767 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -76,11 +76,14 @@ extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; // Magic holds all magic bitboards relevant data for a single square struct Magic { - Bitboard mask; + Bitboard mask; +#ifdef USE_PEXT + uint16_t* attacks; + Bitboard pseudoAttacks; +#else Bitboard* attacks; -#ifndef USE_PEXT - Bitboard magic; - unsigned shift; + Bitboard magic; + unsigned shift; #endif // Compute the attack's index using the 'magic bitboards' approach @@ -98,7 +101,13 @@ struct Magic { #endif } - Bitboard attacks_bb(Bitboard occupied) const { return attacks[index(occupied)]; } + Bitboard attacks_bb(Bitboard occupied) const { +#ifdef USE_PEXT + return pdep(attacks[index(occupied)], pseudoAttacks); +#else + return attacks[index(occupied)]; +#endif + } }; extern Magic Magics[SQUARE_NB][2]; @@ -338,17 +347,18 @@ constexpr Bitboard safe_destination(Square s, int step) { } constexpr Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { - Bitboard attacks = 0; - Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; - Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; + Bitboard attacks = 0, dest = 0; + constexpr Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; + constexpr Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) { Square s = sq; - while (safe_destination(s, d)) + while ((dest = safe_destination(s, d))) { - attacks |= (s += d); - if (occupied & s) + attacks |= dest; + s += d; + if (occupied & dest) { break; } diff --git a/src/types.h b/src/types.h index c109dbb49..7da6d7c80 100644 --- a/src/types.h +++ b/src/types.h @@ -87,8 +87,7 @@ #if defined(USE_PEXT) #include // Header for _pext_u64() intrinsic #define pext(b, m) _pext_u64(b, m) - #else - #define pext(b, m) 0 + #define pdep(b, m) _pdep_u64(b, m) #endif namespace Stockfish {