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/chess.h | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) (limited to 'src/chess.h') diff --git a/src/chess.h b/src/chess.h index 1f874b9..5c5bbf2 100755 --- a/src/chess.h +++ b/src/chess.h @@ -658,6 +658,17 @@ typedef struct _POSITION // (cNonPawns[color][0]), a bitboard adds nothing. BITBOARD bbPieces[2][8]; + // Per-color pawn location bitboard -- same incremental-maintenance + // idea as bbPieces above, but for pawns (which bbPieces + // deliberately excludes). Exists so _BuildOccupiedBB (see.c) can + // build full-board occupancy via two ORs instead of looping + // cPawns[2][8] (up to 16 iterations) on every call -- see + // board_representation/MIGRATION.md section 3. Distinct from + // pHash->bbPawnLocations[2] (pawn-hash-keyed, tied to pawn-eval + // caching); this one is plain POSITION state, reachable without a + // SEARCHER_THREAD_CONTEXT. + BITBOARD bbPawns[2]; + ULONG uWhiteSqBishopCount[2]; // num bishops on white squares SCORE iMaterialBalance[2]; // material balance @@ -2027,12 +2038,15 @@ extern BITBOARD BBADJACENT_RANKS[9]; // board_representation/MIGRATION.md) the planned bbPieces-backed // GetAttacks/CountKingSafetyDefects primitive. extern BITBOARD g_RookRayToEdge[4][128]; +extern BITBOARD g_RookRayAll[128]; extern const int g_RookRayDeltas[4]; extern const FLAG g_RookRayPositiveDir[4]; extern BITBOARD g_BishopRayToEdge[4][128]; +extern BITBOARD g_BishopRayAll[128]; extern const int g_BishopRayDeltas[4]; extern const FLAG g_BishopRayPositiveDir[4]; extern BITBOARD g_KnightAttacksBB[128]; +extern BITBOARD g_PawnAttackOriginBB[2][128]; void InitializeWhiteSquaresTable(void); @@ -2055,6 +2069,9 @@ InitializeBishopRayTables(void); void InitializeKnightAttackTables(void); +void +InitializePawnAttackOriginTable(void); + #ifdef DEBUG ULONG CheckVectorWithIndex(int i, ULONG uColor); #define CHECK_VECTOR_WITH_INDEX(i, color) \ @@ -2797,6 +2814,31 @@ SlowFirstBit(BITBOARD bb); ULONG CDECL SlowLastBit(BITBOARD bb); +// Compiler-builtin (__builtin_ctzll/__builtin_clzll) bsf/bsr -- same +// 1-based/0-for-empty contract as FirstBit/LastBit below, and the +// exact same bsf/bsr instruction the asm FirstBit/LastBit use (this +// build passes no -mbmi, so __builtin_ctzll still lowers to bsf, not +// tzcnt) -- but static inline, so a call site pays for the +// instruction itself and nothing else, no CDECL call/ret/arg-marshal +// overhead. That overhead is exactly what makes it worth having a +// second copy instead of just calling FirstBit/LastBit everywhere: +// worthwhile in a per-move-generated, per-search-node hot path, +// pointless as a blanket replacement elsewhere. static (not extern) +// deliberately -- a plain non-static C99 "inline" definition with no +// out-of-line instantiation anywhere is a link-time trap, not just a +// style choice. +static ULONG INLINE +FastFirstBit(IN BITBOARD bb) +{ + return bb ? ((ULONG)__builtin_ctzll(bb) + 1) : 0; +} + +static ULONG INLINE +FastLastBit(IN BITBOARD bb) +{ + return bb ? (ULONG)(64 - __builtin_clzll(bb)) : 0; +} + #ifdef CROUTINES #define CountBits SlowCountBits #define FirstBit SlowFirstBit @@ -2856,6 +2898,14 @@ SlowGetAttacks(SEE_LIST *pList, #define GetAttacks SlowGetAttacks #endif +// board_representation/MIGRATION.md section 3: bbPieces-backed +// GetAttacks PoC -- not wired into the GetAttacks macro above yet. +void CDECL +_GetAttacksBB(SEE_LIST *pList, + POSITION *pos, + COOR cSquare, + ULONG uSide); + #ifdef _X86_ // // Note: this is most of the stuff that x86.asm assumes about the -- cgit v1.3