Hyperbola quintessence for ARM

Passed STC

https://tests.stockfishchess.org/tests/view/69f44c1e1e5788938e86aa2a
LLR: 2.93 (-2.94,2.94) <0.00,2.00>
Total: 27680 W: 7340 L: 7053 D: 13287
Ptnml(0-2): 72, 2862, 7683, 3153, 70

x86 is unaffected

Data from Joost's machine (Neoverse V2):

```
==== master ====
==== Bench: 2723949 ====
1 Nodes/second : 276819933
2 Nodes/second : 273971948
3 Nodes/second : 275567995
Average (over 3):  275453292
==== 5b54755db23a3de29407b805b85e7bbdef28df49 ====
==== Bench: 2723949 ====
1 Nodes/second : 282861542
2 Nodes/second : 279085277
3 Nodes/second : 279612223
Average (over 3):  280519680 (+1.8%)
```

Data from my M1 macbook

```
Result of 100 runs
==================
base (./stockfish    ) =    1474702  +/- 6169
test (...fish.rbit-pr) =    1500236  +/- 6690
diff                   =     +25533  +/- 4011

speedup        = +0.0173
P(speedup > 0) =  1.0000
```

Hyperbola quintessence works nicely on ARM because it has an efficient bit reversal instruction `rbit`. This lets us avoid the large magic bitboards lookup table. Codegen looks nice (this is for rook attacks I think):

```
0000000100001ecc <__ZN9Stockfish10attacks_bbILNS_9PieceTypeE3EEEyNS_6SquareEy>:
100001ecc: 2a0003e8     mov w8, w0
100001ed0: f002cb09     adrp x9, 0x105964000 <dyld_stub_binder+0x105964000>
100001ed4: 91200129     add x9, x9, #0x800
100001ed8: 8b081928     add x8, x9, x8, lsl #6
100001edc: a9402909     ldp x9, x10, [x8]
100001ee0: 8a01012b     and x11, x9, x1
100001ee4: dac0016c     rbit x12, x11
100001ee8: a941210d     ldp x13, x8, [x8, #0x10]
100001eec: cb08018c     sub x12, x12, x8
100001ef0: dac0018c     rbit x12, x12
100001ef4: cb0d016b     sub x11, x11, x13
100001ef8: ca0b018b     eor x11, x12, x11
100001efc: 8a090169     and x9, x11, x9
100001f00: 8a01014b     and x11, x10, x1
100001f04: dac0016c     rbit x12, x11
100001f08: cb080188     sub x8, x12, x8
100001f0c: dac00108     rbit x8, x8
100001f10: cb0d016b     sub x11, x11, x13
100001f14: ca0b0108     eor x8, x8, x11
100001f18: 8a0a0108     and x8, x8, x10
100001f1c: 8b090100     add x0, x8, x9
100001f20: d65f03c0     ret
```

closes https://github.com/official-stockfish/Stockfish/pull/6795

No functional change
This commit is contained in:
anematode
2026-05-04 08:32:23 +02:00
committed by Joost VandeVondele
parent fe1d2b456c
commit dc1686345c
2 changed files with 96 additions and 30 deletions
+38
View File
@@ -62,6 +62,39 @@ std::string Bitboards::pretty(Bitboard b) {
return s; 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 { namespace {
[[maybe_unused]] constexpr Bitboard constexpr_pext(Bitboard b, Bitboard m) { [[maybe_unused]] constexpr Bitboard constexpr_pext(Bitboard b, Bitboard m) {
Bitboard result = 0, bit = 0; Bitboard result = 0, bit = 0;
@@ -199,6 +232,7 @@ std::array<MagicMask, 0x1480> BishopTable;
#endif #endif
} }
#endif
// Initializes various bitboard tables. It is called at // Initializes various bitboard tables. It is called at
// startup and relies on global objects to be already zero-initialized. // startup and relies on global objects to be already zero-initialized.
@@ -211,8 +245,12 @@ void Bitboards::init() {
for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)
SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2)); SquareDistance[s1][s2] = std::max(distance<File>(s1, s2), distance<Rank>(s1, s2));
#ifdef USE_HYPERBOLA_QUINT
init_magics(Magics);
#else
init_magics(ROOK, const_cast<MagicMask*>(RookTable.data()), Magics, true); init_magics(ROOK, const_cast<MagicMask*>(RookTable.data()), Magics, true);
init_magics(BISHOP, const_cast<MagicMask*>(BishopTable.data()), Magics, true); init_magics(BISHOP, const_cast<MagicMask*>(BishopTable.data()), Magics, true);
#endif
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
{ {
+28
View File
@@ -31,6 +31,11 @@
#include "types.h" #include "types.h"
#ifdef __aarch64__
#include <arm_acle.h>
#define USE_HYPERBOLA_QUINT
#endif
namespace Stockfish { namespace Stockfish {
namespace Bitboards { namespace Bitboards {
@@ -74,6 +79,28 @@ extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB];
extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB];
extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB]; extern Bitboard RayPassBB[SQUARE_NB][SQUARE_NB];
#ifdef USE_HYPERBOLA_QUINT
// 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 = __rbitll(o) - rr;
return (fwd ^ __rbitll(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 // Magic holds all magic bitboards relevant data for a single square
struct Magic { struct Magic {
Bitboard mask; Bitboard mask;
@@ -109,6 +136,7 @@ struct Magic {
#endif #endif
} }
}; };
#endif
extern Magic Magics[SQUARE_NB][2]; extern Magic Magics[SQUARE_NB][2];