summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Gasch <[email protected]>2026-08-23 11:28:49 -0700
committerScott Gasch <[email protected]>2026-08-23 11:28:49 -0700
commitdf8facc22815f9eca0897e27ccce30701ba95322 (patch)
treebf90ad7ff19383fbac9b0a163f31bf3a9fb11fbe
parentdc0c9dbf405095a6483705b153f4c85c64499d4e (diff)
Clean X64 build and ported GetAttacks to x64.
-rwxr-xr-xsrc/chess.h41
-rwxr-xr-xsrc/eval.c30
-rw-r--r--src/evalhash.c84
-rwxr-xr-xsrc/hash.c2
-rw-r--r--src/recogn.c30
-rwxr-xr-xsrc/root.c1
-rwxr-xr-xsrc/search.c21
-rwxr-xr-xsrc/split.c23
-rw-r--r--src/testsearch.c10
-rw-r--r--src/testsee.c2
-rw-r--r--src/x64.asm247
11 files changed, 380 insertions, 111 deletions
diff --git a/src/chess.h b/src/chess.h
index 2bfec4d..0f7bf61 100755
--- a/src/chess.h
+++ b/src/chess.h
@@ -189,6 +189,7 @@ typedef struct _DLIST_ENTRY
#define THREE_PLY 192
#define FOUR_PLY 256
#define MAX_DEPTH_PER_SEARCH (MAX_PLY_PER_SEARCH * ONE_PLY)
+#define MAX_EXTEND_PER_LINE (MAX_PLY_PER_SEARCH * ONE_PLY / 2)
#define IS_VALID_DEPTH(x) (((x) >= 0) && \
((x) <= MAX_DEPTH_PER_SEARCH) && \
@@ -863,6 +864,9 @@ typedef struct _CUMULATIVE_SEARCH_FLAGS
FLAG fInReducedDepthBranch; // not used
FLAG fAvoidNullmove; // restore
FLAG fVerifyNullmove; // restore
+ INT iCumulativeExtend; // restore; total extension
+ // plies spent so far on
+ // this line, root to here
// qsearch
ULONG uQsearchDepth; // restore
@@ -946,10 +950,29 @@ PLY_INFO;
#define EVAL_HASH
#ifdef EVAL_HASH
#define EVAL_HASH_TABLE_SIZE (2097152) // 32Mb (per thread)
+
+//
+// bvFlags values for EVAL_HASH_ENTRY. EXACT means iEval is the real,
+// fully-computed static eval. UPPER/LOWER mean the entry came from a
+// lazy-eval early exit: iEval is the (uncorrected) partial score that
+// would be returned and iBound is the proven bound on the true, fully
+// computed eval (true score <= iBound for UPPER, true score >= iBound
+// for LOWER). Because the bound is a fact about the position -- not
+// about whatever alpha/beta window triggered the lazy exit -- it can
+// be reused by a later probe with a different window, as long as that
+// window is still resolved by the bound (mirrors HASH_FLAG_UPPER /
+// HASH_FLAG_LOWER in the main hash table).
+//
+#define EVAL_HASH_EXACT 0x1
+#define EVAL_HASH_UPPER 0x2
+#define EVAL_HASH_LOWER 0x4
+
typedef struct _EVAL_HASH_ENTRY
{
UINT64 u64Key;
SCORE iEval;
+ SCORE iBound;
+ UCHAR bvFlags;
ULONG uPositional;
COOR cTrapped[2];
@@ -1214,7 +1237,18 @@ _assert(CHAR *szFile, ULONG uLine);
#ifdef DEBUG
#define DISTANCE(a, b) DistanceBetweenSquares((a), (b))
#else
-#define DISTANCE(a, b) g_pDistance[(a) - (b)]
+//
+// (a) and (b) are COOR, i.e. unsigned; the subtraction below must be
+// forced into signed arithmetic before it's used as an index into
+// g_pDistance (which points to the middle of g_uDistance so that
+// negative differences work). Without the cast, (a)-(b) is computed
+// as unsigned and wraps instead of going negative; on a 32-bit build
+// that wrapped value, added to a 32-bit pointer, happens to wrap back
+// to the right address by accident, but on a 64-bit build the 32-bit
+// unsigned result gets zero-extended (not sign-extended) into the
+// 64-bit index and reads wildly out of bounds.
+//
+#define DISTANCE(a, b) g_pDistance[(int)(a) - (int)(b)]
#endif // DEBUG
#define IS_EMPTY( square ) (!(square))
@@ -2966,7 +3000,7 @@ void
ReportEvalHashStats(void);
SCORE
-ProbeEvalHash(SEARCHER_THREAD_CONTEXT *ctx);
+ProbeEvalHash(SEARCHER_THREAD_CONTEXT *ctx, SCORE iAlpha, SCORE iBeta);
SCORE
GetRoughEvalScore(IN SEARCHER_THREAD_CONTEXT *ctx,
@@ -2975,7 +3009,8 @@ GetRoughEvalScore(IN SEARCHER_THREAD_CONTEXT *ctx,
IN FLAG fUseHash);
void
-StoreEvalHash(SEARCHER_THREAD_CONTEXT *ctx, SCORE iScore);
+StoreEvalHash(SEARCHER_THREAD_CONTEXT *ctx, SCORE iScore, SCORE iBound,
+ UCHAR bvFlags);
#endif // EVAL_HASH
#endif // CHESS
diff --git a/src/eval.c b/src/eval.c
index b6a7792..48a21e0 100755
--- a/src/eval.c
+++ b/src/eval.c
@@ -5596,7 +5596,7 @@ Return value:
//
// Check the eval hash
//
- iScoreForSideToMove = ProbeEvalHash(ctx);
+ iScoreForSideToMove = ProbeEvalHash(ctx, iAlpha, iBeta);
if (iScoreForSideToMove != INVALID_SCORE)
{
INC(ctx->sCounters.tree.u64EvalHashHits);
@@ -5714,12 +5714,32 @@ Return value:
//
_QuicklyEstimateKingSafetyTerm(pos, &iAlphaMargin, &iBetaMargin);
_QuicklyEstimatePasserBonuses(pos, pHash, &iAlphaMargin, &iBetaMargin);
-
- if ((iScoreForSideToMove + iAlphaMargin <= iAlpha) ||
- (iScoreForSideToMove - iBetaMargin >= iBeta))
+
+ if (iScoreForSideToMove + iAlphaMargin <= iAlpha)
{
ctx->uPositional = MINU(200, ctx->uPositional);
INC(ctx->sCounters.tree.u64LazyEvals);
+#ifdef EVAL_HASH
+ //
+ // The true eval can't reach iAlpha; that's a fact about
+ // the position (not this particular window), so remember
+ // it as a proven upper bound for reuse by later probes.
+ //
+ StoreEvalHash(ctx, iScoreForSideToMove,
+ (SCORE)(iScoreForSideToMove + iAlphaMargin),
+ EVAL_HASH_UPPER);
+#endif
+ goto end;
+ }
+ else if (iScoreForSideToMove - iBetaMargin >= iBeta)
+ {
+ ctx->uPositional = MINU(200, ctx->uPositional);
+ INC(ctx->sCounters.tree.u64LazyEvals);
+#ifdef EVAL_HASH
+ StoreEvalHash(ctx, iScoreForSideToMove,
+ (SCORE)(iScoreForSideToMove - iBetaMargin),
+ EVAL_HASH_LOWER);
+#endif
goto end;
}
}
@@ -6102,7 +6122,7 @@ Return value:
//
// Store in eval hash.
//
- StoreEvalHash(ctx, iScoreForSideToMove);
+ StoreEvalHash(ctx, iScoreForSideToMove, 0, EVAL_HASH_EXACT);
#endif
end:
diff --git a/src/evalhash.c b/src/evalhash.c
index e0bc75e..210c369 100644
--- a/src/evalhash.c
+++ b/src/evalhash.c
@@ -73,27 +73,48 @@ Return value:
u = (ULONG)u64Key & (EVAL_HASH_TABLE_SIZE - 1);
if (ctx->rgEvalHash[u].u64Key == u64Key)
{
- ctx->uPositional = ctx->rgEvalHash[u].uPositional;
- return(ctx->rgEvalHash[u].iEval);
+ switch(ctx->rgEvalHash[u].bvFlags)
+ {
+ case EVAL_HASH_EXACT:
+ ctx->uPositional = ctx->rgEvalHash[u].uPositional;
+ return(ctx->rgEvalHash[u].iEval);
+ case EVAL_HASH_UPPER:
+ if (ctx->rgEvalHash[u].iBound <= iAlpha)
+ {
+ return(ctx->rgEvalHash[u].iEval);
+ }
+ break;
+ case EVAL_HASH_LOWER:
+ if (ctx->rgEvalHash[u].iBound >= iBeta)
+ {
+ return(ctx->rgEvalHash[u].iEval);
+ }
+ break;
+ }
}
}
return(pos->iMaterialBalance[pos->uToMove] + ctx->uPositional);
}
-SCORE
-ProbeEvalHash(IN OUT SEARCHER_THREAD_CONTEXT *ctx)
+SCORE
+ProbeEvalHash(IN OUT SEARCHER_THREAD_CONTEXT *ctx, IN SCORE iAlpha, IN SCORE iBeta)
/*++
Routine description:
Probe the eval hash; return a real score if there's a hit
- otherwise return INVALID_SCORE.
+ otherwise return INVALID_SCORE. An EXACT entry is always usable.
+ An UPPER/LOWER entry (recorded from a lazy-eval early exit) is
+ only usable if the bound it proved still resolves the caller's
+ current alpha/beta window; otherwise it's treated as a miss and
+ the caller must actually compute something.
Parameters:
IN OUT SEARCHER_THREAD_CONTEXT *ctx : note that in the case
of a hit, *ctx is modified also.
+ IN SCORE iAlpha, IN SCORE iBeta : caller's current window
Return value:
@@ -107,28 +128,52 @@ Return value:
if (ctx->rgEvalHash[u].u64Key == u64Key)
{
- ctx->uPositional = ctx->rgEvalHash[u].uPositional;
- pos->cTrapped[WHITE] = ctx->rgEvalHash[u].cTrapped[WHITE];
- pos->cTrapped[BLACK] = ctx->rgEvalHash[u].cTrapped[BLACK];
- return(ctx->rgEvalHash[u].iEval);
+ switch(ctx->rgEvalHash[u].bvFlags)
+ {
+ case EVAL_HASH_EXACT:
+ ctx->uPositional = ctx->rgEvalHash[u].uPositional;
+ pos->cTrapped[WHITE] = ctx->rgEvalHash[u].cTrapped[WHITE];
+ pos->cTrapped[BLACK] = ctx->rgEvalHash[u].cTrapped[BLACK];
+ return(ctx->rgEvalHash[u].iEval);
+ case EVAL_HASH_UPPER:
+ if (ctx->rgEvalHash[u].iBound <= iAlpha)
+ {
+ return(ctx->rgEvalHash[u].iEval);
+ }
+ break;
+ case EVAL_HASH_LOWER:
+ if (ctx->rgEvalHash[u].iBound >= iBeta)
+ {
+ return(ctx->rgEvalHash[u].iEval);
+ }
+ break;
+ }
}
return(INVALID_SCORE);
}
-void
-StoreEvalHash(IN OUT SEARCHER_THREAD_CONTEXT *ctx,
- IN SCORE iScore)
+void
+StoreEvalHash(IN OUT SEARCHER_THREAD_CONTEXT *ctx,
+ IN SCORE iScore,
+ IN SCORE iBound,
+ IN UCHAR bvFlags)
/*++
Routine description:
Store a score in the eval hash table (which is pointed to
- indirectly via ctx).
+ indirectly via ctx). bvFlags is EVAL_HASH_EXACT for a fully
+ computed eval (iBound ignored) or EVAL_HASH_UPPER/EVAL_HASH_LOWER
+ for a bound proven by a lazy-eval early exit (iScore is the
+ uncorrected partial score that would be returned, iBound is the
+ proven bound on the true eval).
Parameters:
SEARCHER_THREAD_CONTEXT *ctx,
- SCORE iScore
+ SCORE iScore,
+ SCORE iBound,
+ UCHAR bvFlags
Return value:
@@ -141,8 +186,13 @@ Return value:
ULONG u = (ULONG)u64Key & (EVAL_HASH_TABLE_SIZE - 1);
ctx->rgEvalHash[u].u64Key = u64Key;
ctx->rgEvalHash[u].iEval = iScore;
- ctx->rgEvalHash[u].uPositional = ctx->uPositional;
- ctx->rgEvalHash[u].cTrapped[WHITE] = pos->cTrapped[WHITE];
- ctx->rgEvalHash[u].cTrapped[BLACK] = pos->cTrapped[BLACK];
+ ctx->rgEvalHash[u].iBound = iBound;
+ ctx->rgEvalHash[u].bvFlags = bvFlags;
+ if (bvFlags == EVAL_HASH_EXACT)
+ {
+ ctx->rgEvalHash[u].uPositional = ctx->uPositional;
+ ctx->rgEvalHash[u].cTrapped[WHITE] = pos->cTrapped[WHITE];
+ ctx->rgEvalHash[u].cTrapped[BLACK] = pos->cTrapped[BLACK];
+ }
}
#endif // EVAL_HASH
diff --git a/src/hash.c b/src/hash.c
index a29e375..c5e5f85 100755
--- a/src/hash.c
+++ b/src/hash.c
@@ -893,7 +893,7 @@ Return value:
// to the search.
INC(ctx->sCounters.hash.u64OverallHits);
if (pHash->mv.uMove) {
- uThisScore = COMPUTE_MOVE_SCORE(pHash->mv, uDepth);
+ uThisScore = COMPUTE_MOVE_SCORE(pHash->mv, pHash->uDepth);
ASSERT(uThisScore != 0);
if (uThisScore > uMoveScore) {
uMoveScore = uThisScore;
diff --git a/src/recogn.c b/src/recogn.c
index c0b1bb8..c8fc856 100644
--- a/src/recogn.c
+++ b/src/recogn.c
@@ -334,7 +334,7 @@ Return value:
uAdjacent = 0;
for (u = 1; u < pos->uNonPawnCount[uStrong][0]; u++)
{
- uAdjacent += DISTANCE(cWeakKing, pos->cNonPawns[uStrong][u] == 1);
+ uAdjacent += (DISTANCE(cWeakKing, pos->cNonPawns[uStrong][u]) == 1);
}
if (uAdjacent > 1)
{
@@ -345,6 +345,10 @@ Return value:
// If it's the weak side's turn and the strong king is close
// enough that the weak side may be stalemated, fail to recognize.
//
+ ASSERT(IS_ON_BOARD(pos->cNonPawns[uStrong][0]));
+ ASSERT(IS_KING(pos->rgSquare[pos->cNonPawns[uStrong][0]].pPiece));
+ u = DISTANCE(cWeakKing, pos->cNonPawns[uStrong][0]);
+ ASSERT(u != 1);
if (pos->uToMove != uStrong)
{
ASSERT(pos->uToMove == FLIP(uStrong));
@@ -352,17 +356,12 @@ Return value:
{
return(UNRECOGNIZED);
}
-
- ASSERT(IS_ON_BOARD(pos->cNonPawns[uStrong][0]));
- ASSERT(IS_KING(pos->rgSquare[pos->cNonPawns[uStrong][0]].pPiece));
- u = DISTANCE(cWeakKing, pos->cNonPawns[uStrong][0]);
- ASSERT(u != 1);
if ((u == 2) && (ON_EDGE(cWeakKing)))
{
return(UNRECOGNIZED);
}
}
-
+
//
// This is a recognized win for the strong side. Compute a score
// that encourages cornering the weak king and making progress
@@ -549,9 +548,9 @@ Return value:
uAdjacent = 0;
for (u = 1; u < pos->uNonPawnCount[uStrong][0]; u++)
{
- uAdjacent = (DISTANCE(pos->cNonPawns[uStrong][u], cWeakKing) == 1);
+ uAdjacent += (DISTANCE(pos->cNonPawns[uStrong][u], cWeakKing) == 1);
}
- if (!uAdjacent)
+ if (uAdjacent)
{
return(UNRECOGNIZED);
}
@@ -561,18 +560,13 @@ Return value:
// that there might be a stalemate if the weak side is on move and
// on the edge.
//
+ ASSERT(IS_ON_BOARD(pos->cNonPawns[uStrong][0]));
+ ASSERT(IS_KING(pos->rgSquare[pos->cNonPawns[uStrong][0]].pPiece));
+ u = DISTANCE(cWeakKing, pos->cNonPawns[uStrong][0]);
+ ASSERT(u != 1);
if (pos->uToMove != uStrong)
{
ASSERT(pos->uToMove == FLIP(uStrong));
- if (uAdjacent == 1)
- {
- return(UNRECOGNIZED);
- }
-
- ASSERT(IS_ON_BOARD(pos->cNonPawns[uStrong][0]));
- ASSERT(IS_KING(pos->rgSquare[pos->cNonPawns[uStrong][0]].pPiece));
- u = DISTANCE(cWeakKing, pos->cNonPawns[uStrong][0]);
- ASSERT(u != 1);
if ((u == 2) && (ON_EDGE(cWeakKing)))
{
return(UNRECOGNIZED);
diff --git a/src/root.c b/src/root.c
index 87ee4be..9ead880 100755
--- a/src/root.c
+++ b/src/root.c
@@ -632,6 +632,7 @@ Return value:
ctx->sSearchFlags.fVerifyNullmove = TRUE;
ctx->sSearchFlags.uQsearchDepth = 0;
+ ctx->sSearchFlags.iCumulativeExtend = 0;
if (uNumLegalMoves == 1)
{
//
diff --git a/src/search.c b/src/search.c
index 28f6f3d..29bbc66 100755
--- a/src/search.c
+++ b/src/search.c
@@ -383,6 +383,13 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
ASSERT(!InCheck(pos, pos->uToMove));
GenerateMoves(ctx, mvHash, GENERATE_ALL_MOVES);
}
+
+ // The threat/multi-check/no-legal-king-move bonuses above
+ // are independent and can stack past ONE_PLY; clamp the
+ // combined per-position extension to what the rest of the
+ // code (e.g. split.c's HelpSearch) assumes is the max for
+ // a single node.
+ iOrigExtend = MIN(iOrigExtend, ONE_PLY);
// fall through
case PREPARE_TO_TRY_MOVES:
@@ -527,6 +534,16 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
uDepth,
&iExtend);
+ // Cap how many extension plies this line may spend in total
+ // (root to here) so that a chain of checks/threats/etc. can't
+ // stall uDepth's descent indefinitely and burn the entire
+ // MAX_PLY_PER_SEARCH ply budget on one forcing sequence.
+ if (iExtend > 0)
+ {
+ iExtend = MIN(iExtend,
+ MAX(MAX_EXTEND_PER_LINE - pf->iCumulativeExtend, 0));
+ }
+
// Decide whether or not to do history pruning
if (TRUE == WeShouldDoHistoryPruning(iRoughEval,
iAlpha,
@@ -561,6 +578,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
// Compute the next search depth for this move/subtree.
uNextDepth = uDepth - ONE_PLY + iExtend;
if (uNextDepth >= MAX_DEPTH_PER_SEARCH) uNextDepth = 0;
+ pf->iCumulativeExtend += iExtend;
ASSERT(pf->fAvoidNullmove == FALSE);
if (iBestScore == -INFINITY)
{
@@ -586,6 +604,7 @@ Search(IN SEARCHER_THREAD_CONTEXT *ctx,
iScore = -Search(ctx, -iBeta, -iAlpha, uNextDepth);
}
UnmakeMove(ctx, mv);
+ pf->iCumulativeExtend -= iExtend;
ASSERT(PositionsAreEquivalent(pos, &pi->sPosition));
if (WE_SHOULD_STOP_SEARCHING)
{
@@ -1327,6 +1346,7 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
-iAlpha);
pf->uQsearchDepth--;
UnmakeMove(ctx, mv);
+ if (WE_SHOULD_STOP_SEARCHING) goto end;
if (iScore > iBestScore)
{
@@ -1360,7 +1380,6 @@ QSearch(IN SEARCHER_THREAD_CONTEXT *ctx,
}
}
}
- if (WE_SHOULD_STOP_SEARCHING) goto end;
}
}
ASSERT(iBestScore > -NMATE);
diff --git a/src/split.c b/src/split.c
index 714d77b..ce9a752 100755
--- a/src/split.c
+++ b/src/split.c
@@ -1099,8 +1099,19 @@ Return value:
&iExtend);
//
+ // Cap total extension plies spent on this line, same as the
+ // non-split move loop in search.c does.
+ //
+ if (iExtend > 0)
+ {
+ iExtend = MIN(iExtend,
+ MAX(MAX_EXTEND_PER_LINE -
+ ctx->sSearchFlags.iCumulativeExtend, 0));
+ }
+
+ //
// Decide whether to history prune
- //
+ //
if (TRUE == WeShouldDoHistoryPruning(iRoughEval,
iAlpha,
iBeta,
@@ -1117,13 +1128,14 @@ Return value:
iExtend = -ONE_PLY;
ctx->sPlyInfo[ctx->uPly].iExtensionAmount = -ONE_PLY;
}
-
+
//
// Compute next depth
- //
+ //
uDepth = uDepth - ONE_PLY + iExtend;
if (uDepth >= MAX_DEPTH_PER_SEARCH) uDepth = 0;
-
+ ctx->sSearchFlags.iCumulativeExtend += iExtend;
+
iScore = -Search(ctx, -iAlpha - 1, -iAlpha, uDepth);
if ((iAlpha < iScore) && (iScore < iBeta))
{
@@ -1132,7 +1144,7 @@ Return value:
//
// Decide whether to research reduced branches to full depth.
- //
+ //
if ((iExtend < 0) && (iScore >= iBeta))
{
uDepth += ONE_PLY;
@@ -1140,6 +1152,7 @@ Return value:
iScore = -Search(ctx, -iBeta, -iAlpha, uDepth);
}
UnmakeMove(ctx, mv);
+ ctx->sSearchFlags.iCumulativeExtend -= iExtend;
ASSERT(PositionsAreEquivalent(&ctx->sPosition, &board));
if (TRUE == g_SplitInfo[u].fTerminate) break;
if (iScore > iBestScore)
diff --git a/src/testsearch.c b/src/testsearch.c
index 4290a4f..380dfba 100644
--- a/src/testsearch.c
+++ b/src/testsearch.c
@@ -42,7 +42,7 @@ TestSearch(void)
SEARCHER_THREAD_CONTEXT *ctx;
POSITION pos;
ULONG u;
- FLAG fOver;
+ GAME_RESULT result;
FLAG fPost = g_Options.fShouldPost;
FLAG fRet = FALSE;
@@ -79,7 +79,7 @@ TestSearch(void)
#if (PERF_COUNTERS && MP)
ClearHelperThreadIdleness();
#endif
- fOver = Iterate(ctx);
+ result = Iterate(ctx);
//
// How long did that take?
@@ -94,7 +94,7 @@ TestSearch(void)
//
// Did we get a sane move?
//
- if (GAME_NOT_OVER == fOver)
+ if (RESULT_IN_PROGRESS == result.eResult)
{
if (FALSE == SanityCheckMove(&pos, ctx->mvRootMove))
{
@@ -104,11 +104,11 @@ TestSearch(void)
}
}
#ifdef DEBUG
- else if (GAME_WHITE_WON == fOver)
+ else if (RESULT_WHITE_WON == result.eResult)
{
ASSERT(InCheck(&pos, BLACK));
}
- else if (GAME_BLACK_WON == fOver)
+ else if (RESULT_BLACK_WON == result.eResult)
{
ASSERT(InCheck(&pos, WHITE));
}
diff --git a/src/testsee.c b/src/testsee.c
index bb7c651..da7df59 100644
--- a/src/testsee.c
+++ b/src/testsee.c
@@ -161,7 +161,7 @@ TestGetAttacks(void)
SEE_LIST rgAsmList;
ULONG color;
-#ifndef _X86_
+#if !defined(_X86_) && !defined(_X64_)
return;
#endif
diff --git a/src/x64.asm b/src/x64.asm
index e80dc67..89351f2 100644
--- a/src/x64.asm
+++ b/src/x64.asm
@@ -3,8 +3,8 @@
[SEGMENT .data]
-[EXTERN _g_VectorDelta]
-[EXTERN _g_PieceData]
+[EXTERN g_VectorDelta]
+[EXTERN g_PieceData]
%ifdef OSX
;;; Note: The reason for this OSX stuff is that there is a bug in the mac
@@ -24,8 +24,8 @@ _g_NasmPieceData:
alignb 32, db 0
times 4 * 4 * 8 db 0
%else
-[EXTERN _g_VectorDelta]
-[EXTERN _g_PieceData]
+[EXTERN g_VectorDelta]
+[EXTERN g_PieceData]
%endif
[SEGMENT .text]
@@ -39,8 +39,7 @@ _g_NasmPieceData:
;;
LastBit:
_LastBit:
- int 3
- bsr rax, rcx
+ bsr rax, rdi
jnz .found
xor rax, rax
ret
@@ -50,14 +49,13 @@ _LastBit:
[GLOBAL FirstBit]
[GLOBAL _FirstBit]
- ;;
+ ;;
;; ULONGLONG CDECL
;; FirstBit(BITBOARD bb)
- ;;
+ ;;
FirstBit:
_FirstBit:
- ;movq rcx, [rsp+8]
- bsf rax, rcx
+ bsf rax, rdi
jnz .found
xor rax, rax
ret
@@ -67,14 +65,14 @@ _FirstBit:
[GLOBAL CountBits]
[GLOBAL _CountBits]
- ;;
+ ;;
;; ULONGLONG CDECL
;; CountBits(BITBOARD bb)
- ;;
-CountBits:
+ ;;
+CountBits:
_CountBits:
xor rax, rax
- mov rcx, [esp+8]
+ mov rcx, rdi
test rcx, rcx
jz .done
.again: add rax, 1
@@ -94,52 +92,191 @@ _CountBits:
iDelta dd -17
dd +15
-%define uSide ebp+0x14
-%define cSquare ebp+0x10
-%define pos ebp+0xC
-%define pList ebp+8
-;; retaddr ebp+4
-;; old ebp ebp
-;; old ebx ebp-4
-;; old esi ebp-8
-;; old edi ebp-0xC
-%define pOldList ebp-0x10
-%define c ebp-0x14
-%define x ebp-0x18
+;; [rbp-0x8] is the saved rbx from the "push rbx" in the prologue below;
+;; locals must start below that, not alias it.
+%define pOldList qword [rbp-0x10]
+%define c dword [rbp-0x14]
+%define x dword [rbp-0x18]
+%define cSquareSave dword [rbp-0x1C]
+%define uSideSave dword [rbp-0x20]
%define _cNonPawns 0x478
%define _uNonPawnCount 0x500
%define _rgSquare 0x0
-
- ;;
- ;; void CDECL
- ;; GetAttacks(SEE_LIST *pList, ; ebp + 8
- ;; POSITION *pos, ; ebp + 0xC
- ;; COOR cSquare, ; ebp + 0x10
- ;; ULONG uSide) ; ebp + 0x14
- ;;
+
+ ;;
+ ;; void CDECL (SysV AMD64 ABI)
+ ;; GetAttacks(SEE_LIST *pList, ; rdi
+ ;; POSITION *pos, ; rsi
+ ;; COOR cSquare, ; edx
+ ;; ULONG uSide) ; ecx
+ ;;
+ ;; NOTE: a square (COOR) is always >= 0, but a square-minus-
+ ;; square delta is not. Any such delta must be sign-extended
+ ;; into a 64-bit register (movsxd) before being used as a
+ ;; scaled memory index: using the raw 32-bit register zero-
+ ;; extends it into a huge positive 64-bit value instead of the
+ ;; intended negative offset. This is the same bug that turned
+ ;; up in the C DISTANCE macro -- see chess.h.
+ ;;
GetAttacks:
_GetAttacks:
- int 3
+ push rbp
+ mov rbp, rsp
+ push rbx
+ sub rsp, 0x20
+
+ mov cSquareSave, edx
+ mov uSideSave, ecx
+
+ mov pOldList, rdi ; pOldList = pList
+ mov dword [rdi], 0 ; pList->uCount = 0
+ add rdi, 4 ; rdi = &pList->data[0]
+
+ ;; ebx = c = cSquare + iDelta[uSide]
+ movsxd rax, uSideSave
+ mov ebx, [iDelta+rax*4]
+ add ebx, cSquareSave
+
+ ;; ecx = pPawn = BLACK_PAWN | uColor
+ mov ecx, uSideSave
+ or ecx, 2
+
+ ;;
+ ;; Check the pawns
+ ;;
+ test ebx, 0x88
+ jnz .try_other_pawn
+ movsxd rax, ebx
+ mov eax, dword [rsi+rax*8+_rgSquare]
+ cmp eax, ecx
+ jne .try_other_pawn
+ mov rax, pOldList
+ mov dword [rax], 1
+ mov dword [rdi], ecx
+ mov dword [rdi+4], ebx
+ mov dword [rdi+8], 100
+ add rdi, 12
+
+.try_other_pawn:
+ add ebx, 2
+ test ebx, 0x88
+ jnz .done_pawns
+ movsxd rax, ebx
+ mov eax, dword [rsi+rax*8+_rgSquare]
+ cmp eax, ecx
+ jne .done_pawns
+ mov dword [rdi], ecx
+ mov dword [rdi+4], ebx
+ mov dword [rdi+8], 100
+ mov rax, pOldList
+ add rdi, 12
+ add dword [rax], 1
+
+.done_pawns:
+ ;;
+ ;; Do pieces
+ ;;
+ ;; x = pos->uNonPawnCount[uSide][0]
+ mov eax, uSideSave
+ shl eax, 5 ; uSide * 32
+ movsxd rax, eax
+ mov ecx, dword [rsi+rax+_uNonPawnCount]
+ mov x, ecx
+
+.loop_continue:
+ mov ecx, x
+ sub ecx, 1
+ cmp ecx, 0
+ jl near .done
+ mov x, ecx
+
+ ;; eax = c = pos->cNonPawns[uSide][x]
+ mov eax, uSideSave
+ shl eax, 4
+ add eax, ecx ; eax = uSide*16 + x
+ movsxd rax, eax
+ mov eax, dword [rsi+rax*4+_cNonPawns]
+ mov c, eax
+
+ ;; ecx = p = pos->pSquare[c]
+ movsxd rax, eax
+ mov ecx, dword [rsi+rax*8+_rgSquare]
+
+ ;; r8 = iIndex = c - cSquare (must stay signed)
+ sub eax, cSquareSave
+ movsxd r8, eax
+
+ ;;
+ ;; If there is no way for that kind of piece to get to this
+ ;; square then keep looking.
+ ;;
+ mov ebx, 1
+ mov r9d, ecx ; r9 = p, survives to .nothing_blocks
+ shr ecx, 1
+ shl ebx, cl
+ test byte [g_VectorDelta+r8*4+512], bl
+ jz .loop_continue
+
+ ;; if (IS_KNIGHT_OR_KING(p)) goto nothing_blocks
+ and ecx, 3
+ cmp ecx, 2
+ mov ecx, c
+ je .nothing_blocks
+
+ ;;
+ ;; Not a knight or king. Check to see if there is a piece in
+ ;; the path from cSquare to c that blocks the attack.
+ ;;
+ movsx ebx, byte [g_VectorDelta+r8*4+515]
+ mov eax, cSquareSave
+.block_loop:
+ add eax, ebx
+ cmp eax, ecx
+ je .nothing_blocks
+ movsxd rdx, eax
+ cmp dword [rsi+rdx*8], 0
+ jne .loop_continue
+ jmp .block_loop
+
+.nothing_blocks:
+ mov dword [rdi], r9d
+ mov dword [rdi+4], ecx
+
+ ;; index into g_PieceData: sizeof(PIECE_DATA) is 24 on x64 (not
+ ;; 16, as on x86) because its trailing "CHAR *szName" widens
+ ;; from 4 to 8 bytes and pads the struct to 8-byte alignment.
+ mov ecx, r9d
+ shr ecx, 1
+ imul ecx, ecx, 24
+ movsxd rcx, ecx
+ mov ebx, dword [g_PieceData+rcx]
+ mov dword [rdi+8], ebx
+
+ mov rax, pOldList
+ add rdi, 12
+ add dword [rax], 1
+ jmp .loop_continue
+
+.done: add rsp, 0x20
+ pop rbx
+ pop rbp
+ ret
%endif ; !CROUTINES
[GLOBAL LockCompareExchange]
[GLOBAL _LockCompareExchange]
-%define uComp esp+0xC
-%define uExch esp+8
-%define pDest esp+4
- ;;
+ ;;
;; ULONG CDECL
- ;; LockCompareExchange(void *dest, ; esp + 4
- ;; ULONG exch, ; esp + 8
- ;; ULONG comp) ; esp + C
- ;;
+ ;; LockCompareExchange(void *dest, ; rdi (SysV AMD64 ABI)
+ ;; ULONG exch, ; esi
+ ;; ULONG comp) ; edx
+ ;;
LockCompareExchange:
_LockCompareExchange:
- mov ecx, [pDest]
- mov edx, [uExch]
- mov eax, [uComp]
- lock cmpxchg dword [ecx], edx
+ mov eax, edx ; comp -> eax (cmpxchg's implicit compare reg)
+ mov rcx, rdi ; dest pointer -> rcx
+ lock cmpxchg dword [rcx], esi
ret
int 3
@@ -147,13 +284,13 @@ _LockCompareExchange:
[GLOBAL _LockIncrement]
;;
;; ULONG CDECL
- ;; LockIncrement(ULONG *pDest)
- ;;
+ ;; LockIncrement(ULONG *pDest) ; rdi (SysV AMD64 ABI)
+ ;;
LockIncrement:
_LockIncrement:
- mov ecx, [pDest]
+ mov rcx, rdi
mov eax, 1
- lock xadd [ecx], eax
+ lock xadd [rcx], eax
add eax, 1
ret
int 3
@@ -162,13 +299,13 @@ _LockIncrement:
[GLOBAL _LockDecrement]
;;
;; ULONG CDECL
- ;; LockDecrement(ULONG *pDest)
- ;;
+ ;; LockDecrement(ULONG *pDest) ; rdi (SysV AMD64 ABI)
+ ;;
LockDecrement:
_LockDecrement:
- mov ecx, [pDest]
+ mov rcx, rdi
mov eax, -1
- lock xadd [ecx], eax
+ lock xadd [rcx], eax
add eax, -1
ret
int 3