From be420bb8d1d5d16a4e24ab6fd706a5ae898eaa85 Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Fri, 4 Sep 2026 09:22:46 -0700 Subject: Add bitboard-backed GetAttacks (section 2/3), verified faster than asm Board-representation migration, sections 2-3 (GetAttacks half): - board.c: VerifyPositionConsistency's bbPieces consistency check (migration section 2), verified clean via gmake TEST=1 with the assert live. - POSITION.bbPawns[2]: new incrementally-maintained per-color pawn location bitboard (chess.h), maintained at the same 6 move.c sites as bbPieces, populated from scratch in fen.c. Distinct from the pawn-hash-keyed bbPawnLocations; this one needs no SEARCHER_THREAD_CONTEXT, so it's reachable from GetAttacks's actual call sites (which only ever have a POSITION*). - data.c/chess.h/main.c: g_RookRayAll/g_BishopRayAll (all 4 per-square ray directions pre-ORed) and g_PawnAttackOriginBB[2][128] startup tables, plus FastFirstBit/FastLastBit (static inline bsf/bsr wrappers, chess.h) -- supporting tables/helpers for the primitive below. - see.c: _WhoAttacksSquareBB (bitboard "who attacks square X" query) and _GetAttacksBB (SEE_LIST-populating PoC wrapping it), side by side with the existing SlowGetAttacks/asm GetAttacks -- not wired into the GetAttacks macro yet (section 6), pure addition. - testsee.c: SeeListsAreEqual made order-independent (SEE() sorts the list right after GetAttacks returns, so order was never semantically significant); TestGetAttacks extended to run _GetAttacksBB as a third comparison across the existing 20,000-random-position sweep; added an interleaved asm/Slow/BB cycles-per-call benchmark across opening/middlegame/endgame positions. - testsup.c: fixed GenerateRandomLegalPosition (used by the sweep above) to maintain bbPieces/bbPawns at its two hand-placement sites -- a latent gap since section 1 that made its own VerifyPositionConsistency legality gate almost always reject generated positions, causing large, variable retry-loop slowdowns. Verified: 20,000-position x every-square x both-colors correctness sweep passes (gmake TEST=1), precommit_check.sh clean (self-test + DEBUG smoke test). Benchmark: _GetAttacksBB is ~0.53-0.55x asm GetAttacks's cycles/call (opening/middlegame) and ~0.89x (endgame) -- faster, not just equivalent, primarily from replacing bbOccupied's up-to-16-iteration pawn loop with two bbPawns ORs, plus a g_PawnAttackOriginBB table lookup replacing per-call pawn-delta arithmetic and per-direction/per-side-group early-outs in the slider walk. See board_representation/MIGRATION.md section 3 for the full writeup, including a reverted approach that measured slower and why, and the CountKingSafetyDefects half's re-scoped (not yet implemented) design. Also confirmed (not caused by this work, not fixed here): a pre-existing non-deterministic MP-race assertion in util.c:1093's PV printing, reproduced independently on a clean HEAD checkout. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Jntky4yGUTyQVaGCXms4F2 --- src/data.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) (limited to 'src/data.c') diff --git a/src/data.c b/src/data.c index 1ebbeff..217f1e8 100755 --- a/src/data.c +++ b/src/data.c @@ -578,6 +578,15 @@ BITBOARD g_RookRayToEdge[4][128]; const int g_RookRayDeltas[4] = { 16, -16, 1, -1 }; // N, S, E, W (0x88) const FLAG g_RookRayPositiveDir[4] = { TRUE, FALSE, TRUE, FALSE }; +// Per-square OR of all 4 g_RookRayToEdge directions -- "every square a +// rook on c could reach on an empty board, regardless of direction." +// One lookup (+ AND against a slider bitboard) to answer "is uSide's +// rook/queen bitboard aligned with c *at all*", vs. 4 separate +// g_RookRayToEdge lookups to discover the same "no" -- see +// _WhoAttacksSquareBB (see.c) for the consumer and +// board_representation/MIGRATION.md section 3 for the writeup. +BITBOARD g_RookRayAll[128]; + void InitializeRookRayTables(void) /** @@ -600,6 +609,7 @@ Return value: COOR c, cSquare; memset(g_RookRayToEdge, 0, sizeof(g_RookRayToEdge)); + memset(g_RookRayAll, 0, sizeof(g_RookRayAll)); for (uRank = 0; uRank < 8; uRank++) { for (uFile = 0; uFile < 8; uFile++) @@ -612,6 +622,7 @@ Return value: cSquare += g_RookRayDeltas[uDir]) { g_RookRayToEdge[uDir][c] |= COOR_TO_BB(cSquare); + g_RookRayAll[c] |= COOR_TO_BB(cSquare); } } } @@ -625,6 +636,9 @@ BITBOARD g_BishopRayToEdge[4][128]; const int g_BishopRayDeltas[4] = { 17, -17, 15, -15 }; // NE, SW, NW, SE (0x88) const FLAG g_BishopRayPositiveDir[4] = { TRUE, FALSE, TRUE, FALSE }; +// g_RookRayAll's counterpart for the bishop's 4 diagonal directions. +BITBOARD g_BishopRayAll[128]; + void InitializeBishopRayTables(void) /** @@ -647,6 +661,7 @@ Return value: COOR c, cSquare; memset(g_BishopRayToEdge, 0, sizeof(g_BishopRayToEdge)); + memset(g_BishopRayAll, 0, sizeof(g_BishopRayAll)); for (uRank = 0; uRank < 8; uRank++) { for (uFile = 0; uFile < 8; uFile++) @@ -659,6 +674,7 @@ Return value: cSquare += g_BishopRayDeltas[uDir]) { g_BishopRayToEdge[uDir][c] |= COOR_TO_BB(cSquare); + g_BishopRayAll[c] |= COOR_TO_BB(cSquare); } } } @@ -720,3 +736,60 @@ Return value: } } } + +// +// Per-square, per-side "the (up to 2) squares a pawn of this side +// would need to stand on to attack c" bitboard -- e.g. +// g_PawnAttackOriginBB[WHITE][c] is c's two SE/SW neighbors (a white +// pawn attacks diagonally forward, so it must stand behind-and-beside +// c to hit it). Same idea as g_KnightAttacksBB: a single lookup+AND +// against bbPawns[side] answers "does uSide have a pawn attacking c" +// entirely in bit-space, no COOR arithmetic/IS_ON_BOARD check at +// runtime -- see _GetAttacksBB (see.c) for the consumer. +// +BITBOARD g_PawnAttackOriginBB[2][128]; + +void +InitializePawnAttackOriginTable(void) +/** + +Routine description: + + One-time startup init for g_PawnAttackOriginBB -- see its comment. + +Parameters: + + void + +Return value: + + void + +**/ +{ + static const int iSeeDelta[2] = { -17, +15 }; // BLACK, WHITE + ULONG uRank, uFile, uSide; + COOR c, cOrigin; + + memset(g_PawnAttackOriginBB, 0, sizeof(g_PawnAttackOriginBB)); + for (uRank = 0; uRank < 8; uRank++) + { + for (uFile = 0; uFile < 8; uFile++) + { + c = (uRank << 4) | uFile; + for (uSide = 0; uSide < 2; uSide++) + { + cOrigin = c + iSeeDelta[uSide]; + if (IS_ON_BOARD(cOrigin)) + { + g_PawnAttackOriginBB[uSide][c] |= COOR_TO_BB(cOrigin); + } + cOrigin += 2; + if (IS_ON_BOARD(cOrigin)) + { + g_PawnAttackOriginBB[uSide][c] |= COOR_TO_BB(cOrigin); + } + } + } + } +} -- cgit v1.3