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
This commit is contained in:
anematode
2026-04-26 09:18:06 +02:00
committed by Joost VandeVondele
parent ed651aab54
commit e17725f445
4 changed files with 123 additions and 68 deletions
+5 -5
View File
@@ -506,14 +506,14 @@ ifeq ($(COMP),mingw)
CXX=i686-w64-mingw32-c++-posix CXX=i686-w64-mingw32-c++-posix
endif endif
endif endif
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-declarations -fconstexpr-ops-limit=500000000
endif endif
ifeq ($(COMP),icx) ifeq ($(COMP),icx)
comp=icx comp=icx
CXX=icpx CXX=icpx
CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \ CXXFLAGS += --intel -pedantic -Wextra -Wshadow -Wmissing-prototypes \
-Wconditional-uninitialized -Wabi -Wdeprecated -Wconditional-uninitialized -Wabi -Wdeprecated -fconstexpr-steps=500000000
endif endif
ifeq ($(COMP),clang) ifeq ($(COMP),clang)
@@ -524,7 +524,7 @@ ifeq ($(COMP),clang)
endif endif
CXXFLAGS += -pedantic -Wextra -Wshadow -Wmissing-prototypes \ 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 ($(filter $(KERNEL),Darwin OpenBSD FreeBSD),)
ifeq ($(target_windows),) ifeq ($(target_windows),)
@@ -632,7 +632,7 @@ ifeq ($(COMP),gcc)
profile_make = clang-profile-make profile_make = clang-profile-make
profile_use = clang-profile-use profile_use = clang-profile-use
else else
CXXFLAGS += -Wstack-usage=128000 -fno-ipa-cp-clone CXXFLAGS += -Wstack-usage=128000 -fno-ipa-cp-clone -fconstexpr-ops-limit=500000000
endif endif
endif endif
@@ -838,7 +838,7 @@ endif
ifeq ($(pext),yes) ifeq ($(pext),yes)
CXXFLAGS += -DUSE_PEXT CXXFLAGS += -DUSE_PEXT
ifeq ($(comp),$(filter $(comp),gcc clang mingw icx)) ifeq ($(comp),$(filter $(comp),gcc clang mingw icx))
CXXFLAGS += -mbmi2 CXXFLAGS += -mbmi2 -DUSE_COMPTIME_ATTACKS
endif endif
endif endif
+96 -50
View File
@@ -35,13 +35,11 @@ Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
alignas(64) Magic Magics[SQUARE_NB][2]; alignas(64) Magic Magics[SQUARE_NB][2];
namespace { #ifdef USE_PEXT
using MagicMask = uint16_t;
Bitboard RookTable[0x19000]; // To store rook attacks #else
Bitboard BishopTable[0x1480]; // To store bishop attacks using MagicMask = Bitboard;
#endif
void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]);
}
// Returns an ASCII representation of a bitboard suitable // Returns an ASCII representation of a bitboard suitable
// to be printed to standard output. Useful for debugging. // to be printed to standard output. Useful for debugging.
@@ -64,45 +62,33 @@ std::string Bitboards::pretty(Bitboard b) {
return s; return s;
} }
namespace {
// Initializes various bitboard tables. It is called at [[maybe_unused]] constexpr Bitboard constexpr_pext(Bitboard b, Bitboard m) {
// startup and relies on global objects to be already zero-initialized. Bitboard result = 0, bit = 0;
void Bitboards::init() { while (m)
for (unsigned i = 0; i < (1 << 16); ++i)
PopCnt16[i] = uint8_t(std::bitset<16>(i).count());
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
init_magics(ROOK, RookTable, Magics);
init_magics(BISHOP, BishopTable, Magics);
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{ {
for (PieceType pt : {BISHOP, ROOK}) Bitboard last = m & -m;
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) result |= bool(b & last) << bit++;
{ m ^= last;
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;
}
} }
return result;
} }
namespace { // Computes all rook and bishop attacks at startup or optionally, compile time. Magic
// Computes all rook and bishop attacks at startup. Magic
// bitboards are used to look up attacks of sliding pieces. As a reference see // bitboards are used to look up attacks of sliding pieces. As a reference see
// https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use // https://www.chessprogramming.org/Magic_Bitboards. In particular, here we use
// the so called "fancy" approach. // 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 #ifndef USE_PEXT
// Optimal PRNG seeds to pick the correct magics in the shortest time // 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]; Bitboard occupancy[4096];
int epoch[4096] = {}, cnt = 0; int epoch[4096] = {}, cnt = 0;
Bitboard reference[4096] = {};
#endif #endif
Bitboard reference[4096]; int size = 0;
int size = 0;
for (Square s = SQ_A1; s <= SQ_H8; ++s) 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 // 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 // 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. // apply to the 64 or 32 bits word to get the index.
Magic& m = magics[s][pt - BISHOP]; Magic& m = magics[s][pt - BISHOP];
m.mask = Bitboards::sliding_attack(pt, s, 0) & ~edges; Bitboard attacks = Bitboards::sliding_attack(pt, s, 0);
#ifndef USE_PEXT m.mask = attacks & ~edges;
#ifdef USE_PEXT
m.pseudoAttacks = attacks;
#else
m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask);
#endif #endif
// Set the offset for the attacks table of the square. We have individual // 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 // Use Carry-Rippler trick to enumerate all subsets of masks[s] and
// store the corresponding sliding attack bitboard in reference[]. // store the corresponding sliding attack bitboard in reference[].
Bitboard b = 0; Bitboard b = 0;
[[maybe_unused]] Bitboard prevSliding = -1;
do 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; occupancy[size] = b;
#endif
reference[size] = Bitboards::sliding_attack(pt, s, b); reference[size] = Bitboards::sliding_attack(pt, s, b);
#endif
if (HasPext)
m.attacks[pext(b, m.mask)] = reference[size];
size++; size++;
b = (b - m.mask) & m.mask; b = (b - m.mask) & m.mask;
@@ -184,6 +179,57 @@ void init_magics(PieceType pt, Bitboard table[], Magic magics[][2]) {
#endif #endif
} }
} }
#if defined(USE_COMPTIME_ATTACKS) && defined(USE_PEXT)
constexpr auto RookTable = []() {
std::array<uint16_t, 0x19000> result{};
Magic magics[64][2] = {};
init_magics(ROOK, result.data(), magics, false);
return result;
}();
constexpr auto BishopTable = []() {
std::array<uint16_t, 0x1480> result{};
Magic magics[64][2] = {};
init_magics(BISHOP, result.data(), magics, false);
return result;
}();
#else
std::array<MagicMask, 0x19000> RookTable;
std::array<MagicMask, 0x1480> 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<File>(s1, s2), distance<Rank>(s1, s2));
init_magics(ROOK, const_cast<MagicMask*>(RookTable.data()), Magics, true);
init_magics(BISHOP, const_cast<MagicMask*>(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 } // namespace Stockfish
+21 -11
View File
@@ -76,11 +76,14 @@ extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
// Magic holds all magic bitboards relevant data for a single square // Magic holds all magic bitboards relevant data for a single square
struct Magic { struct Magic {
Bitboard mask; Bitboard mask;
#ifdef USE_PEXT
uint16_t* attacks;
Bitboard pseudoAttacks;
#else
Bitboard* attacks; Bitboard* attacks;
#ifndef USE_PEXT Bitboard magic;
Bitboard magic; unsigned shift;
unsigned shift;
#endif #endif
// Compute the attack's index using the 'magic bitboards' approach // Compute the attack's index using the 'magic bitboards' approach
@@ -98,7 +101,13 @@ struct Magic {
#endif #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]; 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) { constexpr Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) {
Bitboard attacks = 0; Bitboard attacks = 0, dest = 0;
Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; constexpr Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST};
Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; constexpr Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST};
for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) for (Direction d : (pt == ROOK ? RookDirections : BishopDirections))
{ {
Square s = sq; Square s = sq;
while (safe_destination(s, d)) while ((dest = safe_destination(s, d)))
{ {
attacks |= (s += d); attacks |= dest;
if (occupied & s) s += d;
if (occupied & dest)
{ {
break; break;
} }
+1 -2
View File
@@ -87,8 +87,7 @@
#if defined(USE_PEXT) #if defined(USE_PEXT)
#include <immintrin.h> // Header for _pext_u64() intrinsic #include <immintrin.h> // Header for _pext_u64() intrinsic
#define pext(b, m) _pext_u64(b, m) #define pext(b, m) _pext_u64(b, m)
#else #define pdep(b, m) _pdep_u64(b, m)
#define pext(b, m) 0
#endif #endif
namespace Stockfish { namespace Stockfish {