From abd835dcbc3a28481224f6253b00b7420d062513 Mon Sep 17 00:00:00 2001 From: Timothy Herchen Date: Sun, 30 Nov 2025 13:17:39 -0800 Subject: [PATCH] Improve update_piece_threats passed on avx512ICL: LLR: 2.95 (-2.94,2.94) <0.00,2.00> Total: 30240 W: 8026 L: 7726 D: 14488 Ptnml(0-2): 95, 3235, 8171, 3513, 106 https://tests.stockfishchess.org/tests/view/69281d9ab23dfeae38cfeeb8 passed on generic architectures: LLR: 2.94 (-2.94,2.94) <0.00,2.00> Total: 73184 W: 19183 L: 18821 D: 35180 Ptnml(0-2): 258, 7988, 19744, 8338, 264 https://tests.stockfishchess.org/tests/view/6928ba11b23dfeae38cff276 subsequent cleanups tested as: LLR: 2.94 (-2.94,2.94) <-1.75,0.25> Total: 72480 W: 18678 L: 18502 D: 35300 Ptnml(0-2): 242, 7925, 19718, 8125, 230 https://tests.stockfishchess.org/tests/view/692a26adb23dfeae38cff566 We add an argument noRaysContaining, which skips all discoveries which contain all bits in the argument; if the argument is from | to, then this will eliminate the discovery. Separately, on AVX512ICL we can speed up the computation of DirtyThreats by moving from a pop_lsb loop to a vector extraction with vpcompressb. See PR for details. closes https://github.com/official-stockfish/Stockfish/pull/6453 No functional change --- src/misc.h | 7 ++++ src/position.cpp | 94 ++++++++++++++++++++++++++++++++++++++++-------- src/position.h | 9 +++-- src/types.h | 23 ++++++++---- 4 files changed, 109 insertions(+), 24 deletions(-) diff --git a/src/misc.h b/src/misc.h index 54598cd0e..db5f701e9 100644 --- a/src/misc.h +++ b/src/misc.h @@ -142,6 +142,13 @@ class ValueList { const T* end() const { return values_ + size_; } const T& operator[](int index) const { return values_[index]; } + T* make_space(size_t count) { + T* result = &values_[size_]; + size_ += count; + assert(size_ <= MaxSize); + return result; + } + private: T values_[MaxSize]; std::size_t size_ = 0; diff --git a/src/position.cpp b/src/position.cpp index 8993c2406..049c72682 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -1052,8 +1052,51 @@ inline void add_dirty_threat( dts->list.push_back({pc, threatened, s, threatenedSq, PutPiece}); } +#ifdef USE_AVX512ICL +// Given a DirtyThreat template and bit offsets to insert the piece type and square, write the threats +// present at the given bitboard. +template +void write_multiple_dirties(const Position& p, + Bitboard mask, + DirtyThreat dt_template, + DirtyThreats* dts) { + static_assert(sizeof(DirtyThreat) == 4); + + const __m512i board = _mm512_loadu_si512(p.piece_array().data()); + const __m512i AllSquares = _mm512_set_epi8( + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, + 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, + 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); + + const int dt_count = popcount(mask); + assert(dt_count <= 16); + + const __m512i template_v = _mm512_set1_epi32(dt_template.raw()); + auto* write = dts->list.make_space(dt_count); + + // Extract the list of squares and upconvert to 32 bits. There are never more than 16 + // incoming threats so this is sufficient. + __m512i threat_squares = _mm512_maskz_compress_epi8(mask, AllSquares); + threat_squares = _mm512_cvtepi8_epi32(_mm512_castsi512_si128(threat_squares)); + + __m512i threat_pieces = + _mm512_maskz_permutexvar_epi8(0x1111111111111111ULL, threat_squares, board); + + // Shift the piece and square into place + threat_squares = _mm512_slli_epi32(threat_squares, SqShift); + threat_pieces = _mm512_slli_epi32(threat_pieces, PcShift); + + const __m512i dirties = + _mm512_ternarylogic_epi32(template_v, threat_squares, threat_pieces, 254 /* A | B | C */); + _mm512_storeu_si512(reinterpret_cast<__m512i*>(write), dirties); +} +#endif + template -void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) { +void Position::update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining) { const Bitboard occupied = pieces(); const Bitboard rookQueens = pieces(ROOK, QUEEN); const Bitboard bishopQueens = pieces(BISHOP, QUEEN); @@ -1093,7 +1136,36 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) } threatened &= occupied; + Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); + Bitboard incoming_threats = + (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) + | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); +#ifdef USE_AVX512ICL + if (threatened) + { + if constexpr (PutPiece) + { + dts->threatenedSqs |= threatened; + dts->threateningSqs |= square_bb(s); + } + + DirtyThreat dt_template{pc, NO_PIECE, s, Square(0), PutPiece}; + write_multiple_dirties( + *this, threatened, dt_template, dts); + } + + Bitboard all_attackers = sliders | incoming_threats; + if (!all_attackers) + return; // Square s is threatened iff there's at least one attacker + + dts->threatenedSqs |= square_bb(s); + dts->threateningSqs |= all_attackers; + + DirtyThreat dt_template{NO_PIECE, pc, Square(0), s, PutPiece}; + write_multiple_dirties(*this, all_attackers, + dt_template, dts); +#else while (threatened) { Square threatenedSq = pop_lsb(threatened); @@ -1104,8 +1176,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, pc, threatenedPc, s, threatenedSq); } - - Bitboard sliders = (rookQueens & rAttacks) | (bishopQueens & bAttacks); +#endif if constexpr (ComputeRay) { @@ -1118,30 +1189,24 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) const Bitboard discovered = ray & qAttacks & occupied; assert(!more_than_one(discovered)); - if (discovered) + if (discovered && (RayPassBB[sliderSq][s] & noRaysContaining) != noRaysContaining) { const Square threatenedSq = lsb(discovered); const Piece threatenedPc = piece_on(threatenedSq); add_dirty_threat(dts, slider, threatenedPc, sliderSq, threatenedSq); } +#ifndef USE_AVX512ICL // for ICL, direct threats were processed earlier (all_attackers) add_dirty_threat(dts, slider, pc, sliderSq, s); +#endif } } else { - while (sliders) - { - Square sliderSq = pop_lsb(sliders); - Piece slider = piece_on(sliderSq); - add_dirty_threat(dts, slider, pc, sliderSq, s); - } + incoming_threats |= sliders; } - Bitboard incoming_threats = - (PseudoAttacks[KNIGHT][s] & knights) | (attacks_bb(s, WHITE) & blackPawns) - | (attacks_bb(s, BLACK) & whitePawns) | (PseudoAttacks[KING][s] & kings); - +#ifndef USE_AVX512ICL while (incoming_threats) { Square srcSq = pop_lsb(incoming_threats); @@ -1152,6 +1217,7 @@ void Position::update_piece_threats(Piece pc, Square s, DirtyThreats* const dts) add_dirty_threat(dts, srcPc, pc, srcSq, s); } +#endif } // Helper used to do/undo a castling move. This is a bit diff --git a/src/position.h b/src/position.h index 711c4e444..7a029ce18 100644 --- a/src/position.h +++ b/src/position.h @@ -187,7 +187,10 @@ class Position { // Other helpers template - void update_piece_threats(Piece pc, Square s, DirtyThreats* const dts); + void update_piece_threats(Piece pc, + Square s, + DirtyThreats* const dts, + Bitboard noRaysContaining = -1ULL); void move_piece(Square from, Square to, DirtyThreats* const dts = nullptr); template void do_castling(Color us, @@ -372,7 +375,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts Bitboard fromTo = from | to; if (dts) - update_piece_threats(pc, from, dts); + update_piece_threats(pc, from, dts, fromTo); byTypeBB[ALL_PIECES] ^= fromTo; byTypeBB[type_of(pc)] ^= fromTo; @@ -381,7 +384,7 @@ inline void Position::move_piece(Square from, Square to, DirtyThreats* const dts board[to] = pc; if (dts) - update_piece_threats(pc, to, dts); + update_piece_threats(pc, to, dts, fromTo); } inline void Position::swap_piece(Square s, Piece pc, DirtyThreats* const dts) { diff --git a/src/types.h b/src/types.h index 3aeb2bcc4..c6613f2f2 100644 --- a/src/types.h +++ b/src/types.h @@ -293,22 +293,31 @@ struct DirtyPiece { // Keep track of what threats change on the board (used by NNUE) struct DirtyThreat { + static constexpr int PcSqOffset = 0; + static constexpr int ThreatenedSqOffset = 8; + static constexpr int ThreatenedPcOffset = 16; + static constexpr int PcOffset = 20; + DirtyThreat() { /* don't initialize data */ } + DirtyThreat(uint32_t raw) : + data(raw) {} DirtyThreat(Piece pc, Piece threatened_pc, Square pc_sq, Square threatened_sq, bool add) { - data = (add << 31) | (pc << 20) | (threatened_pc << 16) | (threatened_sq << 8) | (pc_sq); + data = (uint32_t(add) << 31) | (pc << PcOffset) | (threatened_pc << ThreatenedPcOffset) + | (threatened_sq << ThreatenedSqOffset) | (pc_sq << PcSqOffset); } - Piece pc() const { return static_cast(data >> 20 & 0xf); } - Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } - Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } - Square pc_sq() const { return static_cast(data & 0xff); } - bool add() const { return data >> 31; } + Piece pc() const { return static_cast(data >> 20 & 0xf); } + Piece threatened_pc() const { return static_cast(data >> 16 & 0xf); } + Square threatened_sq() const { return static_cast(data >> 8 & 0xff); } + Square pc_sq() const { return static_cast(data & 0xff); } + bool add() const { return data >> 31; } + uint32_t raw() const { return data; } private: uint32_t data; }; -using DirtyThreatList = ValueList; +using DirtyThreatList = ValueList; // A piece can be involved in at most 8 outgoing attacks and 16 incoming attacks. // Moving a piece also can reveal at most 8 discovered attacks.