From 5c8d794782d3be6368dbba613ef129b11d878d97 Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Fri, 4 Sep 2026 01:03:00 -0700 Subject: Fix passed-pawn bitboard bit-clear bug, LMR gate coupling, inline hot bitboard helpers - bitboard.c: CoorFromBitBoardRank1ToRank8 cleared the lowest set bit unconditionally instead of the reported (highest) one, silently mis-walking doubled-pawn files in eval.c's passed-pawn detection. - search.c/searchsup.c: move GetLMRReduction's precondition checks from inside the function to the caller in search.c (pre-existing work), finishing the split with a matching gate in split.c's HelpSearch -- the parallel-search call site had no gate at all, letting it call GetLMRReduction unconditionally (including for checking moves), reachable only under real multithreading (--cpus > 1) and the intermittent root cause of assertion crashes seen under --cpus 4. - eval.c: redirect CountBits/CoorFromBitBoardRank8ToRank1/ CoorFromBitBoardRank1ToRank8 to inline compiler-builtin versions (gated !CROUTINES) instead of the real out-of-line asm calls, on eval.c's ~20 existing production call sites. CountBits' asm body isn't O(1) popcnt, it's a Kernighan bit-clearing loop plus call overhead, paid on every Eval() call. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Jntky4yGUTyQVaGCXms4F2 --- src/eval.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) (limited to 'src/eval.c') diff --git a/src/eval.c b/src/eval.c index 3e856f1..23f830c 100755 --- a/src/eval.c +++ b/src/eval.c @@ -21,6 +21,68 @@ Revision History: #include "chess.h" +// +// CountBits and the CoorFromBitBoard{Rank8ToRank1,Rank1ToRank8} +// wrappers (bitboard.c) are real out-of-line asm calls on every one of +// eval.c's ~20 production call sites (passed-pawn detection, bishop- +// pair logic, etc.), hit on every real Eval() call. CountBits' asm +// body (x64.asm) isn't even O(1) popcnt, it's a Kernighan bit-clearing +// loop -- O(popcount) iterations plus call overhead. Redirected via +// #define, the same mechanism chess.h already uses for the OTHER +// direction (#ifdef CROUTINES routes CountBits/FirstBit/LastBit to the +// slow C fallbacks) -- this routes them to fast inline compiler +// builtins instead, for every existing call site below with no further +// edits. Gated on !CROUTINES so the CROUTINES debug/comparison build +// still gets the real (slow, cross-checked) implementations. +// +// _FastCoorFromBitBoardRank1ToRank8 also bakes in the wrong-bit-clear +// fix applied to bitboard.c's real CoorFromBitBoardRank1ToRank8 (that +// function used to clear the LOWEST set bit via `*pbb &= (*pbb - 1)` +// regardless of uLastBit, correct only when a single bit was set) -- +// this version clears the bit it actually just reported, via BBSQUARE. +// +#ifndef CROUTINES +static ULONG INLINE +_FastCountBits(IN BITBOARD bb) +{ + return (ULONG)__builtin_popcountll(bb); +} + +static COOR INLINE +_FastCoorFromBitBoardRank8ToRank1(IN OUT BITBOARD *pbb) +{ + COOR c = ILLEGAL_COOR; + ULONG uBitIndex; + + if (*pbb) + { + uBitIndex = (ULONG)__builtin_ctzll(*pbb); + c = BIT_NUMBER_TO_COOR(uBitIndex); + *pbb &= (*pbb - 1); + } + return c; +} + +static COOR INLINE +_FastCoorFromBitBoardRank1ToRank8(IN OUT BITBOARD *pbb) +{ + COOR c = ILLEGAL_COOR; + ULONG uBitIndex; + + if (*pbb) + { + uBitIndex = (ULONG)(63 - __builtin_clzll(*pbb)); + c = BIT_NUMBER_TO_COOR(uBitIndex); + *pbb &= ~BBSQUARE[uBitIndex]; + } + return c; +} + +#define CountBits _FastCountBits +#define CoorFromBitBoardRank8ToRank1 _FastCoorFromBitBoardRank8ToRank1 +#define CoorFromBitBoardRank1ToRank8 _FastCoorFromBitBoardRank1ToRank8 +#endif // !CROUTINES + // // Bishop-mobility ray-walk outcome categories -- see BMobCaseTable in -- cgit v1.3