summaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-04 23:54:51 -0700
committerScott Gasch <[email protected]>2026-09-04 23:54:51 -0700
commit5883f5a64f4f464b877b7637b8e13c25f7d208fc (patch)
tree97951281d86a02254be5682356fa5b29a5076a56 /src/eval.c
parent92fc41226f784b251f41eab7e75c13075e980a54 (diff)
Add Eval() bitboard-migration plan, per-term EVAL_TIME cycle breakdown, drop redundant pawn-location bitboard
board_representation/EVAL.md: rewritten migration plan for a bitboard-backed Eval() (mobility ray-walks + bvAttacks replacement), plus a performance-philosophy section recording the profiling-first, cut-aggressively-except-mobility/safety-awareness approach agreed on this session, and findings on CountKingSafetyDefects' structural inability to share bvAttacks-derived state with _EvalKing. EVAL_TIME per-term instrumentation (chess.h/eval.c/root.c): breaks the existing whole-Eval() cycle counter down by pawns/knight/bishop/ rook/queen/king, the always-paid pre-lazy-exit segment, and the full-eval-only post-lazy segment, printed alongside the existing "Avg. cpu cycles in eval" line. Diagnostic only (EVAL_TIME-gated), no effect on the normal release profile. Drop PAWN_HASH_ENTRY's bbPawnLocations[2]: it duplicated POSITION's own incrementally-maintained bbPawns[2], rebuilt bit-by-bit on every pawn-hash miss for no reason. eval.c now reads pos->bbPawns[] directly; removed a stale per-iteration invariant assert in _EvalPawns that only made sense when the bitboard was being built bit-by-bit in that same loop. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
Diffstat (limited to 'src/eval.c')
-rwxr-xr-xsrc/eval.c120
1 files changed, 75 insertions, 45 deletions
diff --git a/src/eval.c b/src/eval.c
index 23f830c..17793da 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -1344,7 +1344,7 @@ Return value:
if (pHash->uCountPerFile[FLIP(uColor)][uPawnFile] != 0)
{
- ASSERT((pHash->bbPawnLocations[FLIP(uColor)] & BBFILE[FILE(c)]) != 0);
+ ASSERT((pos->bbPawns[FLIP(uColor)] & BBFILE[FILE(c)]) != 0);
//
// The only way a pawn can be a passer/candidate if the other
@@ -1355,7 +1355,7 @@ Return value:
switch(uColor)
{
case WHITE:
- bb = pHash->bbPawnLocations[BLACK] & BBFILE[FILE(c)];
+ bb = pos->bbPawns[BLACK] & BBFILE[FILE(c)];
ASSERT(bb);
while(IS_ON_BOARD(c1 = CoorFromBitBoardRank8ToRank1(&bb)))
{
@@ -1364,7 +1364,7 @@ Return value:
}
break;
case BLACK:
- bb = pHash->bbPawnLocations[WHITE] & BBFILE[FILE(c)];
+ bb = pos->bbPawns[WHITE] & BBFILE[FILE(c)];
ASSERT(bb);
while(IS_ON_BOARD(c1 = CoorFromBitBoardRank1ToRank8(&bb)))
{
@@ -1380,7 +1380,7 @@ Return value:
// critical square. Note if there are no sentries then this
// pawn (on square c) is a passer already, not a candidate.
//
- bb = pHash->bbPawnLocations[FLIP(uColor)] & BBADJACENT_FILES[FILE(c)];
+ bb = pos->bbPawns[FLIP(uColor)] & BBADJACENT_FILES[FILE(c)];
bb &= BBPRECEEDING_RANKS[(c & 0x70) >> 4][uColor];
if (!bb)
{
@@ -1487,7 +1487,7 @@ Return value:
c1 = cSquare + 1 - d1;
if ((IS_ON_BOARD(c1)) && (pHash->uCountPerFile[uColor][FILE(c1) + 1]))
{
- ASSERT(pHash->bbPawnLocations[uColor] & BBFILE[FILE(c1)]);
+ ASSERT(pos->bbPawns[uColor] & BBFILE[FILE(c1)]);
if (!(pos->rgSquare[c1 + 8].bvAttacks[FLIP(uColor)].uWholeThing) ||
(pos->rgSquare[c1 + 8].bvAttacks[uColor].uWholeThing))
{
@@ -1528,7 +1528,7 @@ Return value:
c1 = cSquare - 1 - d1;
if ((IS_ON_BOARD(c1)) && (pHash->uCountPerFile[uColor][FILE(c1) + 1]))
{
- ASSERT(pHash->bbPawnLocations[uColor] & BBFILE[FILE(c1)]);
+ ASSERT(pos->bbPawns[uColor] & BBFILE[FILE(c1)]);
if (!(pos->rgSquare[c1 + 8].bvAttacks[FLIP(uColor)].uWholeThing) ||
(pos->rgSquare[c1 + 8].bvAttacks[uColor].uWholeThing))
@@ -1721,7 +1721,7 @@ Return value:
{
if (pHash->uCountPerFile[FLIP(uColor)][u + 1] != 0)
{
- ASSERT(pHash->bbPawnLocations[FLIP(uColor)] & BBFILE[u]);
+ ASSERT(pos->bbPawns[FLIP(uColor)] & BBFILE[u]);
if (!(pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[u]))
{
break;
@@ -1742,7 +1742,7 @@ Return value:
{
if (pHash->uCountPerFile[FLIP(uColor)][u + 1] != 0)
{
- ASSERT(pHash->bbPawnLocations[FLIP(uColor)] & BBFILE[u]);
+ ASSERT(pos->bbPawns[FLIP(uColor)] & BBFILE[u]);
if (!(pHash->bbPasserLocations[FLIP(uColor)] & BBFILE[u]))
{
break;
@@ -1950,15 +1950,18 @@ Return value:
pHash->uCountPerFile[uColor][uPawnFile]++;
//
- // Update bitboard bit
+ // pos->bbPawns[uColor] is plain POSITION state, maintained
+ // incrementally by move.c on every pawn move -- already
+ // has this pawn's bit set by the time we get here, nothing
+ // to build. (No running-count cross-check against
+ // uCountPerFile here: unlike the old bit-by-bit
+ // pHash->bbPawnLocations this replaces, bbPawns already
+ // holds every pawn on the board up front, not just the
+ // ones this loop has visited so far, so a per-iteration
+ // "counts so far agree" comparison isn't meaningful
+ // against it -- only a post-loop total would be.)
//
- ASSERT(CountBits(pHash->bbPawnLocations[uColor] &
- BBFILE[uPawnFile-1]) < 6);
- ASSERT((pHash->bbPawnLocations[uColor] & COOR_TO_BB(c)) == 0);
- pHash->bbPawnLocations[uColor] |= COOR_TO_BB(c);
- ASSERT(CountBits(pHash->bbPawnLocations[uColor] &
- BBFILE[uPawnFile-1]) ==
- pHash->uCountPerFile[uColor][uPawnFile]);
+ ASSERT(pos->bbPawns[uColor] & COOR_TO_BB(c));
//
// Count unmoved pawns
@@ -2007,7 +2010,7 @@ Return value:
{
ASSERT(IS_VALID_COLOR(uColor));
ASSERT(pos->uPawnCount[uColor] <= 8);
- ASSERT(CountBits(pHash->bbPawnLocations[uColor]) <= 8);
+ ASSERT(CountBits(pos->bbPawns[uColor]) <= 8);
d1 = 16 * g_iAhead[uColor];
for (u = 0;
@@ -2029,12 +2032,12 @@ Return value:
uPawnFile = FILE(c) - 1;
if (pHash->uCountPerFile[uColor][uPawnFile + 1] > 0)
{
- bb = (pHash->bbPawnLocations[uColor] &
+ bb = (pos->bbPawns[uColor] &
BBFILE[uPawnFile] &
BBADJACENT_RANKS[RANK(c)]);
if (!bb)
{
- bb = (pHash->bbPawnLocations[FLIP(uColor)] &
+ bb = (pos->bbPawns[FLIP(uColor)] &
BBFILE[uPawnFile]);
while(IS_ON_BOARD(cSquare =
CoorFromBitBoardRank8ToRank1(&bb)))
@@ -2075,12 +2078,12 @@ Return value:
uPawnFile = FILE(c) + 1;
if (pHash->uCountPerFile[uColor][uPawnFile + 1] > 0)
{
- bb = (pHash->bbPawnLocations[uColor] &
+ bb = (pos->bbPawns[uColor] &
BBFILE[uPawnFile] &
BBADJACENT_RANKS[RANK(c)]);
if (!bb)
{
- bb = (pHash->bbPawnLocations[FLIP(uColor)] &
+ bb = (pos->bbPawns[FLIP(uColor)] &
BBFILE[uPawnFile]);
while(IS_ON_BOARD(cSquare =
CoorFromBitBoardRank8ToRank1(&bb)))
@@ -2152,7 +2155,7 @@ Return value:
fast_skip:
uPawnFile = FILE(c) + 1;
ASSERT((1 <= uPawnFile) && (uPawnFile <= 8));
- ASSERT(CountBits(pHash->bbPawnLocations[uColor] & BBFILE[FILE(c)])
+ ASSERT(CountBits(pos->bbPawns[uColor] & BBFILE[FILE(c)])
== pHash->uCountPerFile[uColor][uPawnFile]);
//
@@ -2640,17 +2643,17 @@ Return value:
bbPc = ~(pHash->bbStationaryPawns[WHITE] |
pHash->bbStationaryPawns[BLACK]);
bbPc &= bbMask;
- bb = pHash->bbPawnLocations[uColor] & bbPc;
+ bb = pos->bbPawns[uColor] & bbPc;
while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
{
ASSERT(pos->rgSquare[cSquare].pPiece);
ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece));
ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == uColor);
- ASSERT(pHash->bbPawnLocations[uColor] & COOR_TO_BB(cSquare));
+ ASSERT(pos->bbPawns[uColor] & COOR_TO_BB(cSquare));
i += TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare];
}
- bb = pHash->bbPawnLocations[FLIP(uColor)] & bbPc;
+ bb = pos->bbPawns[FLIP(uColor)] & bbPc;
while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
{
//
@@ -2660,7 +2663,7 @@ Return value:
ASSERT(pos->rgSquare[cSquare].pPiece);
ASSERT(IS_PAWN(pos->rgSquare[cSquare].pPiece));
ASSERT(GET_COLOR(pos->rgSquare[cSquare].pPiece) == FLIP(uColor));
- ASSERT(pHash->bbPawnLocations[FLIP(uColor)] & COOR_TO_BB(cSquare));
+ ASSERT(pos->bbPawns[FLIP(uColor)] & COOR_TO_BB(cSquare));
i +=
(pos->rgSquare[cSquare|8].bvAttacks[FLIP(uColor)].small.uPawn != 0) *
TRANSIENT_PAWN_ON_BISHOP_COLOR[cSquare] / 2;
@@ -2816,7 +2819,7 @@ Return value:
// by a friendly one -- near the enemy king. This is a
// bishop-specific king-tropism/outpost bonus.
//
- bb = pHash->bbPawnLocations[FLIP(uColor)] &
+ bb = pos->bbPawns[FLIP(uColor)] &
(~pHash->bbStationaryPawns[FLIP(uColor)]);
if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb))
{
@@ -2956,7 +2959,7 @@ Return value:
//
uDist = DISTANCE(c, pos->cNonPawns[FLIP(uColor)][0]);
ASSERT((uDist > 0) && (uDist <= 8));
- bb = pHash->bbPawnLocations[FLIP(uColor)] &
+ bb = pos->bbPawns[FLIP(uColor)] &
(~pHash->bbStationaryPawns[FLIP(uColor)]);
if (TRUE == _IsSquareSafeFromEnemyPawn(pos, c, bb))
{
@@ -4013,7 +4016,7 @@ Return value:
(pHash->uCountPerFile[BLACK][u] > 0);
ASSERT((v >= 0) && (v <= 2));
uCounter += (KingFileDefects[v] + ((v < 2) && ((u == 1) || (u == 8))));
- bb = pHash->bbPawnLocations[ufColor] & BBFILE[u - 1];
+ bb = pos->bbPawns[ufColor] & BBFILE[u - 1];
if (bb)
{
if (uColor == WHITE)
@@ -4188,8 +4191,8 @@ Return value:
if ((u == 2) && (uColor == WHITE))
{
i = 0;
- bb = (pHash->bbPawnLocations[WHITE] |
- pHash->bbPawnLocations[BLACK]);
+ bb = (pos->bbPawns[WHITE] |
+ pos->bbPawns[BLACK]);
while(IS_ON_BOARD(cSquare = CoorFromBitBoardRank8ToRank1(&bb)))
{
i += (DISTANCE(cSquare, pos->cNonPawns[BLACK][0]) -
@@ -4847,7 +4850,8 @@ Return value:
// save some time. BEFORE ANY CODE BELOW TOUCHES THE ATTACK
// TABLES IT NEEDS TO CLEAR/POPULATE THEM THOUGH!!!
//
- pHash = _EvalPawns(ctx, &fDeferred);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalPawns,
+ pHash = _EvalPawns(ctx, &fDeferred));
ASSERT(NULL != pHash);
ASSERT(IS_VALID_FLAG(fDeferred));
pos->iScore[WHITE] += pHash->iScore[WHITE];
@@ -4948,6 +4952,10 @@ Return value:
{
*piPositional = iAlphaMargin;
}
+#ifdef EVAL_TIME
+ ctx->sCounters.tree.u64CyclesEvalPreLazy +=
+ (SystemReadTimeStampCounter() - uTimer);
+#endif
goto end;
}
else if (iScoreForSideToMove - iBetaMargin >= iBeta)
@@ -4957,6 +4965,10 @@ Return value:
{
*piPositional = iBetaMargin;
}
+#ifdef EVAL_TIME
+ ctx->sCounters.tree.u64CyclesEvalPreLazy +=
+ (SystemReadTimeStampCounter() - uTimer);
+#endif
goto end;
}
}
@@ -4978,13 +4990,19 @@ Return value:
}
#endif
+#ifdef EVAL_TIME
+ ctx->sCounters.tree.u64CyclesEvalPreLazy +=
+ (SystemReadTimeStampCounter() - uTimer);
+#endif
+
//
// If we have to clear/populate the attack table, do it now that
// we know we aren't taking a lazy exit.
//
if (TRUE == fDeferred)
{
- _PopulatePawnAttackBits(pos);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalAttackTablePop,
+ _PopulatePawnAttackBits(pos));
}
//
@@ -5047,11 +5065,13 @@ Return value:
//
if (IS_KNIGHT(p))
{
- _EvalKnight(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
+ _EvalKnight(pos, c, pHash));
}
else
{
- _EvalBishop(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
+ _EvalBishop(pos, c, pHash));
}
#ifdef EVAL_DUMP
Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p),
@@ -5088,11 +5108,13 @@ Return value:
//
if (IS_KNIGHT(p))
{
- _EvalKnight(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
+ _EvalKnight(pos, c, pHash));
}
else
{
- _EvalBishop(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
+ _EvalBishop(pos, c, pHash));
}
#ifdef EVAL_DUMP
Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p),
@@ -5118,7 +5140,7 @@ Return value:
ASSERT(IS_ON_BOARD(c));
ASSERT(IS_ROOK(pos->rgSquare[c].pPiece));
ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor);
- _EvalRook(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
#ifdef EVAL_DUMP
p = BLACK_ROOK | uColor;
Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p),
@@ -5135,7 +5157,7 @@ Return value:
ASSERT(IS_ON_BOARD(c));
ASSERT(IS_ROOK(pos->rgSquare[c].pPiece));
ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor);
- _EvalRook(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalRook, _EvalRook(pos, c, pHash));
#ifdef EVAL_DUMP
p = BLACK_ROOK | uColor;
Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p),
@@ -5155,7 +5177,7 @@ Return value:
ASSERT(IS_ON_BOARD(c));
ASSERT(IS_QUEEN(pos->rgSquare[c].pPiece));
ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor);
- _EvalQueen(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
#ifdef EVAL_DUMP
p = BLACK_ROOK | uColor;
Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p),
@@ -5172,7 +5194,7 @@ Return value:
ASSERT(IS_ON_BOARD(c));
ASSERT(IS_QUEEN(pos->rgSquare[c].pPiece));
ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor);
- _EvalQueen(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalQueen, _EvalQueen(pos, c, pHash));
#ifdef EVAL_DUMP
p = BLACK_ROOK | uColor;
Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p),
@@ -5191,7 +5213,7 @@ Return value:
ASSERT(GET_COLOR(p) == BLACK);
ASSERT(IS_KING(p));
#endif
- _EvalKing(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash));
ctx->sPlyInfo[ctx->uPly].iKingScore[BLACK] = pos->iTempScore;
#ifdef EVAL_DUMP
Trace("After *k:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
@@ -5205,7 +5227,7 @@ Return value:
ASSERT(GET_COLOR(p) == WHITE);
ASSERT(IS_KING(p));
#endif
- _EvalKing(pos, c, pHash);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash));
ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE] = pos->iTempScore;
#ifdef EVAL_DUMP
Trace("After .k:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
@@ -5216,6 +5238,9 @@ Return value:
// passed pawns identified by the pawn eval routine again. Also
// see if the side not on move has a trapped piece.
//
+#ifdef EVAL_TIME
+ UINT64 u64PostLazyMiscTimer = SystemReadTimeStampCounter();
+#endif
bb = (pHash->bbPasserLocations[WHITE] | pHash->bbPasserLocations[BLACK]);
if (0 != bb)
{
@@ -5281,8 +5306,8 @@ Return value:
// with two pawn wings where having a bishop is an
// advantage.
//
- bb = (pHash->bbPawnLocations[WHITE] |
- pHash->bbPawnLocations[BLACK]);
+ bb = (pos->bbPawns[WHITE] |
+ pos->bbPawns[BLACK]);
ASSERT((BBFILE[A] | BBFILE[B] | BBFILE[C]) ==
0x0707070707070707ULL);
ASSERT((BBFILE[F] | BBFILE[G] | BBFILE[H]) ==
@@ -5334,6 +5359,11 @@ Return value:
(SCORE)REDUCED_MATERIAL_DOWN_SCALER[pos->uArmyScaler[WHITE]]) / 8;
pos->iScore[WHITE] += iAlphaMargin;
+#ifdef EVAL_TIME
+ ctx->sCounters.tree.u64CyclesEvalPostLazyMisc +=
+ (SystemReadTimeStampCounter() - u64PostLazyMiscTimer);
+#endif
+
//
// Almost done
//