diff options
| author | Scott Gasch <[email protected]> | 2026-09-05 00:52:13 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-09-05 00:52:13 -0700 |
| commit | 57502d6e205bc802fc9b89fd1534add5efc4b9ea (patch) | |
| tree | cd400f2538d093a41ceaee932fc21df5dc5f54e1 /src/chess.h | |
| parent | 2ce3570dc6aa67f82dd6f02fc4e518be3e33aabf (diff) | |
Retire pawns' bvAttacks writes in favor of pos->bbPawnAttacks[2]
First piece type converted per board_representation/EVAL.md section 2:
pawns no longer write into rgSquare[c|8].bvAttacks at all. Every
consumer of "does a pawn attack this square" now reads
pos->bbPawnAttacks[2] directly -- a plain bitboard, computed fresh
each Eval() call from pos->bbPawns[] via the same shift-and-mask
technique generate.c's _GenerateAllPawnMovesBB already uses (zero
per-pawn mailbox iteration, vs. up to 16 delta+IS_ON_BOARD checks
before). Knight/bishop/rook/queen/king are unchanged -- still
populate/read their own bvAttacks bits (uMinor/uRook/uQueen/uKing)
the old way until their own conversions land.
Consumer changes, all in eval.c:
- UNSAFE_FOR_MINOR retired as a macro (it only ever tested the pawn
bit) -- its 3 call sites (knight, bishop x2) now test
bbPawnAttacks directly.
- UNSAFE_FOR_ROOK/_QUEEN masks narrowed to drop the now-dead pawn
bit; call sites OR in an explicit bbPawnAttacks test alongside the
narrowed bvAttacks read.
- Two direct .small.uPawn reads (bishop's transient-pawn mobility
credit, bishop's defended-pawn bonus) switched to bbPawnAttacks
tests.
- _EvalKing's bvAttack/bvDefend (the real king-danger computation)
OR the bitboard bit back in at both read points, careful to
preserve the original ordering where bvDefend must reflect the
king's own just-set defend bit.
- _WhoControlsSquareFast (used by passer-race/trapped-piece/danger
code) ORs the bitboard bit back into its g_SwapTable index at the
same bit position PAWN_BIT always occupied.
- Two bugs caught by manually auditing every remaining |8 site after
the fact (not by any test failing): _EvalPawns' own pawn-duo and
backward-pawn detection read bvAttacks.uWholeThing at a point in
Eval()'s sequence where only pawns could have written it -- once
pawns stopped writing there, both checks went permanently dead
silently. Fixed to read bbPawnAttacks directly. No self-test caught
this; it's exactly the gap EVAL.md section 5's planned exact-score
harness is meant to close.
EVAL_TIME instrumentation: split _EvalPawns' cycle counter into
pawn-hash hit/miss buckets (chess.h/eval.c/root.c), answering whether
the hash is still worth it now that attack-bit population is nearly
free. Measured on one sd12 benchmark position: hits average 90.4
cycles, misses average 1741.3 cycles (~19x), 97.15% hit rate -- the
hash stays a clear win; the miss cost was never mostly attack-bit
population (that's a separate ~1% bucket now, down from ~3.6%), it's
the isolated/doubled/duo/backward-pawn scoring loops, which still do
real per-pawn work on a miss.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
Diffstat (limited to 'src/chess.h')
| -rwxr-xr-x | src/chess.h | 51 |
1 files changed, 48 insertions, 3 deletions
diff --git a/src/chess.h b/src/chess.h index 41c8734..7ff9994 100755 --- a/src/chess.h +++ b/src/chess.h @@ -595,9 +595,17 @@ typedef union _ATTACK_BITV } ATTACK_BITV; -#define UNSAFE_FOR_MINOR(x) ((ULONG)((x).uWholeThing) & 0x00000080UL) -#define UNSAFE_FOR_ROOK(x) ((ULONG)((x).uWholeThing) & 0x000000C0UL) -#define UNSAFE_FOR_QUEEN(x) ((ULONG)((x).uWholeThing) & 0x000000E0UL) +// UNSAFE_FOR_MINOR retired as a bvAttacks-based macro 2026-09-05: +// pawns no longer write PAWN_BIT into bvAttacks (see POSITION's +// bbPawnAttacks[2] comment, chess.h) -- it was PAWN_BIT alone, so +// every former call site now just tests +// (pos->bbPawnAttacks[enemy] & COOR_TO_BB(sq)) directly, no macro +// needed. UNSAFE_FOR_ROOK/_QUEEN's masks below are narrowed to drop +// PAWN_BIT (0x80), which would otherwise silently always read 0 now +// that nothing sets it -- callers combine these with an explicit +// bbPawnAttacks test instead (see eval.c's _EvalRook/_EvalQueen). +#define UNSAFE_FOR_ROOK(x) ((ULONG)((x).uWholeThing) & 0x00000040UL) +#define UNSAFE_FOR_QUEEN(x) ((ULONG)((x).uWholeThing) & 0x00000060UL) #define PAWN_BIT 0x00000080UL #define MINOR_BIT 0x00000040UL @@ -688,6 +696,26 @@ typedef struct _POSITION // not as something callers should call directly anymore. BITBOARD bbOccupied; + // First mover of board_representation/EVAL.md section 2's + // bvAttacks replacement, added 2026-09-05: "which squares does + // this side's pawns attack," computed fresh once per Eval() call + // from bbPawns via a single shift-and-mask (see + // _PopulatePawnAttackBits in eval.c, same technique as + // generate.c's _GenerateAllPawnMovesBB) -- zero per-pawn mailbox + // iteration, so unlike bbPieces/bbPawns/bbOccupied above this is + // NOT incrementally maintained across moves; it's plain Eval()- + // scoped scratch space, recomputed every call the same way + // pos->iScore[] is. Pawns no longer write their attack bit into + // rgSquare[c|8].bvAttacks at all -- every consumer of "does an + // enemy/friendly pawn attack this square" reads this bitboard + // directly instead (UNSAFE_FOR_MINOR/_ROOK/_QUEEN's pawn + // component, _EvalKing's bvAttack/bvDefend). Knight/bishop/rook/ + // queen/king still populate/read bvAttacks for their own bits + // (uMinor/uRook/uQueen/uKing) until their own conversions land -- + // see EVAL.md section 2 for the planned bbMinorAttacks/ + // bbRookAttacks/bbQueenAttacks that will retire the rest of it. + BITBOARD bbPawnAttacks[2]; + ULONG uWhiteSqBishopCount[2]; // num bishops on white squares SCORE iMaterialBalance[2]; // material balance @@ -926,6 +954,23 @@ typedef struct _COUNTERS // its own accumulator directly. // UINT64 u64CyclesEvalPawns; + // + // Split of the counter above by pawn-hash outcome (board_ + // representation/EVAL.md section 0c/0d's "is the hash still + // worth it now that attack-bit population is cheap" question) + // -- Hit = _EvalPawns returned early via the hash (fDeferred + // TRUE); Miss = full recompute, including + // _PopulatePawnAttackBits and the isolated/doubled/duo/ + // backward-pawn scoring loops (fDeferred FALSE). Hit+Miss + // calls sum to u64FullEvals-ish call count, not to + // u64CyclesEvalPawns exactly (that's still measured as one + // contiguous window; these are the same window, just bucketed + // by outcome after the fact). + // + UINT64 u64CyclesEvalPawnsHit; + UINT64 u64CyclesEvalPawnsMiss; + UINT64 u64CountEvalPawnsHit; + UINT64 u64CountEvalPawnsMiss; UINT64 u64CyclesEvalKnight; UINT64 u64CyclesEvalBishop; UINT64 u64CyclesEvalRook; |
