summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-09-05 01:03:28 -0700
committerScott Gasch <[email protected]>2026-09-05 01:03:28 -0700
commit6e8645015f73a09f57e592fbf911e66db845b327 (patch)
tree25206e1a6e442c1e3456051fab57e0c16ca4384b /src
parent57502d6e205bc802fc9b89fd1534add5efc4b9ea (diff)
Replace Eval()'s piece-dispatch loop with per-type bitboard walks
The old loop walked cNonPawns[color][1..N] (a flat, arbitrarily- ordered list mixing knight/bishop/rook/queen), doing a mailbox lookup plus a p&0x4/IS_KNIGHT branch per piece to decide which _Eval* function to call, then stashing rooks/queens into cDefer/uDefer arrays to evaluate in a later pass -- a hard-to-predict branch per piece on top of a mailbox read the callee already redoes for its own ASSERT. pos->bbPieces[color][PIECE_TYPE] already exists (incrementally maintained by move.c) and was simply unused by Eval() until now. Replaced the whole dispatch with a direct per-type bitboard walk: knights, then bishops, for side-to-move, then the same for the other side, then rooks both colors, then queens both colors -- same phase order as before (load-bearing for bvAttacks accumulation), just sourced from a bitboard instead of a mixed list + runtime type dispatch. Retires the cDefer/uDefer bookkeeping entirely -- with each type its own direct walk, "rooks after minors" is just "do that walk after this one," nothing to defer. Verified genuinely behavior-neutral, not just crash-free: built the pre-change and post-change binaries side by side and diffed --batch --command output against tests/ecm_ringers.ep_ at sd10. Node counts, scores, and PVs are byte-for-byte identical across all 11 positions; only wall-clock time and NPS differ, consistently in the new version's favor (~3-5% faster on this suite, e.g. 1088239 nps -> 1143141 nps, 1560092 -> 1616844). Also verified via the normal precommit_check.sh gate. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P6g6iF6mD1Hau6nCZCzwYj
Diffstat (limited to 'src')
-rwxr-xr-xsrc/eval.c229
1 files changed, 101 insertions, 128 deletions
diff --git a/src/eval.c b/src/eval.c
index 35ee663..7287caf 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -4803,14 +4803,11 @@ Return value:
**/
{
- ULONG uDefer[2][2];
- COOR cDefer[2][2][10];
POSITION *pos = &(ctx->sPosition);
SCORE iScoreForSideToMove;
SCORE iAlphaMargin, iBetaMargin;
PAWN_HASH_ENTRY *pHash;
COOR c;
- PIECE p;
ULONG u;
ULONG uColor;
BITBOARD bb;
@@ -5054,131 +5051,111 @@ Return value:
//
// Evaluate individual pieces.
//
+ // board_representation/EVAL.md section 0/2: replaces the old
+ // cNonPawns[color][1..N] walk + per-piece mailbox lookup + p&0x4/
+ // IS_KNIGHT branch dispatch (a hard-to-predict branch per piece,
+ // on top of a redundant mailbox read the callee already redoes
+ // for its own ASSERT) with a direct per-type location-bitboard
+ // walk. pos->bbPieces[color][PIECE_TYPE] already exists,
+ // incrementally maintained by move.c, and was simply unused by
+ // Eval() until now -- no new infrastructure, purely a consumer
+ // change. Also retires the cDefer/uDefer "remember the rooks and
+ // queens for later" bookkeeping entirely: since each type is now
+ // its own direct bitboard walk, "evaluate rooks after minors" is
+ // just "do the rook-bitboard walk after the minor-bitboard walks"
+ // -- nothing to defer.
+ //
+ // Phase order preserved exactly as before (side-to-move's minors,
+ // then the other side's minors, then rooks both colors, then
+ // queens both colors) -- that ordering is load-bearing for
+ // bvAttacks accumulation (each piece's mobility/danger depends on
+ // attack bits already written by earlier-evaluated pieces this
+ // same Eval() call). Knight-vs-bishop order *within* the same
+ // color/phase, and encounter order within a single type's own
+ // bitboard walk, were never meaningful before (cNonPawns' order
+ // is arbitrary swap-with-last-on-removal, not stable) and stay
+ // that way -- EVAL_TERM's plain score accumulation doesn't care.
+ //
pos->uMinorsAtHome[BLACK] = pos->uMinorsAtHome[WHITE] = 0;
- uDefer[BLACK][0] = uDefer[BLACK][1] =
- uDefer[WHITE][0] = uDefer[WHITE][1] = 0;
uColor = pos->uToMove;
- for (u = 1; // skips the king
- u < pos->uNonPawnCount[uColor][0];
- u++)
+ bb = pos->bbPieces[uColor][KNIGHT];
+ while (bb)
{
- c = pos->cNonPawns[uColor][u];
- ASSERT(IS_ON_BOARD(c));
- p = pos->rgSquare[c].pPiece;
- ASSERT(IS_VALID_PIECE(p));
- ASSERT(GET_COLOR(p) == uColor);
- ASSERT(!IS_PAWN(p) && !IS_KING(p));
-
- if (p & 0x4)
- {
- ASSERT(IS_BISHOP(p) || IS_KNIGHT(p));
-
- //
- // Note: The side on move's pieces of value X are
- // evaluated and have their mobility counted and added to
- // the attack table before the other side's pieces. Also,
- // we do not care if this piece is in danger b/c we will
- // find out later on in EvalLookForDanger anyway.
- //
- if (IS_KNIGHT(p))
- {
- TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
- _EvalKnight(pos, c, pHash));
- }
- else
- {
- TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
- _EvalBishop(pos, c, pHash));
- }
+ u = FastFirstBit(bb) - 1;
+ bb &= (bb - 1);
+ c = BIT_NUMBER_TO_COOR(u);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
+ _EvalKnight(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p),
- pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After N:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+#endif
+ }
+ bb = pos->bbPieces[uColor][BISHOP];
+ while (bb)
+ {
+ u = FastFirstBit(bb) - 1;
+ bb &= (bb - 1);
+ c = BIT_NUMBER_TO_COOR(u);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
+ _EvalBishop(pos, c, pHash));
+#ifdef EVAL_DUMP
+ Trace("After B:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
- }
- else
- {
- ASSERT(IS_ROOK(p) || IS_QUEEN(p));
- cDefer[uColor][IS_QUEEN(p)][uDefer[uColor][IS_QUEEN(p)]++] = c;
- }
}
uColor = FLIP(uColor);
ASSERT(uColor != pos->uToMove);
- for (u = 1;
- u < pos->uNonPawnCount[uColor][0];
- u++)
+ bb = pos->bbPieces[uColor][KNIGHT];
+ while (bb)
{
- c = pos->cNonPawns[uColor][u];
- ASSERT(IS_ON_BOARD(c));
- p = pos->rgSquare[c].pPiece;
- ASSERT(IS_VALID_PIECE(p));
- ASSERT(GET_COLOR(p) == uColor);
- ASSERT(!IS_PAWN(p) && !IS_KING(p));
- if (p & 0x4)
- {
- //
- // This time we are evaluating a piece from the side not
- // on move. We will not go back later and look at this
- // piece again so if it's in danger then record it. The
- // drawback, of course, is that danger is only detected if
- // a piece is attacked by an enemy of lesser value!
- //
- if (IS_KNIGHT(p))
- {
- TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
- _EvalKnight(pos, c, pHash));
- }
- else
- {
- TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
- _EvalBishop(pos, c, pHash));
- }
+ u = FastFirstBit(bb) - 1;
+ bb &= (bb - 1);
+ c = BIT_NUMBER_TO_COOR(u);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalKnight,
+ _EvalKnight(pos, c, pHash));
#ifdef EVAL_DUMP
- Trace("After %s:\n%d\t\t%d\n", PieceAbbrev(p),
- pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After N:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
+#endif
+ }
+ bb = pos->bbPieces[uColor][BISHOP];
+ while (bb)
+ {
+ u = FastFirstBit(bb) - 1;
+ bb &= (bb - 1);
+ c = BIT_NUMBER_TO_COOR(u);
+ TIMED_EVAL_CALL(ctx, u64CyclesEvalBishop,
+ _EvalBishop(pos, c, pHash));
+#ifdef EVAL_DUMP
+ Trace("After B:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
- }
- else
- {
- ASSERT(IS_ROOK(p) || IS_QUEEN(p));
- cDefer[uColor][IS_QUEEN(p)][uDefer[uColor][IS_QUEEN(p)]++] = c;
- }
}
//
// Evaluate any rook(s) for side on move then for side not on move.
//
uColor = FLIP(uColor);
- for (u = 0;
- u < uDefer[uColor][0];
- u++)
+ bb = pos->bbPieces[uColor][ROOK];
+ while (bb)
{
- c = cDefer[uColor][0][u];
- ASSERT(IS_ON_BOARD(c));
- ASSERT(IS_ROOK(pos->rgSquare[c].pPiece));
- ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor);
+ u = FastFirstBit(bb) - 1;
+ bb &= (bb - 1);
+ c = BIT_NUMBER_TO_COOR(u);
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),
- pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After R:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
uColor = FLIP(uColor);
- for (u = 0;
- u < uDefer[uColor][0];
- u++)
+ bb = pos->bbPieces[uColor][ROOK];
+ while (bb)
{
- c = cDefer[uColor][0][u];
- ASSERT(IS_ON_BOARD(c));
- ASSERT(IS_ROOK(pos->rgSquare[c].pPiece));
- ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor);
+ u = FastFirstBit(bb) - 1;
+ bb &= (bb - 1);
+ c = BIT_NUMBER_TO_COOR(u);
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),
- pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After R:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
@@ -5186,36 +5163,28 @@ Return value:
// Evaluate any queen(s) for side on move then for side not on move.
//
uColor = FLIP(uColor);
- for (u = 0;
- u < uDefer[uColor][1];
- u++)
+ bb = pos->bbPieces[uColor][QUEEN];
+ while (bb)
{
- c = cDefer[uColor][1][u];
- ASSERT(IS_ON_BOARD(c));
- ASSERT(IS_QUEEN(pos->rgSquare[c].pPiece));
- ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor);
+ u = FastFirstBit(bb) - 1;
+ bb &= (bb - 1);
+ c = BIT_NUMBER_TO_COOR(u);
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),
- pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After Q:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
uColor = FLIP(uColor);
- for (u = 0;
- u < uDefer[uColor][1];
- u++)
+ bb = pos->bbPieces[uColor][QUEEN];
+ while (bb)
{
- c = cDefer[uColor][1][u];
- ASSERT(IS_ON_BOARD(c));
- ASSERT(IS_QUEEN(pos->rgSquare[c].pPiece));
- ASSERT(GET_COLOR(pos->rgSquare[c].pPiece) == uColor);
+ u = FastFirstBit(bb) - 1;
+ bb &= (bb - 1);
+ c = BIT_NUMBER_TO_COOR(u);
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),
- pos->iScore[WHITE], pos->iScore[BLACK]);
+ Trace("After Q:\n%d\t\t%d\n", pos->iScore[WHITE], pos->iScore[BLACK]);
#endif
}
@@ -5225,10 +5194,12 @@ Return value:
c = pos->cNonPawns[BLACK][0];
#ifdef DEBUG
ASSERT(IS_ON_BOARD(c));
- p = pos->rgSquare[c].pPiece;
- ASSERT(IS_VALID_PIECE(p));
- ASSERT(GET_COLOR(p) == BLACK);
- ASSERT(IS_KING(p));
+ {
+ PIECE pDebugKing = pos->rgSquare[c].pPiece;
+ ASSERT(IS_VALID_PIECE(pDebugKing));
+ ASSERT(GET_COLOR(pDebugKing) == BLACK);
+ ASSERT(IS_KING(pDebugKing));
+ }
#endif
TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash));
ctx->sPlyInfo[ctx->uPly].iKingScore[BLACK] = pos->iTempScore;
@@ -5239,10 +5210,12 @@ Return value:
c = pos->cNonPawns[WHITE][0];
#ifdef DEBUG
ASSERT(IS_ON_BOARD(c));
- p = pos->rgSquare[c].pPiece;
- ASSERT(IS_VALID_PIECE(p));
- ASSERT(GET_COLOR(p) == WHITE);
- ASSERT(IS_KING(p));
+ {
+ PIECE pDebugKing = pos->rgSquare[c].pPiece;
+ ASSERT(IS_VALID_PIECE(pDebugKing));
+ ASSERT(GET_COLOR(pDebugKing) == WHITE);
+ ASSERT(IS_KING(pDebugKing));
+ }
#endif
TIMED_EVAL_CALL(ctx, u64CyclesEvalKing, _EvalKing(pos, c, pHash));
ctx->sPlyInfo[ctx->uPly].iKingScore[WHITE] = pos->iTempScore;