From 57502d6e205bc802fc9b89fd1534add5efc4b9ea Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Sat, 5 Sep 2026 00:52:13 -0700 Subject: 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 Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj --- src/root.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/root.c') diff --git a/src/root.c b/src/root.c index 5ce4c9e..0ab42f7 100755 --- a/src/root.c +++ b/src/root.c @@ -614,6 +614,22 @@ Return value: Trace(" attack-table pop: %5.1f%% unaccounted: %5.1f%%\n", (u64Total ? (100.0 * (double)u64AttackPop / (double)u64Total) : 0.0), (u64Total ? (100.0 * (double)u64Unaccounted / (double)u64Total) : 0.0)); + { + UINT64 u64PHits = ctx->sCounters.tree.u64CountEvalPawnsHit; + UINT64 u64PMisses = ctx->sCounters.tree.u64CountEvalPawnsMiss; + UINT64 u64PHitCycles = ctx->sCounters.tree.u64CyclesEvalPawnsHit; + UINT64 u64PMissCycles = ctx->sCounters.tree.u64CyclesEvalPawnsMiss; + Trace(" pawn hash: %" COMPILER_LONGLONG_UNSIGNED_FORMAT + " hits (avg %.1f cyc), %" COMPILER_LONGLONG_UNSIGNED_FORMAT + " misses (avg %.1f cyc), hit rate %.2f%%\n", + u64PHits, + (u64PHits ? (double)u64PHitCycles / (double)u64PHits : 0.0), + u64PMisses, + (u64PMisses ? (double)u64PMissCycles / (double)u64PMisses : 0.0), + ((u64PHits + u64PMisses) ? + (100.0 * (double)u64PHits / (double)(u64PHits + u64PMisses)) : + 0.0)); + } } #endif #endif -- cgit v1.3