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
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
+96 -50
View File
@@ -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<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)
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<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
+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
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;
}
+1 -2
View File
@@ -87,8 +87,7 @@
#if defined(USE_PEXT)
#include <immintrin.h> // Header for _pext_u64() intrinsic
#define pext(b, m) _pext_u64(b, m)
#else
#define pext(b, m) 0
#define pdep(b, m) _pdep_u64(b, m)
#endif
namespace Stockfish {