diff --git a/src/Makefile b/src/Makefile index 1cd017b18..5329ebbe4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -67,7 +67,7 @@ BINDIR = $(PREFIX)/bin PGOBENCH = $(RUN_PREFIX) ./$(EXE) bench ### Source and object files -SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ +SRCS = attacks.cpp benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ misc.cpp movegen.cpp movepick.cpp position.cpp \ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \ nnue/nnue_accumulator.cpp nnue/nnue_misc.cpp nnue/network.cpp \ @@ -76,7 +76,7 @@ SRCS = benchmark.cpp bitboard.cpp evaluate.cpp main.cpp \ OTHER_SRCS = universal/entry_x86.cpp universal/entry_arm64.cpp universal/nnue_embed.cpp -HEADERS = benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ +HEADERS = attacks.h benchmark.h bitboard.h evaluate.h misc.h movegen.h movepick.h history.h \ nnue/nnue_misc.h nnue/features/half_ka_v2_hm.h nnue/features/full_threats.h \ nnue/layers/affine_transform.h nnue/layers/affine_transform_sparse_input.h \ nnue/layers/clipped_relu.h nnue/layers/sqr_clipped_relu.h nnue/nnue_accumulator.h \ diff --git a/src/attacks.cpp b/src/attacks.cpp new file mode 100644 index 000000000..ae112287b --- /dev/null +++ b/src/attacks.cpp @@ -0,0 +1,239 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "attacks.h" + +#include + +#include "misc.h" + +namespace Stockfish::Attacks { + +namespace { + +Bitboard LineBB[SQUARE_NB][SQUARE_NB]; +Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; +Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; + +alignas(64) Magic Magics[SQUARE_NB][2]; + +} + +#ifdef USE_PEXT +using MagicMask = uint16_t; +#else +using MagicMask = Bitboard; +#endif + +#ifdef USE_HYPERBOLA_QUINT +static Bitboard line_mask(Square sq, Direction d1, Direction d2) { + Bitboard mask = 0, dest; + for (Direction d : {d1, d2}) + { + Square s = sq; + while ((dest = safe_destination(s, d))) + { + mask |= dest; + s += d; + } + } + return mask; +} + +static void init_magics(Magic magics[][2]) { + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + Magic& rook = magics[s][ROOK - BISHOP]; + rook.mask1 = line_mask(s, NORTH, SOUTH); + rook.mask2 = line_mask(s, EAST, WEST); + + Magic& bishop = magics[s][BISHOP - BISHOP]; + bishop.mask1 = line_mask(s, NORTH_EAST, SOUTH_WEST); + bishop.mask2 = line_mask(s, NORTH_WEST, SOUTH_EAST); + + rook.r = bishop.r = square_bb(s) * 2; + rook.rr = bishop.rr = square_bb(Square(63 - int(s))) * 2; + } +} + +#else + +namespace { +[[maybe_unused]] constexpr Bitboard constexpr_pext(Bitboard b, Bitboard m) { + Bitboard result = 0, bit = 0; + while (m) + { + Bitboard last = m & -m; + result |= bool(b & last) << bit++; + m ^= last; + } + return result; +} + + #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 + int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020}, + {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}}; + + Bitboard occupancy[4096]; + int epoch[4096] = {}, cnt = 0; + Bitboard reference[4096] = {}; + #endif + int size = 0; + + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s)); + + Magic& m = magics[s][pt - BISHOP]; + Bitboard attacks = sliding_attack(pt, s, 0); + m.mask = attacks & ~edges; + #ifdef USE_PEXT + m.pseudoAttacks = attacks; + #else + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); + #endif + m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size; + size = 0; + + Bitboard b = 0; + [[maybe_unused]] Bitboard prevSliding = -1; + do + { + #ifdef USE_PEXT + if (!tableAlreadyInit) + { + Bitboard sliding = sliding_attack(pt, s, b); + m.attacks[size] = + sliding != prevSliding ? constexpr_pext(sliding, attacks) : m.attacks[size - 1]; + prevSliding = sliding; + } + #else + occupancy[size] = b; + reference[size] = sliding_attack(pt, s, b); + #endif + + size++; + b = (b - m.mask) & m.mask; + } while (b); + + #ifndef USE_PEXT + PRNG rng(seeds[Is64Bit][rank_of(s)]); + + for (int i = 0; i < size;) + { + for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;) + m.magic = rng.sparse_rand(); + + for (++cnt, i = 0; i < size; ++i) + { + unsigned idx = m.index(occupancy[i]); + + if (epoch[idx] < cnt) + { + epoch[idx] = cnt; + m.attacks[idx] = reference[i]; + } + else if (m.attacks[idx] != reference[i]) + break; + } + } + #endif + } +} + + #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 +} + +#endif + +void init() { + +#ifdef USE_HYPERBOLA_QUINT + init_magics(Magics); +#else + init_magics(ROOK, const_cast(RookTable.data()), Magics, true); + init_magics(BISHOP, const_cast(BishopTable.data()), Magics, true); +#endif + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + for (PieceType pt : {BISHOP, ROOK}) + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + { + if (PseudoAttacks[pt][s1] & s2) + { + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = + (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); + RayPassBB[s1][s2] = + attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2); + } + BetweenBB[s1][s2] |= s2; + } + } +} + +const Magic& magic(Square s, PieceType pt) { + assert((pt == BISHOP || pt == ROOK) && is_ok(s)); + return Magics[s][pt - BISHOP]; +} + +Bitboard line_bb(Square s1, Square s2) { + assert(is_ok(s1) && is_ok(s2)); + return LineBB[s1][s2]; +} + +Bitboard between_bb(Square s1, Square s2) { + assert(is_ok(s1) && is_ok(s2)); + return BetweenBB[s1][s2]; +} + +Bitboard ray_pass_bb(Square s1, Square s2) { + assert(is_ok(s1) && is_ok(s2)); + return RayPassBB[s1][s2]; +} + +} // namespace Stockfish::Attacks diff --git a/src/attacks.h b/src/attacks.h new file mode 100644 index 000000000..f13eb6814 --- /dev/null +++ b/src/attacks.h @@ -0,0 +1,262 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2026 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef ATTACKS_H_INCLUDED +#define ATTACKS_H_INCLUDED + +#include +#include +#include + +#include "types.h" +#include "bitboard.h" + +#ifdef __aarch64__ + #include + #define USE_HYPERBOLA_QUINT +#elif defined(__loongarch__) && __loongarch_grlen == 64 + #define USE_HYPERBOLA_QUINT +#endif + +namespace Stockfish::Attacks { + +void init(); + +#ifdef USE_HYPERBOLA_QUINT + +inline Bitboard reverse_bb(Bitboard bb) { + #ifdef __aarch64__ + return __rbitll(bb); + #else // loongarch + Bitboard out; + asm("bitrev.d %0, %1" : "=r"(out) : "r"(bb)); + return out; + #endif +} + +// Hyperbola quintessence implementation for ARM, thanks to the availability of an +// efficient bit reversal instruction. +// See https://www.chessprogramming.org/Hyperbola_Quintessence +struct Magic { + // For rooks: file attacks, rank attacks. For bishops: diagonal/antidiagonal + Bitboard mask1, mask2; + // Precomputed 2 * square_bb(sq), 2 * reverse(square_bb(sq)) + Bitboard r, rr; + + Bitboard hyperbola(Bitboard occupied, Bitboard mask) const { + Bitboard o = occupied & mask; + Bitboard fwd = o - r; + Bitboard rev = reverse_bb(o) - rr; + return (fwd ^ reverse_bb(rev)) & mask; + } + + Bitboard attacks_bb(Bitboard occupied) const { + return hyperbola(occupied, mask1) | hyperbola(occupied, mask2); + } +}; +#else +// Magic holds all magic bitboards relevant data for a single square +struct Magic { + Bitboard mask; + #ifdef USE_PEXT + uint16_t* attacks; + Bitboard pseudoAttacks; + #else + Bitboard* attacks; + Bitboard magic; + unsigned shift; + #endif + + // Compute the attack's index using the 'magic bitboards' approach + unsigned index(Bitboard occupied) const { + + #ifdef USE_PEXT + return unsigned(pext(occupied, mask)); + #else + if (Is64Bit) + return unsigned(((occupied & mask) * magic) >> shift); + + unsigned lo = unsigned(occupied) & unsigned(mask); + unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); + return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; + #endif + } + + Bitboard attacks_bb(Bitboard occupied) const { + #ifdef USE_PEXT + return pdep(attacks[index(occupied)], pseudoAttacks); + #else + return attacks[index(occupied)]; + #endif + } +}; +#endif + +const Magic& magic(Square s, PieceType pt); +Bitboard line_bb(Square s1, Square s2); +Bitboard between_bb(Square s1, Square s2); +Bitboard ray_pass_bb(Square s1, Square s2); + +// Returns the bitboard of target square for the given step +// from the given square. If the step is off the board, returns empty bitboard. +constexpr Bitboard safe_destination(Square s, int step) { + constexpr auto abs = [](int v) { return v < 0 ? -v : v; }; + Square to = Square(s + step); + return is_ok(to) && abs(file_of(s) - file_of(to)) <= 2 ? square_bb(to) : Bitboard(0); +} + +constexpr Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { + Bitboard attacks = 0, dest = 0; + constexpr Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; + constexpr Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; + + for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) + { + Square s = sq; + while ((dest = safe_destination(s, d))) + { + attacks |= dest; + s += d; + if (occupied & dest) + { + break; + } + } + } + + return attacks; +} + +constexpr Bitboard knight_attack(Square sq) { + Bitboard b = {}; + for (int step : {-17, -15, -10, -6, 6, 10, 15, 17}) + b |= safe_destination(sq, step); + return b; +} + +constexpr Bitboard king_attack(Square sq) { + Bitboard b = {}; + for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) + b |= safe_destination(sq, step); + return b; +} + +constexpr Bitboard pseudo_attacks(PieceType pt, Square sq) { + switch (pt) + { + case PieceType::ROOK : + case PieceType::BISHOP : + return sliding_attack(pt, sq, 0); + case PieceType::QUEEN : + return sliding_attack(PieceType::ROOK, sq, 0) | sliding_attack(PieceType::BISHOP, sq, 0); + case PieceType::KNIGHT : + return knight_attack(sq); + case PieceType::KING : + return king_attack(sq); + default : + assert(false); + return 0; + } +} + +inline constexpr auto PseudoAttacks = []() constexpr { + std::array, PIECE_TYPE_NB> attacks{}; + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + attacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); + attacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); + + attacks[KING][s1] = pseudo_attacks(KING, s1); + attacks[KNIGHT][s1] = pseudo_attacks(KNIGHT, s1); + attacks[QUEEN][s1] = attacks[BISHOP][s1] = pseudo_attacks(BISHOP, s1); + attacks[QUEEN][s1] |= attacks[ROOK][s1] = pseudo_attacks(ROOK, s1); + } + + return attacks; +}(); + +inline constexpr auto PawnPushOrAttacks = []() constexpr { + std::array, COLOR_NB> attacks{}; + + for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) + { + attacks[WHITE][s1] = pawn_single_push_bb(WHITE, square_bb(s1)) | PseudoAttacks[WHITE][s1]; + attacks[BLACK][s1] = pawn_single_push_bb(BLACK, square_bb(s1)) | PseudoAttacks[BLACK][s1]; + } + + return attacks; +}(); + +// Returns the pseudo attacks of the given piece type +// assuming an empty board. +template +inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) { + + assert((Pt != PAWN || c < COLOR_NB) && is_ok(s)); + return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s]; +} + +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. +template +inline Bitboard attacks_bb(Square s, Bitboard occupied) { + + assert(Pt != PAWN && is_ok(s)); + + switch (Pt) + { + case BISHOP : + case ROOK : + return magic(s, Pt).attacks_bb(occupied); + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[Pt][s]; + } +} + +// Returns the attacks by the given piece +// assuming the board is occupied according to the passed Bitboard. +// Sliding piece attacks do not continue passed an occupied square. +inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { + + assert(pt != PAWN && is_ok(s)); + + switch (pt) + { + case BISHOP : + return attacks_bb(s, occupied); + case ROOK : + return attacks_bb(s, occupied); + case QUEEN : + return attacks_bb(s, occupied) | attacks_bb(s, occupied); + default : + return PseudoAttacks[pt][s]; + } +} + +inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) { + return type_of(pc) == PAWN ? PseudoAttacks[color_of(pc)][s] + : attacks_bb(type_of(pc), s, occupied); +} + +} // namespace Stockfish::Attacks + +#endif // #ifndef ATTACKS_H_INCLUDED diff --git a/src/bitboard.cpp b/src/bitboard.cpp index cab666fc3..b9cc284e9 100644 --- a/src/bitboard.cpp +++ b/src/bitboard.cpp @@ -18,29 +18,13 @@ #include "bitboard.h" -#include #include -#include - -#include "misc.h" namespace Stockfish { uint8_t PopCnt16[1 << 16]; uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; -Bitboard LineBB[SQUARE_NB][SQUARE_NB]; -Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; -Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; - -alignas(64) Magic Magics[SQUARE_NB][2]; - -#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. std::string Bitboards::pretty(Bitboard b) { @@ -62,178 +46,6 @@ std::string Bitboards::pretty(Bitboard b) { return s; } -#ifdef USE_HYPERBOLA_QUINT -static Bitboard line_mask(Square sq, Direction d1, Direction d2) { - Bitboard mask = 0, dest; - for (Direction d : {d1, d2}) - { - Square s = sq; - while ((dest = Bitboards::safe_destination(s, d))) - { - mask |= dest; - s += d; - } - } - return mask; -} - -static void init_magics(Magic magics[][2]) { - for (Square s = SQ_A1; s <= SQ_H8; ++s) - { - Magic& rook = magics[s][ROOK - BISHOP]; - rook.mask1 = line_mask(s, NORTH, SOUTH); - rook.mask2 = line_mask(s, EAST, WEST); - - Magic& bishop = magics[s][BISHOP - BISHOP]; - bishop.mask1 = line_mask(s, NORTH_EAST, SOUTH_WEST); - bishop.mask2 = line_mask(s, NORTH_WEST, SOUTH_EAST); - - rook.r = bishop.r = square_bb(s) * 2; - rook.rr = bishop.rr = square_bb(Square(63 - int(s))) * 2; - } -} - -#else - -namespace { -[[maybe_unused]] constexpr Bitboard constexpr_pext(Bitboard b, Bitboard m) { - Bitboard result = 0, bit = 0; - while (m) - { - Bitboard last = m & -m; - result |= bool(b & last) << bit++; - m ^= last; - } - return result; -} - - // 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. - #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 - int seeds[][RANK_NB] = {{8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020}, - {728, 10316, 55013, 32803, 12281, 15100, 16645, 255}}; - - Bitboard occupancy[4096]; - int epoch[4096] = {}, cnt = 0; - Bitboard reference[4096] = {}; - #endif - int size = 0; - - for (Square s = SQ_A1; s <= SQ_H8; ++s) - { - // Board edges are not considered in the relevant occupancies - Bitboard edges = ((Rank1BB | Rank8BB) & ~rank_bb(s)) | ((FileABB | FileHBB) & ~file_bb(s)); - - // Given a square 's', the mask is the bitboard of sliding attacks from - // 's' computed on an empty board. The index must be big enough to contain - // all the attacks for each possible subset of the mask and so is 2 power - // the number of 1s of the mask. Hence we deduce the size of the shift to - // apply to the 64 or 32 bits word to get the index. - Magic& m = magics[s][pt - BISHOP]; - 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 - // table sizes for each square with "Fancy Magic Bitboards". - m.attacks = s == SQ_A1 ? table : magics[s - 1][pt - BISHOP].attacks + size; - size = 0; - - // Use Carry-Rippler trick to enumerate all subsets of masks[s] and - // store the corresponding sliding attack bitboard in reference[]. - Bitboard b = 0; - [[maybe_unused]] Bitboard prevSliding = -1; - do - { - #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; - reference[size] = Bitboards::sliding_attack(pt, s, b); - #endif - - size++; - b = (b - m.mask) & m.mask; - } while (b); - - #ifndef USE_PEXT - PRNG rng(seeds[Is64Bit][rank_of(s)]); - - // Find a magic for square 's' picking up an (almost) random number - // until we find the one that passes the verification test. - for (int i = 0; i < size;) - { - for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6;) - m.magic = rng.sparse_rand(); - - // A good magic must map every possible occupancy to an index that - // looks up the correct sliding attack in the attacks[s] database. - // Note that we build up the database for square 's' as a side - // effect of verifying the magic. Keep track of the attempt count - // and save it in epoch[], little speed-up trick to avoid resetting - // m.attacks[] after every failed attempt. - for (++cnt, i = 0; i < size; ++i) - { - unsigned idx = m.index(occupancy[i]); - - if (epoch[idx] < cnt) - { - epoch[idx] = cnt; - m.attacks[idx] = reference[i]; - } - else if (m.attacks[idx] != reference[i]) - break; - } - } - #endif - } -} - - #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 -} - -#endif - // Initializes various bitboard tables. It is called at // startup and relies on global objects to be already zero-initialized. void Bitboards::init() { @@ -244,30 +56,6 @@ void Bitboards::init() { 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)); - -#ifdef USE_HYPERBOLA_QUINT - init_magics(Magics); -#else - init_magics(ROOK, const_cast(RookTable.data()), Magics, true); - init_magics(BISHOP, const_cast(BishopTable.data()), Magics, true); -#endif - - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - { - for (PieceType pt : {BISHOP, ROOK}) - for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - { - if (PseudoAttacks[pt][s1] & s2) - { - LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; - BetweenBB[s1][s2] = - (attacks_bb(pt, s1, square_bb(s2)) & attacks_bb(pt, s2, square_bb(s1))); - RayPassBB[s1][s2] = - attacks_bb(pt, s1, 0) & (attacks_bb(pt, s2, square_bb(s1)) | s2); - } - BetweenBB[s1][s2] |= s2; - } - } } } // namespace Stockfish diff --git a/src/bitboard.h b/src/bitboard.h index eb86e1a65..1461380a2 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -26,18 +26,9 @@ #include #include #include -#include -#include #include "types.h" -#ifdef __aarch64__ - #include - #define USE_HYPERBOLA_QUINT -#elif defined(__loongarch__) && __loongarch_grlen == 64 - #define USE_HYPERBOLA_QUINT -#endif - namespace Stockfish { namespace Bitboards { @@ -77,82 +68,6 @@ constexpr Bitboard Rank8BB = Rank1BB << (8 * 7); extern uint8_t PopCnt16[1 << 16]; extern uint8_t SquareDistance[SQUARE_NB][SQUARE_NB]; -extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; -extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; -extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; - -#ifdef USE_HYPERBOLA_QUINT - -inline Bitboard reverse_bb(Bitboard bb) { - #ifdef __aarch64__ - return __rbitll(bb); - #else // loongarch - Bitboard out; - asm("bitrev.d %0, %1" : "=r"(out) : "r"(bb)); - return out; - #endif -} - -// Hyperbola quintessence implementation for ARM, thanks to the availability of an -// efficient bit reversal instruction. -// See https://www.chessprogramming.org/Hyperbola_Quintessence -struct Magic { - // For rooks: file attacks, rank attacks. For bishops: diagonal/antidiagonal - Bitboard mask1, mask2; - // Precomputed 2 * square_bb(sq), 2 * reverse(square_bb(sq)) - Bitboard r, rr; - - Bitboard hyperbola(Bitboard occupied, Bitboard mask) const { - Bitboard o = occupied & mask; - Bitboard fwd = o - r; - Bitboard rev = reverse_bb(o) - rr; - return (fwd ^ reverse_bb(rev)) & mask; - } - - Bitboard attacks_bb(Bitboard occupied) const { - return hyperbola(occupied, mask1) | hyperbola(occupied, mask2); - } -}; -#else -// Magic holds all magic bitboards relevant data for a single square -struct Magic { - Bitboard mask; - #ifdef USE_PEXT - uint16_t* attacks; - Bitboard pseudoAttacks; - #else - Bitboard* attacks; - Bitboard magic; - unsigned shift; - #endif - - // Compute the attack's index using the 'magic bitboards' approach - unsigned index(Bitboard occupied) const { - - #ifdef USE_PEXT - return unsigned(pext(occupied, mask)); - #else - if (Is64Bit) - return unsigned(((occupied & mask) * magic) >> shift); - - unsigned lo = unsigned(occupied) & unsigned(mask); - unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); - return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; - #endif - } - - Bitboard attacks_bb(Bitboard occupied) const { - #ifdef USE_PEXT - return pdep(attacks[index(occupied)], pseudoAttacks); - #else - return attacks[index(occupied)]; - #endif - } -}; -#endif - -extern Magic Magics[SQUARE_NB][2]; - constexpr Bitboard square_bb(Square s) { assert(is_ok(s)); return 1ULL << s; @@ -218,30 +133,6 @@ constexpr Bitboard pawn_single_push_bb(Color c, Bitboard b) { return c == WHITE ? shift(b) : shift(b); } -// Returns a bitboard representing an entire line (from board edge -// to board edge) that intersects the two given squares. If the given squares -// are not on a same file/rank/diagonal, the function returns 0. For instance, -// line_bb(SQ_C4, SQ_F7) will return a bitboard with the A2-G8 diagonal. -inline Bitboard line_bb(Square s1, Square s2) { - - assert(is_ok(s1) && is_ok(s2)); - return LineBB[s1][s2]; -} - - -// Returns a bitboard representing the squares in the semi-open -// segment between the squares s1 and s2 (excluding s1 but including s2). If the -// given squares are not on a same file/rank/diagonal, it returns s2. For instance, -// between_bb(SQ_C4, SQ_F7) will return a bitboard with squares D5, E6 and F7, but -// between_bb(SQ_E6, SQ_F8) will return a bitboard with the square F8. This trick -// allows to generate non-king evasion moves faster: the defending piece must either -// interpose itself to cover the check or capture the checking piece. -inline Bitboard between_bb(Square s1, Square s2) { - - assert(is_ok(s1) && is_ok(s2)); - return BetweenBB[s1][s2]; -} - // distance() functions return the distance between x and y, defined as the // number of steps for a king in x to reach y. @@ -389,156 +280,6 @@ inline Square pop_lsb(Bitboard& b) { return s; } -namespace Bitboards { -// Returns the bitboard of target square for the given step -// from the given square. If the step is off the board, returns empty bitboard. -constexpr Bitboard safe_destination(Square s, int step) { - constexpr auto abs = [](int v) { return v < 0 ? -v : v; }; - Square to = Square(s + step); - return is_ok(to) && abs(file_of(s) - file_of(to)) <= 2 ? square_bb(to) : Bitboard(0); -} - -constexpr Bitboard sliding_attack(PieceType pt, Square sq, Bitboard occupied) { - Bitboard attacks = 0, dest = 0; - constexpr Direction RookDirections[4] = {NORTH, SOUTH, EAST, WEST}; - constexpr Direction BishopDirections[4] = {NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST}; - - for (Direction d : (pt == ROOK ? RookDirections : BishopDirections)) - { - Square s = sq; - while ((dest = safe_destination(s, d))) - { - attacks |= dest; - s += d; - if (occupied & dest) - { - break; - } - } - } - - return attacks; -} - -constexpr Bitboard knight_attack(Square sq) { - Bitboard b = {}; - for (int step : {-17, -15, -10, -6, 6, 10, 15, 17}) - b |= safe_destination(sq, step); - return b; -} - -constexpr Bitboard king_attack(Square sq) { - Bitboard b = {}; - for (int step : {-9, -8, -7, -1, 1, 7, 8, 9}) - b |= safe_destination(sq, step); - return b; -} - -constexpr Bitboard pseudo_attacks(PieceType pt, Square sq) { - switch (pt) - { - case PieceType::ROOK : - case PieceType::BISHOP : - return sliding_attack(pt, sq, 0); - case PieceType::QUEEN : - return sliding_attack(PieceType::ROOK, sq, 0) | sliding_attack(PieceType::BISHOP, sq, 0); - case PieceType::KNIGHT : - return knight_attack(sq); - case PieceType::KING : - return king_attack(sq); - default : - assert(false); - return 0; - } -} - -} - -inline constexpr auto PseudoAttacks = []() constexpr { - std::array, PIECE_TYPE_NB> attacks{}; - - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - { - attacks[WHITE][s1] = pawn_attacks_bb(square_bb(s1)); - attacks[BLACK][s1] = pawn_attacks_bb(square_bb(s1)); - - attacks[KING][s1] = Bitboards::pseudo_attacks(KING, s1); - attacks[KNIGHT][s1] = Bitboards::pseudo_attacks(KNIGHT, s1); - attacks[QUEEN][s1] = attacks[BISHOP][s1] = Bitboards::pseudo_attacks(BISHOP, s1); - attacks[QUEEN][s1] |= attacks[ROOK][s1] = Bitboards::pseudo_attacks(ROOK, s1); - } - - return attacks; -}(); - -inline constexpr auto PawnPushOrAttacks = []() constexpr { - std::array, COLOR_NB> attacks{}; - - for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) - { - attacks[WHITE][s1] = pawn_single_push_bb(WHITE, square_bb(s1)) | PseudoAttacks[WHITE][s1]; - attacks[BLACK][s1] = pawn_single_push_bb(BLACK, square_bb(s1)) | PseudoAttacks[BLACK][s1]; - } - - return attacks; -}(); - - -// Returns the pseudo attacks of the given piece type -// assuming an empty board. -template -inline Bitboard attacks_bb(Square s, Color c = COLOR_NB) { - - assert((Pt != PAWN || c < COLOR_NB) && is_ok(s)); - return Pt == PAWN ? PseudoAttacks[c][s] : PseudoAttacks[Pt][s]; -} - - -// Returns the attacks by the given piece -// assuming the board is occupied according to the passed Bitboard. -// Sliding piece attacks do not continue passed an occupied square. -template -inline Bitboard attacks_bb(Square s, Bitboard occupied) { - - assert(Pt != PAWN && is_ok(s)); - - switch (Pt) - { - case BISHOP : - case ROOK : - return Magics[s][Pt - BISHOP].attacks_bb(occupied); - case QUEEN : - return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : - return PseudoAttacks[Pt][s]; - } -} - -// Returns the attacks by the given piece -// assuming the board is occupied according to the passed Bitboard. -// Sliding piece attacks do not continue passed an occupied square. -inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { - - assert(pt != PAWN && is_ok(s)); - - switch (pt) - { - case BISHOP : - return attacks_bb(s, occupied); - case ROOK : - return attacks_bb(s, occupied); - case QUEEN : - return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : - return PseudoAttacks[pt][s]; - } -} - -inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) { - return type_of(pc) == PAWN ? PseudoAttacks[color_of(pc)][s] - : attacks_bb(type_of(pc), s, occupied); -} - } // namespace Stockfish #endif // #ifndef BITBOARD_H_INCLUDED diff --git a/src/main.cpp b/src/main.cpp index 6a5f6fe68..c386c4189 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include #include +#include "attacks.h" #include "bitboard.h" #include "misc.h" #include "position.h" @@ -39,6 +40,7 @@ int main(int argc, char* argv[]) { std::cout << engine_info() << std::endl; Bitboards::init(); + Attacks::init(); Position::init(); auto uci = std::make_unique(argc, argv); diff --git a/src/movegen.cpp b/src/movegen.cpp index 86edf6ce2..4f048fee2 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -21,6 +21,7 @@ #include #include +#include "attacks.h" #include "bitboard.h" #include "position.h" @@ -70,7 +71,7 @@ alignas(64) constexpr auto SliderMoves = []() { { for (Square s = SQ_A1; s <= SQ_H8; ++s) { - Bitboard bb = PseudoAttacks[pt][s]; + Bitboard bb = Attacks::PseudoAttacks[pt][s]; int i = 0; while (bb) { @@ -89,7 +90,7 @@ alignas(64) constexpr auto KnightKingMoves = []() { { for (Square s = SQ_A1; s <= SQ_H8; ++s) { - Bitboard bb = PseudoAttacks[pt][s]; + Bitboard bb = Attacks::PseudoAttacks[pt][s]; int i = 0; while (bb) { @@ -110,7 +111,7 @@ splat_precomputed_moves(Move* moveList, Square from, Bitboard occupied, Bitboard uint32_t mask; if constexpr (Pt == BISHOP || Pt == ROOK) { - const Magic& magic = Magics[from][Pt - BISHOP]; + const Attacks::Magic& magic = Attacks::magic(from, Pt); mask = magic.attacks[magic.index(occupied)]; mask &= pext(target, magic.pseudoAttacks); @@ -122,7 +123,7 @@ splat_precomputed_moves(Move* moveList, Square from, Bitboard occupied, Bitboard } else { - mask = pext(target, PseudoAttacks[Pt][from]); + mask = pext(target, Attacks::PseudoAttacks[Pt][from]); __m128i moves = *reinterpret_cast(KnightKingMoves[Pt == KING][from].data()); _mm_storeu_si128(reinterpret_cast<__m128i*>(moveList), @@ -240,7 +241,7 @@ Move* generate_pawn_moves(const Position& pos, Move* moveList, Bitboard target) if (Type == EVASIONS && (target & (pos.ep_square() + Up))) return moveList; - b1 = pawnsNotOn7 & attacks_bb(pos.ep_square(), Them); + b1 = pawnsNotOn7 & Attacks::attacks_bb(pos.ep_square(), Them); assert(b1); @@ -270,7 +271,7 @@ Move* generate_moves(const Position& pos, Move* moveList, Bitboard target) { continue; } #endif - Bitboard b = attacks_bb(from, pos.pieces()) & target; + Bitboard b = Attacks::attacks_bb(from, pos.pieces()) & target; moveList = splat_moves(moveList, from, b); } @@ -290,7 +291,7 @@ Move* generate_all(const Position& pos, Move* moveList) { // Skip generating non-king moves when in double check if (Type != EVASIONS || !more_than_one(pos.checkers())) { - target = Type == EVASIONS ? between_bb(ksq, lsb(pos.checkers())) + target = Type == EVASIONS ? Attacks::between_bb(ksq, lsb(pos.checkers())) : Type == NON_EVASIONS ? ~pos.pieces(Us) : Type == CAPTURES ? pos.pieces(~Us) : ~pos.pieces(); // QUIETS @@ -307,7 +308,7 @@ Move* generate_all(const Position& pos, Move* moveList) { #ifdef USE_AVX512ICL moveList = splat_precomputed_moves(moveList, ksq, 0ULL, b); #else - moveList = splat_moves(moveList, ksq, attacks_bb(ksq) & b); + moveList = splat_moves(moveList, ksq, Attacks::attacks_bb(ksq) & b); #endif if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) diff --git a/src/nnue/features/full_threats.cpp b/src/nnue/features/full_threats.cpp index 6811cea65..83d0fcc11 100644 --- a/src/nnue/features/full_threats.cpp +++ b/src/nnue/features/full_threats.cpp @@ -26,6 +26,7 @@ #include #include +#include "../../attacks.h" #include "../../bitboard.h" #include "../../misc.h" #include "../../position.h" @@ -51,7 +52,7 @@ constexpr auto make_piece_indices_type() { for (Square from = SQ_A1; from <= SQ_H8; ++from) { - Bitboard attacks = PseudoAttacks[PT][from]; + Bitboard attacks = Attacks::PseudoAttacks[PT][from]; for (Square to = SQ_A1; to <= SQ_H8; ++to) { @@ -72,7 +73,7 @@ constexpr auto make_piece_indices_piece() { for (Square from = SQ_A1; from <= SQ_H8; ++from) { - Bitboard attacks = PawnPushOrAttacks[C][from]; + Bitboard attacks = Attacks::PawnPushOrAttacks[C][from]; for (Square to = SQ_A1; to <= SQ_H8; ++to) { @@ -129,14 +130,14 @@ constexpr auto init_threat_offsets() { if (type_of(piece) != PAWN) { - Bitboard attacks = PseudoAttacks[type_of(piece)][from]; + Bitboard attacks = Attacks::PseudoAttacks[type_of(piece)][from]; cumulativePieceOffset += constexpr_popcount(attacks); } else if (from >= SQ_A2 && from <= SQ_H7) { - Bitboard attacks = - (pieceIdx < 8) ? PawnPushOrAttacks[WHITE][from] : PawnPushOrAttacks[BLACK][from]; + Bitboard attacks = (pieceIdx < 8) ? Attacks::PawnPushOrAttacks[WHITE][from] + : Attacks::PawnPushOrAttacks[BLACK][from]; cumulativePieceOffset += constexpr_popcount(attacks); } } @@ -254,7 +255,7 @@ void FullThreats::append_active_indices(Color perspective, const Position& pos, while (bb) { Square from = pop_lsb(bb); - Bitboard attacks = attacks_bb(pt, from, occupied) & occupied; + Bitboard attacks = Attacks::attacks_bb(pt, from, occupied) & occupied; while (attacks) { Square to = pop_lsb(attacks); diff --git a/src/position.cpp b/src/position.cpp index 71a56cb1b..4c0b1dbf5 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -43,6 +43,8 @@ using std::string; namespace Stockfish { +using namespace Attacks; + namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; @@ -1216,11 +1218,11 @@ void Position::update_piece_threats(Piece pc, Square sliderSq = pop_lsb(sliders); Piece slider = piece_on(sliderSq); - const Bitboard ray = RayPassBB[sliderSq][s]; + const Bitboard ray = ray_pass_bb(sliderSq, s); const Bitboard discovered = ray & (rAttacks | bAttacks) & occupiedNoK; assert(!more_than_one(discovered)); - if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining) + if (discovered && (ray_pass_bb(sliderSq, s) & noRaysContaining) != noRaysContaining) { const Square threatenedSq = lsb(discovered); const Piece threatenedPc = piece_on(threatenedSq); diff --git a/src/position.h b/src/position.h index 8027988a0..25b29e8b9 100644 --- a/src/position.h +++ b/src/position.h @@ -29,6 +29,7 @@ #include #include +#include "attacks.h" #include "bitboard.h" #include "types.h" @@ -301,7 +302,7 @@ inline Bitboard Position::attacks_by(Color c) const { Bitboard threats = 0; Bitboard attackers = pieces(c, Pt); while (attackers) - threats |= attacks_bb(pop_lsb(attackers), pieces()); + threats |= Attacks::attacks_bb(pop_lsb(attackers), pieces()); return threats; } } diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index b75b6046a..b3a7b3a6c 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -38,6 +38,7 @@ #include #include +#include "../attacks.h" #include "../bitboard.h" #include "../misc.h" #include "../movegen.h" @@ -1406,7 +1407,7 @@ void Tablebases::init(const std::string& paths) { if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 { for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) - if ((PseudoAttacks[KING][s1] | s1) & s2) + if ((Attacks::PseudoAttacks[KING][s1] | s1) & s2) continue; // Illegal position else if (!off_A1H8(s1) && off_A1H8(s2) > 0)