From 1554a2ca0b85c8fa94cfe8f4b68bd85e62107d6c Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Mon, 4 May 2026 08:33:01 +0200 Subject: [PATCH] Precompute moves and use magics index as compress mask on AVX512ICL Passed STC https://tests.stockfishchess.org/tests/view/69f6df65b64b50e29dbed427 LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 112224 W: 29195 L: 28782 D: 54247 Ptnml(0-2): 312, 12345, 30381, 12766, 308 Local speedup: ``` Result of 100 runs base (...kfish.master) = 2050387 +/- 5497 test (./stockfish ) = 2069598 +/- 4426 diff = +19211 +/- 7468 speedup = +0.0094 P(speedup > 0) = 1.0000 ``` closes https://github.com/official-stockfish/Stockfish/pull/6797 No functional change --- src/bitboard.h | 11 +++ src/movegen.cpp | 89 ++++++++++++++++++- .../layers/affine_transform_sparse_input.h | 13 --- 3 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/bitboard.h b/src/bitboard.h index edeba63b3..db1e69633 100644 --- a/src/bitboard.h +++ b/src/bitboard.h @@ -281,6 +281,17 @@ inline int popcount(Bitboard b) { #endif } +inline constexpr int lsb_index64[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42, + 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, + 31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63}; + +constexpr int constexpr_lsb(uint64_t bb) { + assert(bb != 0); + constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL; + return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58]; +} + // Returns the least significant bit in a non-zero bitboard. inline Square lsb(Bitboard b) { assert(b); diff --git a/src/movegen.cpp b/src/movegen.cpp index 8afd4c35a..86edf6ce2 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -62,6 +62,76 @@ inline Move* splat_moves(Move* moveList, Square from, Bitboard to_bb) { return moveList + popcount(to_bb); } +// Rook/bishop, indexed by (Pt - BISHOP) and from sq +// Moves are provided in ascending order of the piece's attacks on an empty board +alignas(64) constexpr auto SliderMoves = []() { + std::array, SQUARE_NB>, 2> arr{}; + for (PieceType pt : {BISHOP, ROOK}) + { + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + Bitboard bb = PseudoAttacks[pt][s]; + int i = 0; + while (bb) + { + arr[pt - BISHOP][s][i++] = Move(s, Square(constexpr_lsb(bb))); + bb &= bb - 1; + } + } + } + return arr; +}(); + +// Knight/king analog of the above +alignas(64) constexpr auto KnightKingMoves = []() { + std::array, SQUARE_NB>, 2> arr{}; + for (PieceType pt : {KNIGHT, KING}) + { + for (Square s = SQ_A1; s <= SQ_H8; ++s) + { + Bitboard bb = PseudoAttacks[pt][s]; + int i = 0; + while (bb) + { + arr[pt == KING][s][i++] = Move(s, Square(constexpr_lsb(bb))); + bb &= bb - 1; + } + } + } + return arr; +}(); + +template +inline Move* +splat_precomputed_moves(Move* moveList, Square from, Bitboard occupied, Bitboard target) { + static_assert(Pt != QUEEN && Pt != PAWN, "Unsupported piece type"); + + // The nth bit in the mask corresponds to the nth square in the piece's pseudo-attacks + uint32_t mask; + if constexpr (Pt == BISHOP || Pt == ROOK) + { + const Magic& magic = Magics[from][Pt - BISHOP]; + + mask = magic.attacks[magic.index(occupied)]; + mask &= pext(target, magic.pseudoAttacks); + + const __m256i moves = + *reinterpret_cast(SliderMoves[Pt - BISHOP][from].data()); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(moveList), + _mm256_maskz_compress_epi16(mask, moves)); + } + else + { + mask = pext(target, PseudoAttacks[Pt][from]); + + __m128i moves = *reinterpret_cast(KnightKingMoves[Pt == KING][from].data()); + _mm_storeu_si128(reinterpret_cast<__m128i*>(moveList), + _mm_maskz_compress_epi16(mask, moves)); + } + + return moveList + popcount(mask); +} + #else template @@ -192,8 +262,15 @@ Move* generate_moves(const Position& pos, Move* moveList, Bitboard target) { while (bb) { - Square from = pop_lsb(bb); - Bitboard b = attacks_bb(from, pos.pieces()) & target; + Square from = pop_lsb(bb); +#ifdef USE_AVX512ICL + if constexpr (Pt != QUEEN) + { + moveList = splat_precomputed_moves(moveList, from, pos.pieces(), target); + continue; + } +#endif + Bitboard b = attacks_bb(from, pos.pieces()) & target; moveList = splat_moves(moveList, from, b); } @@ -225,9 +302,13 @@ Move* generate_all(const Position& pos, Move* moveList) { moveList = generate_moves(pos, moveList, target); } - Bitboard b = attacks_bb(ksq) & (Type == EVASIONS ? ~pos.pieces(Us) : target); + Bitboard b = Type == EVASIONS ? ~pos.pieces(Us) : target; - moveList = splat_moves(moveList, ksq, b); +#ifdef USE_AVX512ICL + moveList = splat_precomputed_moves(moveList, ksq, 0ULL, b); +#else + moveList = splat_moves(moveList, ksq, attacks_bb(ksq) & b); +#endif if ((Type == QUIETS || Type == NON_EVASIONS) && pos.can_castle(Us & ANY_CASTLING)) for (CastlingRights cr : {Us & KING_SIDE, Us & QUEEN_SIDE}) diff --git a/src/nnue/layers/affine_transform_sparse_input.h b/src/nnue/layers/affine_transform_sparse_input.h index 0b09ae6c4..ab77dc4bd 100644 --- a/src/nnue/layers/affine_transform_sparse_input.h +++ b/src/nnue/layers/affine_transform_sparse_input.h @@ -38,19 +38,6 @@ namespace Stockfish::Eval::NNUE::Layers { -#if (USE_SSSE3 | (USE_NEON >= 8)) -static constexpr int lsb_index64[64] = { - 0, 47, 1, 56, 48, 27, 2, 60, 57, 49, 41, 37, 28, 16, 3, 61, 54, 58, 35, 52, 50, 42, - 21, 44, 38, 32, 29, 23, 17, 11, 4, 62, 46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, - 31, 22, 10, 45, 25, 39, 14, 33, 19, 30, 9, 24, 13, 18, 8, 12, 7, 6, 5, 63}; - -constexpr int constexpr_lsb(uint64_t bb) { - assert(bb != 0); - constexpr uint64_t debruijn64 = 0x03F79D71B4CB0A89ULL; - return lsb_index64[((bb ^ (bb - 1)) * debruijn64) >> 58]; -} -#endif - // Sparse input implementation template class AffineTransformSparseInput {