diff --git a/src/attacks.cpp b/src/attacks.cpp index ae112287b..15100331f 100644 --- a/src/attacks.cpp +++ b/src/attacks.cpp @@ -30,7 +30,11 @@ Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; +#ifdef USE_DUAL_HYPERBOLA_QUINT +alignas(64) DualMagic DualMagics[SQUARE_NB]; +#else alignas(64) Magic Magics[SQUARE_NB][2]; +#endif } @@ -40,8 +44,7 @@ using MagicMask = uint16_t; using MagicMask = Bitboard; #endif -#ifdef USE_HYPERBOLA_QUINT -static Bitboard line_mask(Square sq, Direction d1, Direction d2) { +[[maybe_unused]] static Bitboard line_mask(Square sq, Direction d1, Direction d2) { Bitboard mask = 0, dest; for (Direction d : {d1, d2}) { @@ -55,6 +58,7 @@ static Bitboard line_mask(Square sq, Direction d1, Direction d2) { return mask; } +#ifdef USE_HYPERBOLA_QUINT static void init_magics(Magic magics[][2]) { for (Square s = SQ_A1; s <= SQ_H8; ++s) { @@ -71,6 +75,48 @@ static void init_magics(Magic magics[][2]) { } } +#elif defined(USE_DUAL_HYPERBOLA_QUINT) + +// Sliding attacks within a rank, indexed by the slider's file and the +// 8-bit rank occupancy, yielding the 8-bit attack set on that rank +constexpr auto RankAttacks = []() { + std::array, FILE_NB> table{}; + for (int file = 0; file < 8; ++file) + for (int occ = 0; occ < 256; ++occ) + { + uint8_t attacks = 0; + for (int f = file + 1; f <= 7; ++f) + { + attacks |= uint8_t(1 << f); + if (occ & (1 << f)) + break; + } + for (int f = file - 1; f >= 0; --f) + { + attacks |= uint8_t(1 << f); + if (occ & (1 << f)) + break; + } + table[file][occ] = attacks; + } + return table; +}(); + +static void init_dual_magics(DualMagic magics[]) { + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + DualMagic& m = magics[s]; + m.maskFile = line_mask(s, NORTH, SOUTH); + m.maskDiag = line_mask(s, NORTH_EAST, SOUTH_WEST); + m.maskNone = 0; + m.maskAntidiag = line_mask(s, NORTH_WEST, SOUTH_EAST); + m.r = square_bb(s) * 2; + m.rr = square_bb(Square(63 - int(s))) * 2; + m.rankAttacksLookup = RankAttacks[int(file_of(s))].data(); + m.shift = 8 * int(rank_of(s)); + } +} + #else namespace { @@ -181,7 +227,7 @@ constexpr auto BishopTable = []() { init_magics(BISHOP, result.data(), magics, false); return result; }(); - #else + #elif !defined(USE_DUAL_HYPERBOLA_QUINT) && !defined(USE_HYPERBOLA_QUINT) std::array RookTable; std::array BishopTable; #endif @@ -193,6 +239,8 @@ void init() { #ifdef USE_HYPERBOLA_QUINT init_magics(Magics); +#elif defined(USE_DUAL_HYPERBOLA_QUINT) + init_dual_magics(DualMagics); #else init_magics(ROOK, const_cast(RookTable.data()), Magics, true); init_magics(BISHOP, const_cast(BishopTable.data()), Magics, true); @@ -216,10 +264,14 @@ void init() { } } +#ifdef USE_DUAL_HYPERBOLA_QUINT +const DualMagic& dual_magic(Square s) { return DualMagics[s]; } +#else const Magic& magic(Square s, PieceType pt) { assert((pt == BISHOP || pt == ROOK) && is_ok(s)); return Magics[s][pt - BISHOP]; } +#endif Bitboard line_bb(Square s1, Square s2) { assert(is_ok(s1) && is_ok(s2)); diff --git a/src/attacks.h b/src/attacks.h index f13eb6814..f74cbbd9b 100644 --- a/src/attacks.h +++ b/src/attacks.h @@ -31,6 +31,9 @@ #define USE_HYPERBOLA_QUINT #elif defined(__loongarch__) && __loongarch_grlen == 64 #define USE_HYPERBOLA_QUINT +#elif defined(USE_AVX2) && !defined(USE_PEXT) + #include + #define USE_DUAL_HYPERBOLA_QUINT #endif namespace Stockfish::Attacks { @@ -69,6 +72,61 @@ struct Magic { return hyperbola(occupied, mask1) | hyperbola(occupied, mask2); } }; + +const Magic& magic(Square s, PieceType pt); + +#elif defined(USE_DUAL_HYPERBOLA_QUINT) + +struct DualMagic { + // file, diagonal, unused, antidiagonal + Bitboard maskFile, maskDiag, maskNone, maskAntidiag; + // Precomputed 2 * square_bb(sq), 2 * reverse(square_bb(sq)) + Bitboard r, rr; + + const uint8_t* RESTRICT rankAttacksLookup; + // 8 * rank_of(sq) + int shift; + + // We always compute [bishop, rook] attacks at once, then rely on + // compiler's DCE and CSE to eliminate unneeded re-computations or extractions. + // + // When using hyperbola quintessence, file, diagonal and antidiagonal attacks + // can use a byte reversal rather than a full bit reversal (because all squares + // reside in different bytes). Rank atttacks cannot. Thus, for rank attacks + // only, we use a compact lookup table indexed by the 8 bits of the rank's occupancy. + std::pair both_attacks_bb(Bitboard occupied) const { + // Byteswap within 64-bit elements + const auto bswap = [](__m256i v) { + return _mm256_shuffle_epi8(v, _mm256_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15)); + }; + + // Each lane contains a mask and we follow the same HQ algorithm as + // given above in the ARM64 code path + const __m256i mask = _mm256_load_si256(reinterpret_cast(this)); + const __m256i rs = _mm256_set1_epi64x(r); + const __m256i rrs = _mm256_set1_epi64x(rr); + + __m256i o = _mm256_and_si256(mask, _mm256_set1_epi64x(occupied)); + __m256i fwd = _mm256_sub_epi64(o, rs); + __m256i rev = bswap(_mm256_sub_epi64(bswap(o), rrs)); + __m256i result = _mm256_and_si256(_mm256_xor_si256(fwd, rev), mask); + + // Lane 0: rook attacks (file only); lane 1: bishop attacks + __m128i rookBishop = + _mm_or_si128(_mm256_extracti128_si256(result, 1), _mm256_castsi256_si128(result)); + + Bitboard rowOccupancy = rankAttacksLookup[(occupied >> shift) & 0xff]; + Bitboard rankAttacks = rowOccupancy << shift; + + // [bishop, rook] + return {_mm_extract_epi64(rookBishop, 1), _mm_cvtsi128_si64(rookBishop) + rankAttacks}; + } +}; + +const DualMagic& dual_magic(Square s); + #else // Magic holds all magic bitboards relevant data for a single square struct Magic { @@ -105,12 +163,14 @@ struct Magic { #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); + +#endif + +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. @@ -220,6 +280,21 @@ inline Bitboard attacks_bb(Square s, Bitboard occupied) { assert(Pt != PAWN && is_ok(s)); +#ifdef USE_DUAL_HYPERBOLA_QUINT + const auto [bishop, rook] = dual_magic(s).both_attacks_bb(occupied); + + switch (Pt) + { + case BISHOP : + return bishop; + case ROOK : + return rook; + case QUEEN : + return bishop | rook; + default : + return PseudoAttacks[Pt][s]; + } +#else switch (Pt) { case BISHOP : @@ -230,6 +305,7 @@ inline Bitboard attacks_bb(Square s, Bitboard occupied) { default : return PseudoAttacks[Pt][s]; } +#endif } // Returns the attacks by the given piece diff --git a/src/misc.h b/src/misc.h index d763c50d3..9059762ff 100644 --- a/src/misc.h +++ b/src/misc.h @@ -544,6 +544,14 @@ void move_to_front(std::vector& vec, Predicate pred) { #define sf_unreachable() #endif +#ifdef __GNUC__ + #define RESTRICT __restrict__ +#elif defined(_MSC_VER) + #define RESTRICT __restrict +#else + #define RESTRICT +#endif + } // namespace Stockfish template