diff options
| author | Scott Gasch <[email protected]> | 2026-08-23 11:28:49 -0700 |
|---|---|---|
| committer | Scott Gasch <[email protected]> | 2026-08-23 11:28:49 -0700 |
| commit | df8facc22815f9eca0897e27ccce30701ba95322 (patch) | |
| tree | bf90ad7ff19383fbac9b0a163f31bf3a9fb11fbe | |
| parent | dc0c9dbf405095a6483705b153f4c85c64499d4e (diff) | |
Clean X64 build and ported GetAttacks to x64.
| -rwxr-xr-x | src/chess.h | 41 | ||||
| -rwxr-xr-x | src/eval.c | 30 | ||||
| -rw-r--r-- | src/evalhash.c | 84 | ||||
| -rwxr-xr-x | src/hash.c | 2 | ||||
| -rw-r--r-- | src/recogn.c | 30 | ||||
| -rwxr-xr-x | src/root.c | 1 | ||||
| -rwxr-xr-x | src/search.c | 21 | ||||
| -rwxr-xr-x | src/split.c | 23 | ||||
| -rw-r--r-- | src/testsearch.c | 10 | ||||
| -rw-r--r-- | src/testsee.c | 2 | ||||
| -rw-r--r-- | src/x64.asm | 247 |
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 @@ -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 @@ -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); @@ -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 |
