/*1Stockfish, a UCI chess playing engine derived from Glaurung 2.12Copyright (C) 2004-2025 The Stockfish developers (see AUTHORS file)34Stockfish is free software: you can redistribute it and/or modify5it under the terms of the GNU General Public License as published by6the Free Software Foundation, either version 3 of the License, or7(at your option) any later version.89Stockfish is distributed in the hope that it will be useful,10but WITHOUT ANY WARRANTY; without even the implied warranty of11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12GNU General Public License for more details.1314You should have received a copy of the GNU General Public License15along with this program. If not, see <http://www.gnu.org/licenses/>.16*/1718#include "tbprobe.h"1920#include <algorithm>21#include <atomic>22#include <cassert>23#include <cstdint>24#include <cstdlib>25#include <cstring>26#include <deque>27#include <fstream>28#include <initializer_list>29#include <iostream>30#include <mutex>31#include <sstream>32#include <string_view>33#include <sys/stat.h>34#include <type_traits>35#include <utility>36#include <vector>3738#include "../bitboard.h"39#include "../misc.h"40#include "../movegen.h"41#include "../position.h"42#include "../search.h"43#include "../types.h"44#include "../ucioption.h"4546#ifndef _WIN3247#include <fcntl.h>48#include <sys/mman.h>49#include <unistd.h>50#else51#define WIN32_LEAN_AND_MEAN52#ifndef NOMINMAX53#define NOMINMAX // Disable macros min() and max()54#endif55#include <windows.h>56#endif5758using namespace Stockfish::Tablebases;5960int Stockfish::Tablebases::MaxCardinality;6162namespace Stockfish {6364namespace {6566constexpr int TBPIECES = 7; // Max number of supported pieces67constexpr int MAX_DTZ =681 << 18; // Max DTZ supported times 2, large enough to deal with the syzygy TB limit.6970enum {71BigEndian,72LittleEndian73};74enum TBType {75WDL,76DTZ77}; // Used as template parameter7879// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables80enum TBFlag {81STM = 1,82Mapped = 2,83WinPlies = 4,84LossPlies = 8,85Wide = 16,86SingleValue = 12887};8889inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }90inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }9192constexpr std::string_view PieceToChar = " PNBRQK pnbrqk";9394int MapPawns[SQUARE_NB];95int MapB1H1H7[SQUARE_NB];96int MapA1D1D4[SQUARE_NB];97int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB]9899int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements100int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB]101int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D]102103// Comparison function to sort leading pawns in ascending MapPawns[] order104bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; }105int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); }106107constexpr Value WDL_to_value[] = {-VALUE_MATE + MAX_PLY + 1, VALUE_DRAW - 2, VALUE_DRAW,108VALUE_DRAW + 2, VALUE_MATE - MAX_PLY - 1};109110template<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>111inline void swap_endian(T& x) {112static_assert(std::is_unsigned_v<T>, "Argument of swap_endian not unsigned");113114uint8_t tmp, *c = (uint8_t*) &x;115for (int i = 0; i < Half; ++i)116tmp = c[i], c[i] = c[End - i], c[End - i] = tmp;117}118template<>119inline void swap_endian<uint8_t>(uint8_t&) {}120121template<typename T, int LE>122T number(void* addr) {123T v;124125if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare)126std::memcpy(&v, addr, sizeof(T));127else128v = *((T*) addr);129130if (LE != IsLittleEndian)131swap_endian(v);132return v;133}134135// DTZ tables don't store valid scores for moves that reset the rule50 counter136// like captures and pawn moves but we can easily recover the correct dtz of the137// previous move if we know the position's WDL score.138int dtz_before_zeroing(WDLScore wdl) {139return wdl == WDLWin ? 1140: wdl == WDLCursedWin ? 101141: wdl == WDLBlessedLoss ? -101142: wdl == WDLLoss ? -1143: 0;144}145146// Return the sign of a number (-1, 0, 1)147template<typename T>148int sign_of(T val) {149return (T(0) < val) - (val < T(0));150}151152// Numbers in little-endian used by sparseIndex[] to point into blockLength[]153struct SparseEntry {154char block[4]; // Number of block155char offset[2]; // Offset within the block156};157158static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");159160using Sym = uint16_t; // Huffman symbol161162struct LR {163enum Side {164Left,165Right166};167168uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12169// bits is the right-hand symbol. If the symbol has length 1,170// then the left-hand symbol is the stored value.171template<Side S>172Sym get() {173return S == Left ? ((lr[1] & 0xF) << 8) | lr[0]174: S == Right ? (lr[2] << 4) | (lr[1] >> 4)175: (assert(false), Sym(-1));176}177};178179static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes");180181// Tablebases data layout is structured as following:182//183// TBFile: memory maps/unmaps the physical .rtbw and .rtbz files184// TBTable: one object for each file with corresponding indexing information185// TBTables: has ownership of TBTable objects, keeping a list and a hash186187// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are188// memory mapped for best performance. Files are mapped at first access: at init189// time only existence of the file is checked.190class TBFile: public std::ifstream {191192std::string fname;193194public:195// Look for and open the file among the Paths directories where the .rtbw196// and .rtbz files can be found. Multiple directories are separated by ";"197// on Windows and by ":" on Unix-based operating systems.198//199// Example:200// C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6201static std::string Paths;202203TBFile(const std::string& f) {204205#ifndef _WIN32206constexpr char SepChar = ':';207#else208constexpr char SepChar = ';';209#endif210std::stringstream ss(Paths);211std::string path;212213while (std::getline(ss, path, SepChar))214{215fname = path + "/" + f;216std::ifstream::open(fname);217if (is_open())218return;219}220}221222// Memory map the file and check it.223uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {224if (is_open())225close(); // Need to re-open to get native file descriptor226227#ifndef _WIN32228struct stat statbuf;229int fd = ::open(fname.c_str(), O_RDONLY);230231if (fd == -1)232return *baseAddress = nullptr, nullptr;233234fstat(fd, &statbuf);235236if (statbuf.st_size % 64 != 16)237{238std::cerr << "Corrupt tablebase file " << fname << std::endl;239exit(EXIT_FAILURE);240}241242*mapping = statbuf.st_size;243*baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);244#if defined(MADV_RANDOM)245madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);246#endif247::close(fd);248249if (*baseAddress == MAP_FAILED)250{251std::cerr << "Could not mmap() " << fname << std::endl;252exit(EXIT_FAILURE);253}254#else255// Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.256HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,257OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);258259if (fd == INVALID_HANDLE_VALUE)260return *baseAddress = nullptr, nullptr;261262DWORD size_high;263DWORD size_low = GetFileSize(fd, &size_high);264265if (size_low % 64 != 16)266{267std::cerr << "Corrupt tablebase file " << fname << std::endl;268exit(EXIT_FAILURE);269}270271HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr);272CloseHandle(fd);273274if (!mmap)275{276std::cerr << "CreateFileMapping() failed" << std::endl;277exit(EXIT_FAILURE);278}279280*mapping = uint64_t(mmap);281*baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);282283if (!*baseAddress)284{285std::cerr << "MapViewOfFile() failed, name = " << fname286<< ", error = " << GetLastError() << std::endl;287exit(EXIT_FAILURE);288}289#endif290uint8_t* data = (uint8_t*) *baseAddress;291292constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}};293294if (memcmp(data, Magics[type == WDL], 4))295{296std::cerr << "Corrupted table in file " << fname << std::endl;297unmap(*baseAddress, *mapping);298return *baseAddress = nullptr, nullptr;299}300301return data + 4; // Skip Magics's header302}303304static void unmap(void* baseAddress, uint64_t mapping) {305306#ifndef _WIN32307munmap(baseAddress, mapping);308#else309UnmapViewOfFile(baseAddress);310CloseHandle((HANDLE) mapping);311#endif312}313};314315std::string TBFile::Paths;316317// struct PairsData contains low-level indexing information to access TB data.318// There are 8, 4, or 2 PairsData records for each TBTable, according to the type319// of table and if positions have pawns or not. It is populated at first access.320struct PairsData {321uint8_t flags; // Table flags, see enum TBFlag322uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols323uint8_t minSymLen; // Minimum length in bits of the Huffman symbols324uint32_t blocksNum; // Number of blocks in the TB file325size_t sizeofBlock; // Block size in bytes326size_t span; // About every span values there is a SparseIndex[] entry327Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value328LR* btree; // btree[sym] stores the left and right symbols that expand sym329uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536330uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum331SparseEntry* sparseIndex; // Partial indices into blockLength[]332size_t sparseIndexSize; // Size of SparseIndex[] table333uint8_t* data; // Start of Huffman compressed data334std::vector<uint64_t>335base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l336std::vector<uint8_t>337symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256338Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups339uint64_t groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces340int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1)341uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)342};343344// struct TBTable contains indexing information to access the corresponding TBFile.345// There are 2 types of TBTable, corresponding to a WDL or a DTZ file. TBTable346// is populated at init time but the nested PairsData records are populated at347// first access, when the corresponding file is memory mapped.348template<TBType Type>349struct TBTable {350using Ret = std::conditional_t<Type == WDL, WDLScore, int>;351352static constexpr int Sides = Type == WDL ? 2 : 1;353354std::atomic_bool ready;355void* baseAddress;356uint8_t* map;357uint64_t mapping;358Key key;359Key key2;360int pieceCount;361bool hasPawns;362bool hasUniquePieces;363uint8_t pawnCount[2]; // [Lead color / other color]364PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0]365366PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; }367368TBTable() :369ready(false),370baseAddress(nullptr) {}371explicit TBTable(const std::string& code);372explicit TBTable(const TBTable<WDL>& wdl);373374~TBTable() {375if (baseAddress)376TBFile::unmap(baseAddress, mapping);377}378};379380template<>381TBTable<WDL>::TBTable(const std::string& code) :382TBTable() {383384StateInfo st;385Position pos;386387key = pos.set(code, WHITE, &st).material_key();388pieceCount = pos.count<ALL_PIECES>();389hasPawns = pos.pieces(PAWN);390391hasUniquePieces = false;392for (Color c : {WHITE, BLACK})393for (PieceType pt = PAWN; pt < KING; ++pt)394if (popcount(pos.pieces(c, pt)) == 1)395hasUniquePieces = true;396397// Set the leading color. In case both sides have pawns the leading color398// is the side with fewer pawns because this leads to better compression.399bool c = !pos.count<PAWN>(BLACK)400|| (pos.count<PAWN>(WHITE) && pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));401402pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);403pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);404405key2 = pos.set(code, BLACK, &st).material_key();406}407408template<>409TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) :410TBTable() {411412// Use the corresponding WDL table to avoid recalculating all from scratch413key = wdl.key;414key2 = wdl.key2;415pieceCount = wdl.pieceCount;416hasPawns = wdl.hasPawns;417hasUniquePieces = wdl.hasUniquePieces;418pawnCount[0] = wdl.pawnCount[0];419pawnCount[1] = wdl.pawnCount[1];420}421422// class TBTables creates and keeps ownership of the TBTable objects, one for423// each TB file found. It supports a fast, hash-based, table lookup. Populated424// at init time, accessed at probe time.425class TBTables {426427struct Entry {428Key key;429TBTable<WDL>* wdl;430TBTable<DTZ>* dtz;431432template<TBType Type>433TBTable<Type>* get() const {434return (TBTable<Type>*) (Type == WDL ? (void*) wdl : (void*) dtz);435}436};437438static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb439static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket440441Entry hashTable[Size + Overflow];442443std::deque<TBTable<WDL>> wdlTable;444std::deque<TBTable<DTZ>> dtzTable;445size_t foundDTZFiles = 0;446size_t foundWDLFiles = 0;447448void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {449uint32_t homeBucket = uint32_t(key) & (Size - 1);450Entry entry{key, wdl, dtz};451452// Ensure last element is empty to avoid overflow when looking up453for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)454{455Key otherKey = hashTable[bucket].key;456if (otherKey == key || !hashTable[bucket].get<WDL>())457{458hashTable[bucket] = entry;459return;460}461462// Robin Hood hashing: If we've probed for longer than this element,463// insert here and search for a new spot for the other element instead.464uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1);465if (otherHomeBucket > homeBucket)466{467std::swap(entry, hashTable[bucket]);468key = otherKey;469homeBucket = otherHomeBucket;470}471}472std::cerr << "TB hash table size too low!" << std::endl;473exit(EXIT_FAILURE);474}475476public:477template<TBType Type>478TBTable<Type>* get(Key key) {479for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry)480{481if (entry->key == key || !entry->get<Type>())482return entry->get<Type>();483}484}485486void clear() {487memset(hashTable, 0, sizeof(hashTable));488wdlTable.clear();489dtzTable.clear();490foundDTZFiles = 0;491foundWDLFiles = 0;492}493494void info() const {495sync_cout << "info string Found " << foundWDLFiles << " WDL and " << foundDTZFiles496<< " DTZ tablebase files (up to " << MaxCardinality << "-man)." << sync_endl;497}498499void add(const std::vector<PieceType>& pieces);500};501502TBTables TBTables;503504// If the corresponding file exists two new objects TBTable<WDL> and TBTable<DTZ>505// are created and added to the lists and hash table. Called at init time.506void TBTables::add(const std::vector<PieceType>& pieces) {507508std::string code;509510for (PieceType pt : pieces)511code += PieceToChar[pt];512code.insert(code.find('K', 1), "v");513514TBFile file_dtz(code + ".rtbz"); // KRK -> KRvK515if (file_dtz.is_open())516{517file_dtz.close();518foundDTZFiles++;519}520521TBFile file(code + ".rtbw"); // KRK -> KRvK522523if (!file.is_open()) // Only WDL file is checked524return;525526file.close();527foundWDLFiles++;528529MaxCardinality = std::max(int(pieces.size()), MaxCardinality);530531wdlTable.emplace_back(code);532dtzTable.emplace_back(wdlTable.back());533534// Insert into the hash keys for both colors: KRvK with KR white and black535insert(wdlTable.back().key, &wdlTable.back(), &dtzTable.back());536insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back());537}538539// TB tables are compressed with canonical Huffman code. The compressed data is divided into540// blocks of size d->sizeofBlock, and each block stores a variable number of symbols.541// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols542// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536543// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after544// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most545// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly546// of draws or mostly of wins, but such tables are actually quite common. In principle, the547// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for548// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so549// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long.550// The generator picks the size that leads to the smallest table. The "book" of symbols and551// Huffman codes are the same for all blocks in the table. A non-symmetric pawnless TB file552// will have one table for wtm and one for btm, a TB file with pawns will have tables per553// file a,b,c,d also, in this case, one set for wtm and one for btm.554int decompress_pairs(PairsData* d, uint64_t idx) {555556// Special case where all table positions store the same value557if (d->flags & TBFlag::SingleValue)558return d->minSymLen;559560// First we need to locate the right block that stores the value at index "idx".561// Because each block n stores blockLength[n] + 1 values, the index i of the block562// that contains the value at position idx is:563//564// for (i = -1, sum = 0; sum <= idx; i++)565// sum += blockLength[i + 1] + 1;566//567// This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that568// point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry569// that stores the blockLength[] index and the offset within that block of the value570// with index I(k), where:571//572// I(k) = k * d->span + d->span / 2 (1)573574// First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)575uint32_t k = uint32_t(idx / d->span);576577// Then we read the corresponding SparseIndex[] entry578uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);579int offset = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);580581// Now compute the difference idx - I(k). From the definition of k, we know that582//583// idx = k * d->span + idx % d->span (2)584//585// So from (1) and (2) we can compute idx - I(K):586int diff = idx % d->span - d->span / 2;587588// Sum the above to offset to find the offset corresponding to our idx589offset += diff;590591// Move to the previous/next block, until we reach the correct block that contains idx,592// that is when 0 <= offset <= d->blockLength[block]593while (offset < 0)594offset += d->blockLength[--block] + 1;595596while (offset > d->blockLength[block])597offset -= d->blockLength[block++] + 1;598599// Finally, we find the start address of our block of canonical Huffman symbols600uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(block) * d->sizeofBlock));601602// Read the first 64 bits in our block, this is a (truncated) sequence of603// unknown number of symbols of unknown length but we know the first one604// is at the beginning of this 64-bit sequence.605uint64_t buf64 = number<uint64_t, BigEndian>(ptr);606ptr += 2;607int buf64Size = 64;608Sym sym;609610while (true)611{612int len = 0; // This is the symbol length - d->min_sym_len613614// Now get the symbol length. For any symbol s64 of length l right-padded615// to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we616// can find the symbol length iterating through base64[].617while (buf64 < d->base64[len])618++len;619620// All the symbols of a given length are consecutive integers (numerical621// sequence property), so we can compute the offset of our symbol of622// length len, stored at the beginning of buf64.623sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen));624625// Now add the value of the lowest symbol of length len to get our symbol626sym += number<Sym, LittleEndian>(&d->lowestSym[len]);627628// If our offset is within the number of values represented by symbol sym,629// we are done.630if (offset < d->symlen[sym] + 1)631break;632633// ...otherwise update the offset and continue to iterate634offset -= d->symlen[sym] + 1;635len += d->minSymLen; // Get the real length636buf64 <<= len; // Consume the just processed symbol637buf64Size -= len;638639if (buf64Size <= 32)640{ // Refill the buffer641buf64Size += 32;642buf64 |= uint64_t(number<uint32_t, BigEndian>(ptr++)) << (64 - buf64Size);643}644}645646// Now we have our symbol that expands into d->symlen[sym] + 1 symbols.647// We binary-search for our value recursively expanding into the left and648// right child symbols until we reach a leaf node where symlen[sym] + 1 == 1649// that will store the value we need.650while (d->symlen[sym])651{652Sym left = d->btree[sym].get<LR::Left>();653654// If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and655// expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then656// we know that, for instance, the tenth value (offset = 10) will be on657// the left side because in Recursive Pairing child symbols are adjacent.658if (offset < d->symlen[left] + 1)659sym = left;660else661{662offset -= d->symlen[left] + 1;663sym = d->btree[sym].get<LR::Right>();664}665}666667return d->btree[sym].get<LR::Left>();668}669670bool check_dtz_stm(TBTable<WDL>*, int, File) { return true; }671672bool check_dtz_stm(TBTable<DTZ>* entry, int stm, File f) {673674auto flags = entry->get(stm, f)->flags;675return (flags & TBFlag::STM) == stm || ((entry->key == entry->key2) && !entry->hasPawns);676}677678// DTZ scores are sorted by frequency of occurrence and then assigned the679// values 0, 1, 2, ... in order of decreasing frequency. This is done for each680// of the four WDLScore values. The mapping information necessary to reconstruct681// the original values are stored in the TB file and read during map[] init.682WDLScore map_score(TBTable<WDL>*, File, int value, WDLScore) { return WDLScore(value - 2); }683684int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {685686constexpr int WDLMap[] = {1, 3, 0, 2, 0};687688auto flags = entry->get(0, f)->flags;689690uint8_t* map = entry->map;691uint16_t* idx = entry->get(0, f)->map_idx;692if (flags & TBFlag::Mapped)693{694if (flags & TBFlag::Wide)695value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value];696else697value = map[idx[WDLMap[wdl + 2]] + value];698}699700// DTZ tables store distance to zero in number of moves or plies. We701// want to return plies, so we have to convert to plies when needed.702if ((wdl == WDLWin && !(flags & TBFlag::WinPlies))703|| (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin704|| wdl == WDLBlessedLoss)705value *= 2;706707return value + 1;708}709710// A temporary fix for the compiler bug with AVX-512. (#4450)711#ifdef USE_AVX512712#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15713#define CLANG_AVX512_BUG_FIX __attribute__((optnone))714#endif715#endif716717#ifndef CLANG_AVX512_BUG_FIX718#define CLANG_AVX512_BUG_FIX719#endif720721// Compute a unique index out of a position and use it to probe the TB file. To722// encode k pieces of the same type and color, first sort the pieces by square in723// ascending order s1 <= s2 <= ... <= sk then compute the unique index as:724//725// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk]726//727template<typename T, typename Ret = typename T::Ret>728CLANG_AVX512_BUG_FIX Ret729do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {730731Square squares[TBPIECES];732Piece pieces[TBPIECES];733uint64_t idx;734int next = 0, size = 0, leadPawnsCnt = 0;735PairsData* d;736Bitboard b, leadPawns = 0;737File tbFile = FILE_A;738739// A given TB entry like KRK has associated two material keys: KRvk and Kvkr.740// If both sides have the same pieces keys are equal. In this case TB tables741// only stores the 'white to move' case, so if the position to lookup has black742// to move, we need to switch the color and flip the squares before to lookup.743bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move());744745// TB files are calculated for white as the stronger side. For instance, we746// have KRvK, not KvKR. A position where the stronger side is white will have747// its material key == entry->key, otherwise we have to switch the color and748// flip the squares before to lookup.749bool blackStronger = (pos.material_key() != entry->key);750751int flipColor = (symmetricBlackToMove || blackStronger) * 8;752int flipSquares = (symmetricBlackToMove || blackStronger) * 56;753int stm = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move();754755// For pawns, TB files store 4 separate tables according if leading pawn is on756// file a, b, c or d after reordering. The leading pawn is the one with maximum757// MapPawns[] value, that is the one most toward the edges and with lowest rank.758if (entry->hasPawns)759{760761// In all the 4 tables, pawns are at the beginning of the piece sequence and762// their color is the reference one. So we just pick the first one.763Piece pc = Piece(entry->get(0, 0)->pieces[0] ^ flipColor);764765assert(type_of(pc) == PAWN);766767leadPawns = b = pos.pieces(color_of(pc), PAWN);768do769squares[size++] = pop_lsb(b) ^ flipSquares;770while (b);771772leadPawnsCnt = size;773774std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));775776tbFile = File(edge_distance(file_of(squares[0])));777}778779// DTZ tables are one-sided, i.e. they store positions only for white to780// move or only for black to move, so check for side to move to be stm,781// early exit otherwise.782if (!check_dtz_stm(entry, stm, tbFile))783return *result = CHANGE_STM, Ret();784785// Now we are ready to get all the position pieces (but the lead pawns) and786// directly map them to the correct color and square.787b = pos.pieces() ^ leadPawns;788do789{790Square s = pop_lsb(b);791squares[size] = s ^ flipSquares;792pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);793} while (b);794795assert(size >= 2);796797d = entry->get(stm, tbFile);798799// Then we reorder the pieces to have the same sequence as the one stored800// in pieces[i]: the sequence that ensures the best compression.801for (int i = leadPawnsCnt; i < size - 1; ++i)802for (int j = i + 1; j < size; ++j)803if (d->pieces[i] == pieces[j])804{805std::swap(pieces[i], pieces[j]);806std::swap(squares[i], squares[j]);807break;808}809810// Now we map again the squares so that the square of the lead piece is in811// the triangle A1-D1-D4.812if (file_of(squares[0]) > FILE_D)813for (int i = 0; i < size; ++i)814squares[i] = flip_file(squares[i]);815816// Encode leading pawns starting with the one with minimum MapPawns[] and817// proceeding in ascending order.818if (entry->hasPawns)819{820idx = LeadPawnIdx[leadPawnsCnt][squares[0]];821822std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp);823824for (int i = 1; i < leadPawnsCnt; ++i)825idx += Binomial[i][MapPawns[squares[i]]];826827goto encode_remaining; // With pawns we have finished special treatments828}829830// In positions without pawns, we further flip the squares to ensure leading831// piece is below RANK_5.832if (rank_of(squares[0]) > RANK_4)833for (int i = 0; i < size; ++i)834squares[i] = flip_rank(squares[i]);835836// Look for the first piece of the leading group not on the A1-D4 diagonal837// and ensure it is mapped below the diagonal.838for (int i = 0; i < d->groupLen[0]; ++i)839{840if (!off_A1H8(squares[i]))841continue;842843if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1844for (int j = i; j < size; ++j)845squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);846break;847}848849// Encode the leading group.850//851// Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR852// and bK (each 0...63). The simplest way to map this position to an index853// is like this:854//855// index = wK * 64 * 64 + wR * 64 + bK;856//857// But this way the TB is going to have 64*64*64 = 262144 positions, with858// lots of positions being equivalent (because they are mirrors of each859// other) and lots of positions being invalid (two pieces on one square,860// adjacent kings, etc.).861// Usually the first step is to take the wK and bK together. There are just862// 462 ways legal and not-mirrored ways to place the wK and bK on the board.863// Once we have placed the wK and bK, there are 62 squares left for the wR864// Mapping its square from 0..63 to available squares 0..61 can be done like:865//866// wR -= (wR > wK) + (wR > bK);867//868// In words: if wR "comes later" than wK, we deduct 1, and the same if wR869// "comes later" than bK. In case of two same pieces like KRRvK we want to870// place the two Rs "together". If we have 62 squares left, we can place two871// Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be872// swapped and still get the same position.)873//874// In case we have at least 3 unique pieces (including kings) we encode them875// together.876if (entry->hasUniquePieces)877{878879int adjust1 = squares[1] > squares[0];880int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]);881882// First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3883// triangle to 0...5. There are 63 squares for second piece and 62884// (mapped to 0...61) for the third.885if (off_A1H8(squares[0]))886idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2;887888// First piece is on a1-h8 diagonal, second below: map this occurrence to889// 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal890// to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27.891else if (off_A1H8(squares[1]))892idx = (6 * 63 + rank_of(squares[0]) * 28 + MapB1H1H7[squares[1]]) * 62 + squares[2]893- adjust2;894895// First two pieces are on a1-h8 diagonal, third below896else if (off_A1H8(squares[2]))897idx = 6 * 63 * 62 + 4 * 28 * 62 + rank_of(squares[0]) * 7 * 28898+ (rank_of(squares[1]) - adjust1) * 28 + MapB1H1H7[squares[2]];899900// All 3 pieces on the diagonal a1-h8901else902idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + rank_of(squares[0]) * 7 * 6903+ (rank_of(squares[1]) - adjust1) * 6 + (rank_of(squares[2]) - adjust2);904}905else906// We don't have at least 3 unique pieces, like in KRRvKBB, just map907// the kings.908idx = MapKK[MapA1D1D4[squares[0]]][squares[1]];909910encode_remaining:911idx *= d->groupIdx[0];912Square* groupSq = squares + d->groupLen[0];913914// Encode remaining pawns and then pieces according to square, in ascending order915bool remainingPawns = entry->hasPawns && entry->pawnCount[1];916917while (d->groupLen[++next])918{919std::stable_sort(groupSq, groupSq + d->groupLen[next]);920uint64_t n = 0;921922// Map down a square if "comes later" than a square in the previous923// groups (similar to what was done earlier for leading group pieces).924for (int i = 0; i < d->groupLen[next]; ++i)925{926auto f = [&](Square s) { return groupSq[i] > s; };927auto adjust = std::count_if(squares, groupSq, f);928n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns];929}930931remainingPawns = false;932idx += n * d->groupIdx[next];933groupSq += d->groupLen[next];934}935936// Now that we have the index, decompress the pair and get the score937return map_score(entry, tbFile, decompress_pairs(d, idx), wdl);938}939940// Group together pieces that will be encoded together. The general rule is that941// a group contains pieces of the same type and color. The exception is the leading942// group that, in case of positions without pawns, can be formed by 3 different943// pieces (default) or by the king pair when there is not a unique piece apart944// from the kings. When there are pawns, pawns are always first in pieces[].945//946// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K947//948// The actual grouping depends on the TB generator and can be inferred from the949// sequence of pieces in piece[] array.950template<typename T>951void set_groups(T& e, PairsData* d, int order[], File f) {952953int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2;954d->groupLen[n] = 1;955956// Number of pieces per group is stored in groupLen[], for instance in KRKN957// the encoder will default on '111', so groupLen[] will be (3, 1).958for (int i = 1; i < e.pieceCount; ++i)959if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1])960d->groupLen[n]++;961else962d->groupLen[++n] = 1;963964d->groupLen[++n] = 0; // Zero-terminated965966// The sequence in pieces[] defines the groups, but not the order in which967// they are encoded. If the pieces in a group g can be combined on the board968// in N(g) different ways, then the position encoding will be of the form:969//970// g1 * N(g2) * N(g3) + g2 * N(g3) + g3971//972// This ensures unique encoding for the whole position. The order of the973// groups is a per-table parameter and could not follow the canonical leading974// pawns/pieces -> remaining pawns -> remaining pieces. In particular the975// first group is at order[0] position and the remaining pawns, when present,976// are at order[1] position.977bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides978int next = pp ? 2 : 1;979int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0);980uint64_t idx = 1;981982for (int k = 0; next < n || k == order[0] || k == order[1]; ++k)983if (k == order[0]) // Leading pawns or pieces984{985d->groupIdx[0] = idx;986idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] : e.hasUniquePieces ? 31332 : 462;987}988else if (k == order[1]) // Remaining pawns989{990d->groupIdx[1] = idx;991idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]];992}993else // Remaining pieces994{995d->groupIdx[next] = idx;996idx *= Binomial[d->groupLen[next]][freeSquares];997freeSquares -= d->groupLen[next++];998}9991000d->groupIdx[n] = idx;1001}10021003// In Recursive Pairing each symbol represents a pair of children symbols. So1004// read d->btree[] symbols data and expand each one in his left and right child1005// symbol until reaching the leaves that represent the symbol value.1006uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {10071008visited[s] = true; // We can set it now because tree is acyclic1009Sym sr = d->btree[s].get<LR::Right>();10101011if (sr == 0xFFF)1012return 0;10131014Sym sl = d->btree[s].get<LR::Left>();10151016if (!visited[sl])1017d->symlen[sl] = set_symlen(d, sl, visited);10181019if (!visited[sr])1020d->symlen[sr] = set_symlen(d, sr, visited);10211022return d->symlen[sl] + d->symlen[sr] + 1;1023}10241025uint8_t* set_sizes(PairsData* d, uint8_t* data) {10261027d->flags = *data++;10281029if (d->flags & TBFlag::SingleValue)1030{1031d->blocksNum = d->blockLengthSize = 0;1032d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init1033d->minSymLen = *data++; // Here we store the single value1034return data;1035}10361037// groupLen[] is a zero-terminated list of group lengths, the last groupIdx[]1038// element stores the biggest index that is the tb size.1039uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen];10401041d->sizeofBlock = 1ULL << *data++;1042d->span = 1ULL << *data++;1043d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up1044auto padding = number<uint8_t, LittleEndian>(data++);1045d->blocksNum = number<uint32_t, LittleEndian>(data);1046data += sizeof(uint32_t);1047d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]1048// does not point out of range.1049d->maxSymLen = *data++;1050d->minSymLen = *data++;1051d->lowestSym = (Sym*) data;1052d->base64.resize(d->maxSymLen - d->minSymLen + 1);10531054// See https://en.wikipedia.org/wiki/Huffman_coding1055// The canonical code is ordered such that longer symbols (in terms of1056// the number of bits of their Huffman code) have a lower numeric value,1057// so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian).1058// Starting from this we compute a base64[] table indexed by symbol length1059// and containing 64 bit values so that d->base64[i] >= d->base64[i+1].10601061// Implementation note: we first cast the unsigned size_t "base64.size()"1062// to a signed int "base64_size" variable and then we are able to subtract 2,1063// avoiding unsigned overflow warnings.10641065int base64_size = static_cast<int>(d->base64.size());1066for (int i = base64_size - 2; i >= 0; --i)1067{1068d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])1069- number<Sym, LittleEndian>(&d->lowestSym[i + 1]))1070/ 2;10711072assert(d->base64[i] * 2 >= d->base64[i + 1]);1073}10741075// Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more1076// than d->base64[i+1] and given the above assert condition, we ensure that1077// d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i1078// and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i].1079for (int i = 0; i < base64_size; ++i)1080d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits10811082data += base64_size * sizeof(Sym);1083d->symlen.resize(number<uint16_t, LittleEndian>(data));1084data += sizeof(uint16_t);1085d->btree = (LR*) data;10861087// The compression scheme used is "Recursive Pairing", that replaces the most1088// frequent adjacent pair of symbols in the source message by a new symbol,1089// reevaluating the frequencies of all of the symbol pairs with respect to1090// the extended alphabet, and then repeating the process.1091// See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf1092std::vector<bool> visited(d->symlen.size());10931094for (std::size_t sym = 0; sym < d->symlen.size(); ++sym)1095if (!visited[sym])1096d->symlen[sym] = set_symlen(d, sym, visited);10971098return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1);1099}11001101uint8_t* set_dtz_map(TBTable<WDL>&, uint8_t* data, File) { return data; }11021103uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {11041105e.map = data;11061107for (File f = FILE_A; f <= maxFile; ++f)1108{1109auto flags = e.get(0, f)->flags;1110if (flags & TBFlag::Mapped)1111{1112if (flags & TBFlag::Wide)1113{1114data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table1115for (int i = 0; i < 4; ++i)1116{ // Sequence like 3,x,x,x,1,x,0,2,x,x1117e.get(0, f)->map_idx[i] = uint16_t((uint16_t*) data - (uint16_t*) e.map + 1);1118data += 2 * number<uint16_t, LittleEndian>(data) + 2;1119}1120}1121else1122{1123for (int i = 0; i < 4; ++i)1124{1125e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1);1126data += *data + 1;1127}1128}1129}1130}11311132return data += uintptr_t(data) & 1; // Word alignment1133}11341135// Populate entry's PairsData records with data from the just memory-mapped file.1136// Called at first access.1137template<typename T>1138void set(T& e, uint8_t* data) {11391140PairsData* d;11411142enum {1143Split = 1,1144HasPawns = 21145};11461147assert(e.hasPawns == bool(*data & HasPawns));1148assert((e.key != e.key2) == bool(*data & Split));11491150data++; // First byte stores flags11511152const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1;1153const File maxFile = e.hasPawns ? FILE_D : FILE_A;11541155bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides11561157assert(!pp || e.pawnCount[0]);11581159for (File f = FILE_A; f <= maxFile; ++f)1160{11611162for (int i = 0; i < sides; i++)1163*e.get(i, f) = PairsData();11641165int order[][2] = {{*data & 0xF, pp ? *(data + 1) & 0xF : 0xF},1166{*data >> 4, pp ? *(data + 1) >> 4 : 0xF}};1167data += 1 + pp;11681169for (int k = 0; k < e.pieceCount; ++k, ++data)1170for (int i = 0; i < sides; i++)1171e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF);11721173for (int i = 0; i < sides; ++i)1174set_groups(e, e.get(i, f), order[i], f);1175}11761177data += uintptr_t(data) & 1; // Word alignment11781179for (File f = FILE_A; f <= maxFile; ++f)1180for (int i = 0; i < sides; i++)1181data = set_sizes(e.get(i, f), data);11821183data = set_dtz_map(e, data, maxFile);11841185for (File f = FILE_A; f <= maxFile; ++f)1186for (int i = 0; i < sides; i++)1187{1188(d = e.get(i, f))->sparseIndex = (SparseEntry*) data;1189data += d->sparseIndexSize * sizeof(SparseEntry);1190}11911192for (File f = FILE_A; f <= maxFile; ++f)1193for (int i = 0; i < sides; i++)1194{1195(d = e.get(i, f))->blockLength = (uint16_t*) data;1196data += d->blockLengthSize * sizeof(uint16_t);1197}11981199for (File f = FILE_A; f <= maxFile; ++f)1200for (int i = 0; i < sides; i++)1201{1202data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment1203(d = e.get(i, f))->data = data;1204data += d->blocksNum * d->sizeofBlock;1205}1206}12071208// If the TB file corresponding to the given position is already memory-mapped1209// then return its base address, otherwise, try to memory map and init it. Called1210// at every probe, memory map, and init only at first access. Function is thread1211// safe and can be called concurrently.1212template<TBType Type>1213void* mapped(TBTable<Type>& e, const Position& pos) {12141215static std::mutex mutex;12161217// Use 'acquire' to avoid a thread reading 'ready' == true while1218// another is still working. (compiler reordering may cause this).1219if (e.ready.load(std::memory_order_acquire))1220return e.baseAddress; // Could be nullptr if file does not exist12211222std::scoped_lock<std::mutex> lk(mutex);12231224if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock1225return e.baseAddress;12261227// Pieces strings in decreasing order for each color, like ("KPP","KR")1228std::string fname, w, b;1229for (PieceType pt = KING; pt >= PAWN; --pt)1230{1231w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]);1232b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]);1233}12341235fname =1236(e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? ".rtbw" : ".rtbz");12371238uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type);12391240if (data)1241set(e, data);12421243e.ready.store(true, std::memory_order_release);1244return e.baseAddress;1245}12461247template<TBType Type, typename Ret = typename TBTable<Type>::Ret>1248Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) {12491250if (pos.count<ALL_PIECES>() == 2) // KvK1251return Ret(WDLDraw);12521253TBTable<Type>* entry = TBTables.get<Type>(pos.material_key());12541255if (!entry || !mapped(*entry, pos))1256return *result = FAIL, Ret();12571258return do_probe_table(pos, entry, wdl, result);1259}12601261// For a position where the side to move has a winning capture it is not necessary1262// to store a winning value so the generator treats such positions as "don't care"1263// and tries to assign to it a value that improves the compression ratio. Similarly,1264// if the side to move has a drawing capture, then the position is at least drawn.1265// If the position is won, then the TB needs to store a win value. But if the1266// position is drawn, the TB may store a loss value if that is better for compression.1267// All of this means that during probing, the engine must look at captures and probe1268// their results and must probe the position itself. The "best" result of these1269// probes is the correct result for the position.1270// DTZ tables do not store values when a following move is a zeroing winning move1271// (winning capture or winning pawn move). Also, DTZ store wrong values for positions1272// where the best move is an ep-move (even if losing). So in all these cases set1273// the state to ZEROING_BEST_MOVE.1274template<bool CheckZeroingMoves>1275WDLScore search(Position& pos, ProbeState* result) {12761277WDLScore value, bestValue = WDLLoss;1278StateInfo st;12791280auto moveList = MoveList<LEGAL>(pos);1281size_t totalCount = moveList.size(), moveCount = 0;12821283for (const Move move : moveList)1284{1285if (!pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))1286continue;12871288moveCount++;12891290pos.do_move(move, st);1291value = -search<false>(pos, result);1292pos.undo_move(move);12931294if (*result == FAIL)1295return WDLDraw;12961297if (value > bestValue)1298{1299bestValue = value;13001301if (value >= WDLWin)1302{1303*result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move1304return value;1305}1306}1307}13081309// In case we have already searched all the legal moves we don't have to probe1310// the TB because the stored score could be wrong. For instance TB tables1311// do not contain information on position with ep rights, so in this case1312// the result of probe_wdl_table is wrong. Also in case of only capture1313// moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to1314// return with ZEROING_BEST_MOVE set.1315bool noMoreMoves = (moveCount && moveCount == totalCount);13161317if (noMoreMoves)1318value = bestValue;1319else1320{1321value = probe_table<WDL>(pos, result);13221323if (*result == FAIL)1324return WDLDraw;1325}13261327// DTZ stores a "don't care" value if bestValue is a win1328if (bestValue >= value)1329return *result = (bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue;13301331return *result = OK, value;1332}13331334} // namespace133513361337// Called at startup and after every change to1338// "SyzygyPath" UCI option to (re)create the various tables. It is not thread1339// safe, nor it needs to be.1340void Tablebases::init(const std::string& paths) {13411342TBTables.clear();1343MaxCardinality = 0;1344TBFile::Paths = paths;13451346if (paths.empty())1347return;13481349// MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..271350int code = 0;1351for (Square s = SQ_A1; s <= SQ_H8; ++s)1352if (off_A1H8(s) < 0)1353MapB1H1H7[s] = code++;13541355// MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..91356std::vector<Square> diagonal;1357code = 0;1358for (Square s = SQ_A1; s <= SQ_D4; ++s)1359if (off_A1H8(s) < 0 && file_of(s) <= FILE_D)1360MapA1D1D4[s] = code++;13611362else if (!off_A1H8(s) && file_of(s) <= FILE_D)1363diagonal.push_back(s);13641365// Diagonal squares are encoded as last ones1366for (auto s : diagonal)1367MapA1D1D4[s] = code++;13681369// MapKK[] encodes all the 462 possible legal positions of two kings where1370// the first is in the a1-d1-d4 triangle. If the first king is on the a1-d41371// diagonal, the other one shall not be above the a1-h8 diagonal.1372std::vector<std::pair<int, Square>> bothOnDiagonal;1373code = 0;1374for (int idx = 0; idx < 10; idx++)1375for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1)1376if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 01377{1378for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)1379if ((PseudoAttacks[KING][s1] | s1) & s2)1380continue; // Illegal position13811382else if (!off_A1H8(s1) && off_A1H8(s2) > 0)1383continue; // First on diagonal, second above13841385else if (!off_A1H8(s1) && !off_A1H8(s2))1386bothOnDiagonal.emplace_back(idx, s2);13871388else1389MapKK[idx][s2] = code++;1390}13911392// Legal positions with both kings on a diagonal are encoded as last ones1393for (auto p : bothOnDiagonal)1394MapKK[p.first][p.second] = code++;13951396// Binomial[] stores the Binomial Coefficients using Pascal rule. There1397// are Binomial[k][n] ways to choose k elements from a set of n elements.1398Binomial[0][0] = 1;13991400for (int n = 1; n < 64; n++) // Squares1401for (int k = 0; k < 6 && k <= n; ++k) // Pieces1402Binomial[k][n] =1403(k > 0 ? Binomial[k - 1][n - 1] : 0) + (k < n ? Binomial[k][n - 1] : 0);14041405// MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible1406// available squares when the leading one is in 's'. Moreover the pawn with1407// highest MapPawns[] is the leading pawn, the one nearest the edge, and1408// among pawns with the same file, the one with the lowest rank.1409int availableSquares = 47; // Available squares when lead pawn is in a214101411// Init the tables for the encoding of leading pawns group: with 7-men TB we1412// can have up to 5 leading pawns (KPPPPPK).1413for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)1414for (File f = FILE_A; f <= FILE_D; ++f)1415{1416// Restart the index at every file because TB table is split1417// by file, so we can reuse the same index for different files.1418int idx = 0;14191420// Sum all possible combinations for a given file, starting with1421// the leading pawn on rank 2 and increasing the rank.1422for (Rank r = RANK_2; r <= RANK_7; ++r)1423{1424Square sq = make_square(f, r);14251426// Compute MapPawns[] at first pass.1427// If sq is the leading pawn square, any other pawn cannot be1428// below or more toward the edge of sq. There are 47 available1429// squares when sq = a2 and reduced by 2 for any rank increase1430// due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 451431if (leadPawnsCnt == 1)1432{1433MapPawns[sq] = availableSquares--;1434MapPawns[flip_file(sq)] = availableSquares--;1435}1436LeadPawnIdx[leadPawnsCnt][sq] = idx;1437idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];1438}1439// After a file is traversed, store the cumulated per-file index1440LeadPawnsSize[leadPawnsCnt][f] = idx;1441}14421443// Add entries in TB tables if the corresponding ".rtbw" file exists1444for (PieceType p1 = PAWN; p1 < KING; ++p1)1445{1446TBTables.add({KING, p1, KING});14471448for (PieceType p2 = PAWN; p2 <= p1; ++p2)1449{1450TBTables.add({KING, p1, p2, KING});1451TBTables.add({KING, p1, KING, p2});14521453for (PieceType p3 = PAWN; p3 < KING; ++p3)1454TBTables.add({KING, p1, p2, KING, p3});14551456for (PieceType p3 = PAWN; p3 <= p2; ++p3)1457{1458TBTables.add({KING, p1, p2, p3, KING});14591460for (PieceType p4 = PAWN; p4 <= p3; ++p4)1461{1462TBTables.add({KING, p1, p2, p3, p4, KING});14631464for (PieceType p5 = PAWN; p5 <= p4; ++p5)1465TBTables.add({KING, p1, p2, p3, p4, p5, KING});14661467for (PieceType p5 = PAWN; p5 < KING; ++p5)1468TBTables.add({KING, p1, p2, p3, p4, KING, p5});1469}14701471for (PieceType p4 = PAWN; p4 < KING; ++p4)1472{1473TBTables.add({KING, p1, p2, p3, KING, p4});14741475for (PieceType p5 = PAWN; p5 <= p4; ++p5)1476TBTables.add({KING, p1, p2, p3, KING, p4, p5});1477}1478}14791480for (PieceType p3 = PAWN; p3 <= p1; ++p3)1481for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4)1482TBTables.add({KING, p1, p2, KING, p3, p4});1483}1484}14851486TBTables.info();1487}14881489// Probe the WDL table for a particular position.1490// If *result != FAIL, the probe was successful.1491// The return value is from the point of view of the side to move:1492// -2 : loss1493// -1 : loss, but draw under 50-move rule1494// 0 : draw1495// 1 : win, but draw under 50-move rule1496// 2 : win1497WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) {14981499*result = OK;1500return search<false>(pos, result);1501}15021503// Probe the DTZ table for a particular position.1504// If *result != FAIL, the probe was successful.1505// The return value is from the point of view of the side to move:1506// n < -100 : loss, but draw under 50-move rule1507// -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0)1508// -1 : loss, the side to move is mated1509// 0 : draw1510// 1 < n <= 100 : win in n ply (assuming 50-move counter == 0)1511// 100 < n : win, but draw under 50-move rule1512//1513// The return value n can be off by 1: a return value -n can mean a loss1514// in n+1 ply and a return value +n can mean a win in n+1 ply. This1515// cannot happen for tables with positions exactly on the "edge" of1516// the 50-move rule.1517//1518// This implies that if dtz > 0 is returned, the position is certainly1519// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine1520// picks moves that preserve dtz + 50-move-counter <= 99.1521//1522// If n = 100 immediately after a capture or pawn move, then the position1523// is also certainly a win, and during the whole phase until the next1524// capture or pawn move, the inequality to be preserved is1525// dtz + 50-move-counter <= 100.1526//1527// In short, if a move is available resulting in dtz + 50-move-counter <= 99,1528// then do not accept moves leading to dtz + 50-move-counter == 100.1529int Tablebases::probe_dtz(Position& pos, ProbeState* result) {15301531*result = OK;1532WDLScore wdl = search<true>(pos, result);15331534if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws1535return 0;15361537// DTZ stores a 'don't care value in this case, or even a plain wrong1538// one as in case the best move is a losing ep, so it cannot be probed.1539if (*result == ZEROING_BEST_MOVE)1540return dtz_before_zeroing(wdl);15411542int dtz = probe_table<DTZ>(pos, result, wdl);15431544if (*result == FAIL)1545return 0;15461547if (*result != CHANGE_STM)1548return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl);15491550// DTZ stores results for the other side, so we need to do a 1-ply search and1551// find the winning move that minimizes DTZ.1552StateInfo st;1553int minDTZ = 0xFFFF;15541555for (const Move move : MoveList<LEGAL>(pos))1556{1557bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;15581559pos.do_move(move, st);15601561// For zeroing moves we want the dtz of the move _before_ doing it,1562// otherwise we will get the dtz of the next move sequence. Search the1563// position after the move to get the score sign (because even in a1564// winning position we could make a losing capture or go for a draw).1565dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result)) : -probe_dtz(pos, result);15661567// If the move mates, force minDTZ to 11568if (dtz == 1 && pos.checkers() && MoveList<LEGAL>(pos).size() == 0)1569minDTZ = 1;15701571// Convert result from 1-ply search. Zeroing moves are already accounted1572// by dtz_before_zeroing() that returns the DTZ of the previous move.1573if (!zeroing)1574dtz += sign_of(dtz);15751576// Skip the draws and if we are winning only pick positive dtz1577if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl))1578minDTZ = dtz;15791580pos.undo_move(move);15811582if (*result == FAIL)1583return 0;1584}15851586// When there are no legal moves, the position is mate: we return -11587return minDTZ == 0xFFFF ? -1 : minDTZ;1588}158915901591// Use the DTZ tables to rank root moves.1592//1593// A return value false indicates that not all probes were successful.1594bool Tablebases::root_probe(Position& pos,1595Search::RootMoves& rootMoves,1596bool rule50,1597bool rankDTZ) {15981599ProbeState result = OK;1600StateInfo st;16011602// Obtain 50-move counter for the root position1603int cnt50 = pos.rule50_count();16041605// Check whether a position was repeated since the last zeroing move.1606bool rep = pos.has_repeated();16071608int dtz, bound = rule50 ? (MAX_DTZ / 2 - 100) : 1;16091610// Probe and rank each move1611for (auto& m : rootMoves)1612{1613pos.do_move(m.pv[0], st);16141615// Calculate dtz for the current move counting from the root position1616if (pos.rule50_count() == 0)1617{1618// In case of a zeroing move, dtz is one of -101/-1/0/1/1011619WDLScore wdl = -probe_wdl(pos, &result);1620dtz = dtz_before_zeroing(wdl);1621}1622else if ((rule50 && pos.is_draw(1)) || pos.is_repetition(1))1623{1624// In case a root move leads to a draw by repetition or 50-move rule,1625// we set dtz to zero. Note: since we are only 1 ply from the root,1626// this must be a true 3-fold repetition inside the game history.1627dtz = 0;1628}1629else1630{1631// Otherwise, take dtz for the new position and correct by 1 ply1632dtz = -probe_dtz(pos, &result);1633dtz = dtz > 0 ? dtz + 1 : dtz < 0 ? dtz - 1 : dtz;1634}16351636// Make sure that a mating move is assigned a dtz value of 11637if (pos.checkers() && dtz == 2 && MoveList<LEGAL>(pos).size() == 0)1638dtz = 1;16391640pos.undo_move(m.pv[0]);16411642if (result == FAIL)1643return false;16441645// Better moves are ranked higher. Certain wins are ranked equally.1646// Losing moves are ranked equally unless a 50-move draw is in sight.1647int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ - (rankDTZ ? dtz : 0)1648: MAX_DTZ / 2 - (dtz + cnt50))1649: dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ - (rankDTZ ? dtz : 0)1650: -MAX_DTZ / 2 + (-dtz + cnt50))1651: 0;1652m.tbRank = r;16531654// Determine the score to be displayed for this move. Assign at least1655// 1 cp to cursed wins and let it grow to 49 cp as the positions gets1656// closer to a real win.1657m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 11658: r > 0 ? Value((std::max(3, r - (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200)1659: r == 0 ? VALUE_DRAW1660: r > -bound1661? Value((std::min(-3, r + (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200)1662: -VALUE_MATE + MAX_PLY + 1;1663}16641665return true;1666}166716681669// Use the WDL tables to rank root moves.1670// This is a fallback for the case that some or all DTZ tables are missing.1671//1672// A return value false indicates that not all probes were successful.1673bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50) {16741675static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ};16761677ProbeState result = OK;1678StateInfo st;1679WDLScore wdl;168016811682// Probe and rank each move1683for (auto& m : rootMoves)1684{1685pos.do_move(m.pv[0], st);16861687if (pos.is_draw(1))1688wdl = WDLDraw;1689else1690wdl = -probe_wdl(pos, &result);16911692pos.undo_move(m.pv[0]);16931694if (result == FAIL)1695return false;16961697m.tbRank = WDL_to_rank[wdl + 2];16981699if (!rule50)1700wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw;1701m.tbScore = WDL_to_value[wdl + 2];1702}17031704return true;1705}17061707Config Tablebases::rank_root_moves(const OptionsMap& options,1708Position& pos,1709Search::RootMoves& rootMoves,1710bool rankDTZ) {1711Config config;17121713if (rootMoves.empty())1714return config;17151716config.rootInTB = false;1717config.useRule50 = bool(options["Syzygy50MoveRule"]);1718config.probeDepth = int(options["SyzygyProbeDepth"]);1719config.cardinality = int(options["SyzygyProbeLimit"]);17201721bool dtz_available = true;17221723// Tables with fewer pieces than SyzygyProbeLimit are searched with1724// probeDepth == DEPTH_ZERO1725if (config.cardinality > MaxCardinality)1726{1727config.cardinality = MaxCardinality;1728config.probeDepth = 0;1729}17301731if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))1732{1733// Rank moves using DTZ tables1734config.rootInTB = root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ);17351736if (!config.rootInTB)1737{1738// DTZ tables are missing; try to rank moves using WDL tables1739dtz_available = false;1740config.rootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]);1741}1742}17431744if (config.rootInTB)1745{1746// Sort moves according to TB rank1747std::stable_sort(1748rootMoves.begin(), rootMoves.end(),1749[](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; });17501751// Probe during search only if DTZ is not available and we are winning1752if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW)1753config.cardinality = 0;1754}1755else1756{1757// Clean up if root_probe() and root_probe_wdl() have failed1758for (auto& m : rootMoves)1759m.tbRank = 0;1760}17611762return config;1763}1764} // namespace Stockfish176517661767