/*
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
#include "types.h"
#include "bitboard.h"
#ifdef __aarch64__
#include
#define USE_HYPERBOLA_QUINT
#elif defined(__loongarch__) && __loongarch_grlen == 64
#define USE_HYPERBOLA_QUINT
#elif defined(USE_AVX2)
#include
#define USE_DUAL_HYPERBOLA_QUINT
#endif
namespace Stockfish::Attacks {
void init();
#ifdef USE_HYPERBOLA_QUINT
inline Bitboard reverse_bb(Bitboard bb) {
#if __has_builtin(__builtin_bitreverse64)
return __builtin_bitreverse64(bb);
#else
#ifdef __aarch64__
#if defined(__GNUC__) && !defined(__clang__) \
&& (__GNUC__ < 12 || (__GNUC__ == 12 && __GNUC_MINOR__ < 2))
// no rbit in arm_acle.h
Bitboard out;
asm("rbit %0, %1" : "=r"(out) : "r"(bb));
return out;
#else
return __rbitll(bb);
#endif
#else // loongarch
Bitboard out;
asm("bitrev.d %0, %1" : "=r"(out) : "r"(bb));
return out;
#endif
#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;
Bitboard hyperbola(Square s, Bitboard occupied, Bitboard mask) const {
Bitboard o = occupied & mask;
Bitboard fwd = o - square_bb(s);
Bitboard rev = reverse_bb(o) - square_bb(Square(63 - int(s)));
return (fwd ^ reverse_bb(rev)) & mask;
}
Bitboard attacks_bb(Square s, Bitboard occupied) const {
return hyperbola(s, occupied, mask1) | hyperbola(s, occupied, mask2);
}
};
const Magic& magic(Square s, PieceType pt);
#elif defined(USE_DUAL_HYPERBOLA_QUINT)
struct alignas(32) DualMagic {
// file, diagonal, unused, antidiagonal
Bitboard maskFile, maskDiag, maskNone, maskAntidiag;
// Precomputed 2 * square_bb(sq), 2 * reverse(square_bb(sq))
Bitboard r, rr;
const u8* 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 attacks 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 128-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};
}
};
extern DualMagic DualMagics[SQUARE_NB];
inline const DualMagic& dual_magic(Square s) { return DualMagics[s]; }
#else
// Magic holds all magic bitboards relevant data for a single square
struct Magic {
Bitboard mask;
Bitboard* attacks;
Bitboard magic;
unsigned shift;
// Compute the attack's index using the 'magic bitboards' approach
unsigned index(Bitboard occupied) const {
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;
}
Bitboard attacks_bb([[maybe_unused]] Square s, Bitboard occupied) const {
return attacks[index(occupied)];
}
};
const Magic& magic(Square s, PieceType pt);
#endif
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
inline Bitboard line_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2));
return LineBB[s1][s2];
}
inline Bitboard between_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2));
return BetweenBB[s1][s2];
}
inline Bitboard ray_pass_bb(Square s1, Square s2) {
assert(is_ok(s1) && is_ok(s2));
return RayPassBB[s1][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 past an occupied square.
template
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 :
case ROOK :
return magic(s, Pt).attacks_bb(s, occupied);
case QUEEN :
return attacks_bb(s, occupied) | attacks_bb(s, occupied);
default :
return PseudoAttacks[Pt][s];
}
#endif
}
inline std::pair both_attacks_bb(Square s, Bitboard occupied) {
#ifdef USE_DUAL_HYPERBOLA_QUINT
return dual_magic(s).both_attacks_bb(occupied);
#else
return {attacks_bb(s, occupied), attacks_bb(s, occupied)};
#endif
}
// Returns the attacks by the given piece
// assuming the board is occupied according to the passed Bitboard.
// Sliding piece attacks do not continue past 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);
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