summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-18 23:43:55 -0700
committerScott Gasch <[email protected]>2026-09-18 23:43:55 -0700
commitb70be6fde0fef0694e404e16d70f00c73dd971a2 (patch)
treed5523636bf76bd53f8ac4a60e508c458fa3c2a51 /src
parent004a5736c6d41747d8fc0e15d230ab80bd6cced6 (diff)
see.c: replace _WhoAttacksSquareBB's ray-walk with magic-bitboard lookups
_RookAttacksBB/_BishopAttacksBB (generate.c) are already O(1) FORCEINLINE magic-table lookups, proven and already used everywhere else in this codebase (eval.c, searchsup.c, generate.c's own move generation) -- this function alone predated that migration and was left on a manual per-direction ray-walk (4 rook + 4 bishop directions, nearest-blocker isolation via FastFirstBit/FastLastBit). King handling is also branchless now: king adjacency is symmetric, so g_KingAttacksBB[cSquare] doubles as "every square a king standing there would attack cSquare from," same O(1)-table-lookup shape the knight line already uses -- no DISTANCE()==1 branch needed. Old ray-walk kept as _WhoAttacksSquareRayBB, testsee.c's TestWhoAttacksSquareMagic correctness-diff reference (20000 random positions x every square x both colors, clean) -- same convention as _AddXRaysMailbox next to _AddXRaysBB from an earlier SEE investigation this session. Isolated benchmark: magic is 0.95x-0.99x ray-walk in a standalone 3-position microbenchmark (small, since the ray-walk already has its own early-out for "no slider anywhere near this square," per its own header comment). Measured end-to-end (sd10 + sn5M, all three curated suites, before vs. after): node counts and solve counts bit-for-bit identical (pure algorithmic swap, zero behavior change). Wall-clock ~0.8% faster overall, all 6 measurements faster, none slower. Co-Authored-By: Claude Sonnet 5 <[email protected]>
Diffstat (limited to 'src')
-rwxr-xr-xsrc/chess.h22
-rw-r--r--src/main.c1
-rw-r--r--src/see.c95
-rw-r--r--src/testsee.c105
4 files changed, 208 insertions, 15 deletions
diff --git a/src/chess.h b/src/chess.h
index 802c89d..e98c99f 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -3442,6 +3442,9 @@ TestIsAttackedBB(void);
void
TestSeeFastSign(void);
+void
+TestWhoAttacksSquareMagic(void);
+
//
// hash.c
//
@@ -3954,17 +3957,26 @@ _GetAttacksBB(SEE_LIST *pList,
COOR cSquare,
ULONG uSide);
-// board_representation/MOVEGEN_MIGRATION.md section 6a: exposed (not
-// static) so generate.c's Part B king-flight code can call it with a
-// king-vacated occupancy bitboard. Deliberately excludes pawns -- see
-// its own header comment in see.c; callers needing pawn attacks must
-// check g_PawnAttackOriginBB separately.
+// Magic-bitboard implementation (as of 2026-09-19) -- exposed (not
+// static) so generate.c's Part B king-flight code and movesup.c can
+// call it with a king-vacated occupancy bitboard. Deliberately
+// excludes pawns -- see its own header comment in see.c; callers
+// needing pawn attacks must check g_PawnAttackOriginBB separately.
BITBOARD
_WhoAttacksSquareBB(POSITION *pos,
COOR cSquare,
ULONG uSide,
BITBOARD bbOccupied);
+// Ray-walk reference implementation, superseded by the magic version
+// above -- see its own header comment in see.c. Exposed non-static so
+// testsee.c can diff it against the magic version.
+BITBOARD
+_WhoAttacksSquareRayBB(POSITION *pos,
+ COOR cSquare,
+ ULONG uSide,
+ BITBOARD bbOccupied);
+
// _GetAttacksBB is the only implementation as of 2026-09-06 (see
// MIGRATION.md section 7) -- this macro exists only so call sites
// written against the name "GetAttacks" didn't need touching when the
diff --git a/src/main.c b/src/main.c
index 736a5aa..dc21248 100644
--- a/src/main.c
+++ b/src/main.c
@@ -616,6 +616,7 @@ Return value:
TestGetAttacks();
TestIsAttackedBB();
TestSeeFastSign();
+ TestWhoAttacksSquareMagic();
TestMoveGenerator();
TestLegalMoveGenerator();
TestGenerateKnightSpeed();
diff --git a/src/see.c b/src/see.c
index 2edd6c5..dd60537 100644
--- a/src/see.c
+++ b/src/see.c
@@ -47,17 +47,24 @@ Revision History:
// EVAL.md section 0. Callers below just read pos->bbOccupied.)
//
-// Non-static (unlike its historical file-local status) so
-// generate.c's Part B (_GenerateEscapes) bitboard work can call it
-// directly for king-flight safety testing, passing an occupancy
-// bitboard with the king itself removed to correctly account for
-// x-ray/discovered attacks when the king steps out of a slider's way
-// -- see board_representation/MOVEGEN_MIGRATION.md section 6a.
+// Ray-walk reference implementation -- superseded 2026-09-19 by the
+// magic-bitboard version below (_WhoAttacksSquareBB itself now uses
+// magic lookups; this name is kept only as testsee.c's
+// TestWhoAttacksSquareMagic correctness-diff reference, same
+// convention as _AddXRaysMailbox next to _AddXRaysBB). Non-static
+// (unlike its historical file-local status) so generate.c's Part B
+// (_GenerateEscapes) bitboard work could call it directly for
+// king-flight safety testing, passing an occupancy bitboard with the
+// king itself removed to correctly account for x-ray/discovered
+// attacks when the king steps out of a slider's way -- see
+// board_representation/MOVEGEN_MIGRATION.md section 6a. (That real
+// call site, and _GetAttacksBB's, now go through the magic version
+// below instead.)
BITBOARD
-_WhoAttacksSquareBB(IN POSITION *pos,
- IN COOR cSquare,
- IN ULONG uSide,
- IN BITBOARD bbOccupied)
+_WhoAttacksSquareRayBB(IN POSITION *pos,
+ IN COOR cSquare,
+ IN ULONG uSide,
+ IN BITBOARD bbOccupied)
/**
Routine description:
@@ -183,6 +190,74 @@ Return value:
return bbAttackers;
}
+// Magic-bitboard implementation -- the real _WhoAttacksSquareBB as of
+// 2026-09-19, all production callers (movesup.c, generate.c,
+// _GetAttacksBB/SeeFastSign below) go through this name.
+// _RookAttacksBB/_BishopAttacksBB (generate.c) are already O(1)
+// FORCEINLINE magic-table lookups (one multiply, one shift, one table
+// read), proven and already used everywhere else in this codebase
+// (eval.c, searchsup.c, generate.c's own move generation) -- this
+// function alone predated that migration and was left on the manual
+// per-direction ray-walk (_WhoAttacksSquareRayBB above, kept as
+// testsee.c's correctness-diff reference). King handling is branchless:
+// king adjacency is symmetric, so g_KingAttacksBB[cSquare] (every
+// square one king-step from cSquare) doubles as "every square a king
+// standing there would attack cSquare from" -- ANDing it against a
+// single-bit bitboard of the actual king square is either that bit or
+// zero, same O(1)-table-lookup shape the knight line already uses, no
+// DISTANCE()==1 branch needed.
+BITBOARD
+_WhoAttacksSquareBB(IN POSITION *pos,
+ IN COOR cSquare,
+ IN ULONG uSide,
+ IN BITBOARD bbOccupied)
+/**
+
+Routine description:
+
+ Return a bitboard of every uSide knight/bishop/rook/queen/king
+ that attacks cSquare in the current position, blockers included.
+ Pawns are deliberately excluded -- see _GetAttacksBB. Magic-
+ bitboard equivalent of the historical ray-walk implementation
+ (_WhoAttacksSquareRayBB above) -- see that function's description
+ for what this replaces.
+
+Parameters:
+
+ POSITION *pos,
+ COOR cSquare : target square
+ ULONG uSide : side whose attackers on cSquare we want
+ BITBOARD bbOccupied : full-board occupancy (see pos->bbOccupied)
+
+Return value:
+
+ BITBOARD
+
+**/
+{
+ BITBOARD bbAttackers;
+ BITBOARD bbRookSliders;
+ BITBOARD bbBishopSliders;
+
+ // Branchless: king adjacency is symmetric, so g_KingAttacksBB[cSquare]
+ // (every square one king-step from cSquare) doubles as "every square
+ // a king standing there would attack cSquare from" -- ANDing it
+ // against a single-bit bitboard of the actual king square is either
+ // that bit or zero, exactly matching what the DISTANCE()==1 branch
+ // computed, same O(1)-table-lookup shape the knight line above
+ // already uses.
+ bbAttackers = g_KnightAttacksBB[cSquare] & pos->bbPieces[uSide][KNIGHT];
+ bbAttackers |= g_KingAttacksBB[cSquare] & COOR_TO_BB(KingPosition(pos, uSide));
+
+ bbRookSliders = pos->bbPieces[uSide][ROOK] | pos->bbPieces[uSide][QUEEN];
+ bbAttackers |= _RookAttacksBB(cSquare, bbOccupied) & bbRookSliders;
+
+ bbBishopSliders = pos->bbPieces[uSide][BISHOP] | pos->bbPieces[uSide][QUEEN];
+ bbAttackers |= _BishopAttacksBB(cSquare, bbOccupied) & bbBishopSliders;
+
+ return bbAttackers;
+}
+
void CDECL
_GetAttacksBB(IN OUT SEE_LIST *pList,
IN POSITION *pos,
diff --git a/src/testsee.c b/src/testsee.c
index 6346697..bbcd7e0 100644
--- a/src/testsee.c
+++ b/src/testsee.c
@@ -519,4 +519,109 @@ TestSeeFastSign(void)
free(ctx2);
}
}
+
+// _WhoAttacksSquareMagicBB (see.c) replaces _WhoAttacksSquareBB's
+// manual per-direction ray-walk with the same O(1) magic-bitboard
+// lookups (_RookAttacksBB/_BishopAttacksBB, generate.c) already used
+// everywhere else in this codebase. Same correctness+speed pattern as
+// the other Test* functions here.
+void
+TestWhoAttacksSquareMagic(void)
+{
+ POSITION pos;
+ ULONG u;
+ COOR c;
+ ULONG uSide;
+ BITBOARD bbRay;
+ BITBOARD bbMagic;
+ const ULONG uPositions = 20000;
+
+ Trace("Testing _WhoAttacksSquareMagicBB (magic vs ray-walk)...\n");
+ for (u = 0; u < uPositions; u++)
+ {
+ GenerateRandomLegalPosition(&pos);
+ FOREACH_SQUARE(c)
+ {
+ if (!IS_ON_BOARD(c)) continue;
+ for (uSide = BLACK; uSide <= WHITE; uSide++)
+ {
+ bbRay = _WhoAttacksSquareRayBB(&pos, c, uSide, pos.bbOccupied);
+ bbMagic = _WhoAttacksSquareBB(&pos, c, uSide,
+ pos.bbOccupied);
+ if (bbRay != bbMagic)
+ {
+ Trace("cSquare=%d uSide=%lu bbRay=0x%llx bbMagic=0x%llx\n",
+ c, uSide, bbRay, bbMagic);
+ UtilPanic(TESTCASE_FAILURE,
+ &pos,
+ "_WhoAttacksSquareBB/ray-walk mismatch",
+ NULL, NULL,
+ __FILE__, __LINE__);
+ }
+ }
+ }
+ }
+
+ //
+ // Speed: same isolated, interleaved cycles/call methodology as the
+ // other benchmarks above.
+ //
+ {
+ static const char *rgszFen[3] =
+ {
+ "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
+ "r1bq1rk1/pp2bppp/2n1pn2/2pp4/3P4/2NBPN2/PP3PPP/R1BQ1RK1 w - - 0 1",
+ "8/5k2/8/3K4/8/8/8/4R3 w - - 0 1",
+ };
+ static const char *rgszLabel[3] =
+ {
+ "opening ", "middlegame", "endgame ",
+ };
+ POSITION posBench;
+ BITBOARD bbSink;
+ UINT64 u64RayTotal, u64MagicTotal, u64Start;
+ ULONG uIter;
+ ULONG uSq;
+ COOR cBench;
+ ULONG uSideBench;
+ const ULONG uCallsPerPosition = 200000;
+
+ Trace("Benchmarking _WhoAttacksSquareBB: ray-walk vs magic "
+ "(interleaved, %lu calls/position)...\n",
+ uCallsPerPosition);
+ for (u = 0; u < 3; u++)
+ {
+ FenToPosition(&posBench, (char *)rgszFen[u]);
+ u64RayTotal = 0;
+ u64MagicTotal = 0;
+ for (uIter = 0; uIter < uCallsPerPosition; uIter++)
+ {
+ uSq = uIter % 64;
+ cBench = BIT_NUMBER_TO_COOR(uSq);
+ uSideBench = uIter & 1;
+ if (!IS_ON_BOARD(cBench)) continue;
+
+ u64Start = SystemReadTimeStampCounter();
+ bbSink = _WhoAttacksSquareRayBB(&posBench, cBench, uSideBench,
+ posBench.bbOccupied);
+ u64RayTotal += (SystemReadTimeStampCounter() - u64Start);
+
+ u64Start = SystemReadTimeStampCounter();
+ bbSink = _WhoAttacksSquareBB(&posBench, cBench,
+ uSideBench,
+ posBench.bbOccupied);
+ u64MagicTotal += (SystemReadTimeStampCounter() - u64Start);
+ (void)bbSink;
+ }
+ printf(" %s: ray-walk %" COMPILER_LONGLONG_UNSIGNED_FORMAT
+ " cycles/call, magic %"
+ COMPILER_LONGLONG_UNSIGNED_FORMAT " cycles/call "
+ "(magic is %.2fx ray-walk)\n",
+ rgszLabel[u],
+ u64RayTotal / uCallsPerPosition,
+ u64MagicTotal / uCallsPerPosition,
+ (double)u64MagicTotal / (double)u64RayTotal);
+ }
+ }
+}
#endif // TEST