/*1Stockfish, a UCI chess playing engine derived from Glaurung 2.12Copyright (C) 2004-2026 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>37#include <array>3839#include "../bitboard.h"40#include "../misc.h"41#include "../movegen.h"42#include "../position.h"43#include "../search.h"44#include "../types.h"45#include "../ucioption.h"4647#ifndef _WIN3248#include <fcntl.h>49#include <sys/mman.h>50#include <unistd.h>51#else52#define WIN32_LEAN_AND_MEAN53#ifndef NOMINMAX54#define NOMINMAX // Disable macros min() and max()55#endif56#include <windows.h>57#endif5859using namespace Stockfish::Tablebases;6061int Stockfish::Tablebases::MaxCardinality;6263namespace Stockfish {6465namespace {6667constexpr int TBPIECES = 7; // Max number of supported pieces68constexpr int MAX_DTZ =691 << 18; // Max DTZ supported times 2, large enough to deal with the syzygy TB limit.7071enum {72BigEndian,73LittleEndian74};75enum TBType {76WDL,77DTZ78}; // Used as template parameter7980// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables81enum TBFlag {82STM = 1,83Mapped = 2,84WinPlies = 4,85LossPlies = 8,86Wide = 16,87SingleValue = 12888};8990inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }91inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }9293constexpr std::string_view PieceToChar = " PNBRQK pnbrqk";9495int MapPawns[SQUARE_NB];96int MapB1H1H7[SQUARE_NB];97int MapA1D1D4[SQUARE_NB];98int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB]99100int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements101int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB]102int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D]103104// Comparison function to sort leading pawns in ascending MapPawns[] order105bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; }106int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); }107108constexpr Value WDL_to_value[] = {-VALUE_MATE + MAX_PLY + 1, VALUE_DRAW - 2, VALUE_DRAW,109VALUE_DRAW + 2, VALUE_MATE - MAX_PLY - 1};110111template<typename T, int Half = sizeof(T) / 2, int End = sizeof(T) - 1>112inline void swap_endian(T& x) {113static_assert(std::is_unsigned_v<T>, "Argument of swap_endian not unsigned");114115uint8_t tmp, *c = (uint8_t*) &x;116for (int i = 0; i < Half; ++i)117tmp = c[i], c[i] = c[End - i], c[End - i] = tmp;118}119template<>120inline void swap_endian<uint8_t>(uint8_t&) {}121122template<typename T, int LE>123T number(void* addr) {124T v;125126if (uintptr_t(addr) & (alignof(T) - 1)) // Unaligned pointer (very rare)127std::memcpy(&v, addr, sizeof(T));128else129v = *((T*) addr);130131if (LE != IsLittleEndian)132swap_endian(v);133return v;134}135136// DTZ tables don't store valid scores for moves that reset the rule50 counter137// like captures and pawn moves but we can easily recover the correct dtz of the138// previous move if we know the position's WDL score.139int dtz_before_zeroing(WDLScore wdl) {140return wdl == WDLWin ? 1141: wdl == WDLCursedWin ? 101142: wdl == WDLBlessedLoss ? -101143: wdl == WDLLoss ? -1144: 0;145}146147// Return the sign of a number (-1, 0, 1)148template<typename T>149int sign_of(T val) {150return (T(0) < val) - (val < T(0));151}152153// Numbers in little-endian used by sparseIndex[] to point into blockLength[]154struct SparseEntry {155char block[4]; // Number of block156char offset[2]; // Offset within the block157};158159static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes");160161using Sym = uint16_t; // Huffman symbol162163struct LR {164enum Side {165Left,166Right167};168169uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12170// bits is the right-hand symbol. If the symbol has length 1,171// then the left-hand symbol is the stored value.172template<Side S>173Sym get() {174return S == Left ? ((lr[1] & 0xF) << 8) | lr[0]175: S == Right ? (lr[2] << 4) | (lr[1] >> 4)176: (assert(false), Sym(-1));177}178};179180static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes");181182// Tablebases data layout is structured as following:183//184// TBFile: memory maps/unmaps the physical .rtbw and .rtbz files185// TBTable: one object for each file with corresponding indexing information186// TBTables: has ownership of TBTable objects, keeping a list and a hash187188// class TBFile memory maps/unmaps the single .rtbw and .rtbz files. Files are189// memory mapped for best performance. Files are mapped at first access: at init190// time only existence of the file is checked.191class TBFile: public std::ifstream {192193std::string fname;194195public:196// Look for and open the file among the Paths directories where the .rtbw197// and .rtbz files can be found. Multiple directories are separated by ";"198// on Windows and by ":" on Unix-based operating systems.199//200// Example:201// C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6202static std::string Paths;203204TBFile(const std::string& f) {205206#ifndef _WIN32207constexpr char SepChar = ':';208#else209constexpr char SepChar = ';';210#endif211std::stringstream ss(Paths);212std::string path;213214while (std::getline(ss, path, SepChar))215{216fname = path + "/" + f;217std::ifstream::open(fname);218if (is_open())219return;220}221}222223// Memory map the file and check it.224uint8_t* map(void** baseAddress, uint64_t* mapping, TBType type) {225if (is_open())226close(); // Need to re-open to get native file descriptor227228#ifndef _WIN32229struct stat statbuf;230int fd = ::open(fname.c_str(), O_RDONLY);231232if (fd == -1)233return *baseAddress = nullptr, nullptr;234235fstat(fd, &statbuf);236237if (statbuf.st_size % 64 != 16)238{239std::cerr << "Corrupt tablebase file " << fname << std::endl;240exit(EXIT_FAILURE);241}242243*mapping = statbuf.st_size;244*baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0);245#if defined(MADV_RANDOM)246madvise(*baseAddress, statbuf.st_size, MADV_RANDOM);247#endif248::close(fd);249250if (*baseAddress == MAP_FAILED)251{252std::cerr << "Could not mmap() " << fname << std::endl;253exit(EXIT_FAILURE);254}255#else256// Note FILE_FLAG_RANDOM_ACCESS is only a hint to Windows and as such may get ignored.257HANDLE fd = CreateFileA(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr,258OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, nullptr);259260if (fd == INVALID_HANDLE_VALUE)261return *baseAddress = nullptr, nullptr;262263DWORD size_high;264DWORD size_low = GetFileSize(fd, &size_high);265266if (size_low % 64 != 16)267{268std::cerr << "Corrupt tablebase file " << fname << std::endl;269exit(EXIT_FAILURE);270}271272HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr);273CloseHandle(fd);274275if (!mmap)276{277std::cerr << "CreateFileMapping() failed" << std::endl;278exit(EXIT_FAILURE);279}280281*mapping = uint64_t(mmap);282*baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0);283284if (!*baseAddress)285{286std::cerr << "MapViewOfFile() failed, name = " << fname287<< ", error = " << GetLastError() << std::endl;288exit(EXIT_FAILURE);289}290#endif291uint8_t* data = (uint8_t*) *baseAddress;292293constexpr uint8_t Magics[][4] = {{0xD7, 0x66, 0x0C, 0xA5}, {0x71, 0xE8, 0x23, 0x5D}};294295if (memcmp(data, Magics[type == WDL], 4))296{297std::cerr << "Corrupted table in file " << fname << std::endl;298unmap(*baseAddress, *mapping);299return *baseAddress = nullptr, nullptr;300}301302return data + 4; // Skip Magics's header303}304305static void unmap(void* baseAddress, uint64_t mapping) {306307#ifndef _WIN32308munmap(baseAddress, mapping);309#else310UnmapViewOfFile(baseAddress);311CloseHandle((HANDLE) mapping);312#endif313}314};315316std::string TBFile::Paths;317318// struct PairsData contains low-level indexing information to access TB data.319// There are 8, 4, or 2 PairsData records for each TBTable, according to the type320// of table and if positions have pawns or not. It is populated at first access.321struct PairsData {322uint8_t flags; // Table flags, see enum TBFlag323uint8_t maxSymLen; // Maximum length in bits of the Huffman symbols324uint8_t minSymLen; // Minimum length in bits of the Huffman symbols325uint32_t blocksNum; // Number of blocks in the TB file326size_t sizeofBlock; // Block size in bytes327size_t span; // About every span values there is a SparseIndex[] entry328Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value329LR* btree; // btree[sym] stores the left and right symbols that expand sym330uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536331uint32_t blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum332SparseEntry* sparseIndex; // Partial indices into blockLength[]333size_t sparseIndexSize; // Size of SparseIndex[] table334uint8_t* data; // Start of Huffman compressed data335std::vector<uint64_t>336base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l337std::vector<uint8_t>338symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256339Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups340uint64_t groupIdx[TBPIECES + 1]; // Start index used for the encoding of the group's pieces341int groupLen[TBPIECES + 1]; // Number of pieces in a given group: KRKN -> (3, 1)342uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss (used in DTZ)343};344345// struct TBTable contains indexing information to access the corresponding TBFile.346// There are 2 types of TBTable, corresponding to a WDL or a DTZ file. TBTable347// is populated at init time but the nested PairsData records are populated at348// first access, when the corresponding file is memory mapped.349template<TBType Type>350struct TBTable {351using Ret = std::conditional_t<Type == WDL, WDLScore, int>;352353static constexpr int Sides = Type == WDL ? 2 : 1;354355std::atomic_bool ready;356void* baseAddress;357uint8_t* map;358uint64_t mapping;359Key key;360Key key2;361int pieceCount;362bool hasPawns;363bool hasUniquePieces;364uint8_t pawnCount[2]; // [Lead color / other color]365PairsData items[Sides][4]; // [wtm / btm][FILE_A..FILE_D or 0]366367PairsData* get(int stm, int f) { return &items[stm % Sides][hasPawns ? f : 0]; }368369TBTable() :370ready(false),371baseAddress(nullptr) {}372explicit TBTable(const std::string& code);373explicit TBTable(const TBTable<WDL>& wdl);374375~TBTable() {376if (baseAddress)377TBFile::unmap(baseAddress, mapping);378}379};380381template<>382TBTable<WDL>::TBTable(const std::string& code) :383TBTable() {384385StateInfo st;386Position pos;387388key = pos.set(code, WHITE, &st).material_key();389pieceCount = pos.count<ALL_PIECES>();390hasPawns = pos.pieces(PAWN);391392hasUniquePieces = false;393for (Color c : {WHITE, BLACK})394for (PieceType pt = PAWN; pt < KING; ++pt)395if (popcount(pos.pieces(c, pt)) == 1)396hasUniquePieces = true;397398// Set the leading color. In case both sides have pawns the leading color399// is the side with fewer pawns because this leads to better compression.400bool c = !pos.count<PAWN>(BLACK)401|| (pos.count<PAWN>(WHITE) && pos.count<PAWN>(BLACK) >= pos.count<PAWN>(WHITE));402403pawnCount[0] = pos.count<PAWN>(c ? WHITE : BLACK);404pawnCount[1] = pos.count<PAWN>(c ? BLACK : WHITE);405406key2 = pos.set(code, BLACK, &st).material_key();407}408409template<>410TBTable<DTZ>::TBTable(const TBTable<WDL>& wdl) :411TBTable() {412413// Use the corresponding WDL table to avoid recalculating all from scratch414key = wdl.key;415key2 = wdl.key2;416pieceCount = wdl.pieceCount;417hasPawns = wdl.hasPawns;418hasUniquePieces = wdl.hasUniquePieces;419pawnCount[0] = wdl.pawnCount[0];420pawnCount[1] = wdl.pawnCount[1];421}422423// class TBTables creates and keeps ownership of the TBTable objects, one for424// each TB file found. It supports a fast, hash-based, table lookup. Populated425// at init time, accessed at probe time.426class TBTables {427428struct Entry {429Key key;430TBTable<WDL>* wdl;431TBTable<DTZ>* dtz;432433template<TBType Type>434TBTable<Type>* get() const {435return (TBTable<Type>*) (Type == WDL ? (void*) wdl : (void*) dtz);436}437};438439static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb440static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket441442Entry hashTable[Size + Overflow];443444std::deque<TBTable<WDL>> wdlTable;445std::deque<TBTable<DTZ>> dtzTable;446size_t foundDTZFiles = 0;447size_t foundWDLFiles = 0;448449void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {450uint32_t homeBucket = uint32_t(key) & (Size - 1);451Entry entry{key, wdl, dtz};452453// Ensure last element is empty to avoid overflow when looking up454for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket)455{456Key otherKey = hashTable[bucket].key;457if (otherKey == key || !hashTable[bucket].get<WDL>())458{459hashTable[bucket] = entry;460return;461}462463// Robin Hood hashing: If we've probed for longer than this element,464// insert here and search for a new spot for the other element instead.465uint32_t otherHomeBucket = uint32_t(otherKey) & (Size - 1);466if (otherHomeBucket > homeBucket)467{468std::swap(entry, hashTable[bucket]);469key = otherKey;470homeBucket = otherHomeBucket;471}472}473std::cerr << "TB hash table size too low!" << std::endl;474exit(EXIT_FAILURE);475}476477public:478template<TBType Type>479TBTable<Type>* get(Key key) {480for (const Entry* entry = &hashTable[uint32_t(key) & (Size - 1)];; ++entry)481{482if (entry->key == key || !entry->get<Type>())483return entry->get<Type>();484}485}486487void clear() {488memset(hashTable, 0, sizeof(hashTable));489wdlTable.clear();490dtzTable.clear();491foundDTZFiles = 0;492foundWDLFiles = 0;493}494495void info() const {496sync_cout << "info string Found " << foundWDLFiles << " WDL and " << foundDTZFiles497<< " DTZ tablebase files (up to " << MaxCardinality << "-man)." << sync_endl;498}499500void add(const std::vector<PieceType>& pieces);501};502503TBTables TBTables;504505// If the corresponding file exists two new objects TBTable<WDL> and TBTable<DTZ>506// are created and added to the lists and hash table. Called at init time.507void TBTables::add(const std::vector<PieceType>& pieces) {508509std::string code;510511for (PieceType pt : pieces)512code += PieceToChar[pt];513code.insert(code.find('K', 1), "v");514515TBFile file_dtz(code + ".rtbz"); // KRK -> KRvK516if (file_dtz.is_open())517{518file_dtz.close();519foundDTZFiles++;520}521522TBFile file(code + ".rtbw"); // KRK -> KRvK523524if (!file.is_open()) // Only WDL file is checked525return;526527file.close();528foundWDLFiles++;529530MaxCardinality = std::max(int(pieces.size()), MaxCardinality);531532wdlTable.emplace_back(code);533dtzTable.emplace_back(wdlTable.back());534535// Insert into the hash keys for both colors: KRvK with KR white and black536insert(wdlTable.back().key, &wdlTable.back(), &dtzTable.back());537insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back());538}539540// TB tables are compressed with canonical Huffman code. The compressed data is divided into541// blocks of size d->sizeofBlock, and each block stores a variable number of symbols.542// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols543// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536544// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after545// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most546// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly547// of draws or mostly of wins, but such tables are actually quite common. In principle, the548// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for549// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so550// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long.551// The generator picks the size that leads to the smallest table. The "book" of symbols and552// Huffman codes are the same for all blocks in the table. A non-symmetric pawnless TB file553// will have one table for wtm and one for btm, a TB file with pawns will have tables per554// file a,b,c,d also, in this case, one set for wtm and one for btm.555int decompress_pairs(PairsData* d, uint64_t idx) {556557// Special case where all table positions store the same value558if (d->flags & TBFlag::SingleValue)559return d->minSymLen;560561// First we need to locate the right block that stores the value at index "idx".562// Because each block n stores blockLength[n] + 1 values, the index i of the block563// that contains the value at position idx is:564//565// for (i = -1, sum = 0; sum <= idx; i++)566// sum += blockLength[i + 1] + 1;567//568// This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that569// point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry570// that stores the blockLength[] index and the offset within that block of the value571// with index I(k), where:572//573// I(k) = k * d->span + d->span / 2 (1)574575// First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)576uint32_t k = uint32_t(idx / d->span);577578// Then we read the corresponding SparseIndex[] entry579uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);580int offset = number<uint16_t, LittleEndian>(&d->sparseIndex[k].offset);581582// Now compute the difference idx - I(k). From the definition of k, we know that583//584// idx = k * d->span + idx % d->span (2)585//586// So from (1) and (2) we can compute idx - I(K):587int diff = int(idx % d->span - d->span / 2);588589// Sum the above to offset to find the offset corresponding to our idx590offset += diff;591592// Move to the previous/next block, until we reach the correct block that contains idx,593// that is when 0 <= offset <= d->blockLength[block]594while (offset < 0)595offset += d->blockLength[--block] + 1;596597while (offset > d->blockLength[block])598offset -= d->blockLength[block++] + 1;599600// Finally, we find the start address of our block of canonical Huffman symbols601uint32_t* ptr = (uint32_t*) (d->data + (uint64_t(block) * d->sizeofBlock));602603// Read the first 64 bits in our block, this is a (truncated) sequence of604// unknown number of symbols of unknown length but we know the first one605// is at the beginning of this 64-bit sequence.606uint64_t buf64 = number<uint64_t, BigEndian>(ptr);607ptr += 2;608int buf64Size = 64;609Sym sym;610611while (true)612{613int len = 0; // This is the symbol length - d->min_sym_len614615// Now get the symbol length. For any symbol s64 of length l right-padded616// to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we617// can find the symbol length iterating through base64[].618while (buf64 < d->base64[len])619++len;620621// All the symbols of a given length are consecutive integers (numerical622// sequence property), so we can compute the offset of our symbol of623// length len, stored at the beginning of buf64.624sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen));625626// Now add the value of the lowest symbol of length len to get our symbol627sym += number<Sym, LittleEndian>(&d->lowestSym[len]);628629// If our offset is within the number of values represented by symbol sym,630// we are done.631if (offset < d->symlen[sym] + 1)632break;633634// ...otherwise update the offset and continue to iterate635offset -= d->symlen[sym] + 1;636len += d->minSymLen; // Get the real length637buf64 <<= len; // Consume the just processed symbol638buf64Size -= len;639640if (buf64Size <= 32)641{ // Refill the buffer642buf64Size += 32;643buf64 |= uint64_t(number<uint32_t, BigEndian>(ptr++)) << (64 - buf64Size);644}645}646647// Now we have our symbol that expands into d->symlen[sym] + 1 symbols.648// We binary-search for our value recursively expanding into the left and649// right child symbols until we reach a leaf node where symlen[sym] + 1 == 1650// that will store the value we need.651while (d->symlen[sym])652{653Sym left = d->btree[sym].get<LR::Left>();654655// If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and656// expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then657// we know that, for instance, the tenth value (offset = 10) will be on658// the left side because in Recursive Pairing child symbols are adjacent.659if (offset < d->symlen[left] + 1)660sym = left;661else662{663offset -= d->symlen[left] + 1;664sym = d->btree[sym].get<LR::Right>();665}666}667668return d->btree[sym].get<LR::Left>();669}670671bool check_dtz_stm(TBTable<WDL>*, int, File) { return true; }672673bool check_dtz_stm(TBTable<DTZ>* entry, int stm, File f) {674675auto flags = entry->get(stm, f)->flags;676return (flags & TBFlag::STM) == stm || ((entry->key == entry->key2) && !entry->hasPawns);677}678679// DTZ scores are sorted by frequency of occurrence and then assigned the680// values 0, 1, 2, ... in order of decreasing frequency. This is done for each681// of the four WDLScore values. The mapping information necessary to reconstruct682// the original values are stored in the TB file and read during map[] init.683WDLScore map_score(TBTable<WDL>*, File, int value, WDLScore) { return WDLScore(value - 2); }684685int map_score(TBTable<DTZ>* entry, File f, int value, WDLScore wdl) {686687constexpr int WDLMap[] = {1, 3, 0, 2, 0};688689auto flags = entry->get(0, f)->flags;690691uint8_t* map = entry->map;692uint16_t* idx = entry->get(0, f)->map_idx;693if (flags & TBFlag::Mapped)694{695if (flags & TBFlag::Wide)696value = ((uint16_t*) map)[idx[WDLMap[wdl + 2]] + value];697else698value = map[idx[WDLMap[wdl + 2]] + value];699}700701// DTZ tables store distance to zero in number of moves or plies. We702// want to return plies, so we have to convert to plies when needed.703if ((wdl == WDLWin && !(flags & TBFlag::WinPlies))704|| (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) || wdl == WDLCursedWin705|| wdl == WDLBlessedLoss)706value *= 2;707708return value + 1;709}710711// A temporary fix for the compiler bug with vectorization. (#4450)712#if defined(__clang__) && defined(__clang_major__) && __clang_major__ >= 15713#define DISABLE_CLANG_LOOP_VEC _Pragma("clang loop vectorize(disable)")714#else715#define DISABLE_CLANG_LOOP_VEC716#endif717718// Compute a unique index out of a position and use it to probe the TB file. To719// encode k pieces of the same type and color, first sort the pieces by square in720// ascending order s1 <= s2 <= ... <= sk then compute the unique index as:721//722// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk]723//724template<typename T, typename Ret = typename T::Ret>725Ret do_probe_table(const Position& pos, T* entry, WDLScore wdl, ProbeState* result) {726727Square squares[TBPIECES];728Piece pieces[TBPIECES];729uint64_t idx;730int next = 0, size = 0, leadPawnsCnt = 0;731PairsData* d;732Bitboard b, leadPawns = 0;733File tbFile = FILE_A;734735// A given TB entry like KRK has associated two material keys: KRvk and Kvkr.736// If both sides have the same pieces keys are equal. In this case TB tables737// only stores the 'white to move' case, so if the position to lookup has black738// to move, we need to switch the color and flip the squares before to lookup.739bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move());740741// TB files are calculated for white as the stronger side. For instance, we742// have KRvK, not KvKR. A position where the stronger side is white will have743// its material key == entry->key, otherwise we have to switch the color and744// flip the squares before to lookup.745bool blackStronger = (pos.material_key() != entry->key);746747int flipColor = (symmetricBlackToMove || blackStronger) * 8;748int flipSquares = (symmetricBlackToMove || blackStronger) * 56;749int stm = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move();750751// For pawns, TB files store 4 separate tables according if leading pawn is on752// file a, b, c or d after reordering. The leading pawn is the one with maximum753// MapPawns[] value, that is the one most toward the edges and with lowest rank.754if (entry->hasPawns)755{756757// In all the 4 tables, pawns are at the beginning of the piece sequence and758// their color is the reference one. So we just pick the first one.759Piece pc = Piece(entry->get(0, 0)->pieces[0] ^ flipColor);760761assert(type_of(pc) == PAWN);762763leadPawns = b = pos.pieces(color_of(pc), PAWN);764do765squares[size++] = pop_lsb(b) ^ flipSquares;766while (b);767768leadPawnsCnt = size;769770std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));771772tbFile = File(edge_distance(file_of(squares[0])));773}774775// DTZ tables are one-sided, i.e. they store positions only for white to776// move or only for black to move, so check for side to move to be stm,777// early exit otherwise.778if (!check_dtz_stm(entry, stm, tbFile))779return *result = CHANGE_STM, Ret();780781// Now we are ready to get all the position pieces (but the lead pawns) and782// directly map them to the correct color and square.783b = pos.pieces() ^ leadPawns;784do785{786Square s = pop_lsb(b);787squares[size] = s ^ flipSquares;788pieces[size++] = Piece(pos.piece_on(s) ^ flipColor);789} while (b);790791assert(size >= 2);792793d = entry->get(stm, tbFile);794795// Then we reorder the pieces to have the same sequence as the one stored796// in pieces[i]: the sequence that ensures the best compression.797for (int i = leadPawnsCnt; i < size - 1; ++i)798for (int j = i + 1; j < size; ++j)799if (d->pieces[i] == pieces[j])800{801std::swap(pieces[i], pieces[j]);802std::swap(squares[i], squares[j]);803break;804}805806// Now we map again the squares so that the square of the lead piece is in807// the triangle A1-D1-D4.808if (file_of(squares[0]) > FILE_D)809{810DISABLE_CLANG_LOOP_VEC811for (int i = 0; i < size; ++i)812squares[i] = flip_file(squares[i]);813}814815// Encode leading pawns starting with the one with minimum MapPawns[] and816// proceeding in ascending order.817if (entry->hasPawns)818{819idx = LeadPawnIdx[leadPawnsCnt][squares[0]];820821std::stable_sort(squares + 1, squares + leadPawnsCnt, pawns_comp);822823for (int i = 1; i < leadPawnsCnt; ++i)824idx += Binomial[i][MapPawns[squares[i]]];825826goto encode_remaining; // With pawns we have finished special treatments827}828829// In positions without pawns, we further flip the squares to ensure leading830// piece is below RANK_5.831if (rank_of(squares[0]) > RANK_4)832{833DISABLE_CLANG_LOOP_VEC834for (int i = 0; i < size; ++i)835squares[i] = flip_rank(squares[i]);836}837838// Look for the first piece of the leading group not on the A1-D4 diagonal839// and ensure it is mapped below the diagonal.840DISABLE_CLANG_LOOP_VEC841for (int i = 0; i < d->groupLen[0]; ++i)842{843if (!off_A1H8(squares[i]))844continue;845846if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1847{848DISABLE_CLANG_LOOP_VEC849for (int j = i; j < size; ++j)850squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);851}852break;853}854855// Encode the leading group.856//857// Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR858// and bK (each 0...63). The simplest way to map this position to an index859// is like this:860//861// index = wK * 64 * 64 + wR * 64 + bK;862//863// But this way the TB is going to have 64*64*64 = 262144 positions, with864// lots of positions being equivalent (because they are mirrors of each865// other) and lots of positions being invalid (two pieces on one square,866// adjacent kings, etc.).867// Usually the first step is to take the wK and bK together. There are just868// 462 ways legal and not-mirrored ways to place the wK and bK on the board.869// Once we have placed the wK and bK, there are 62 squares left for the wR870// Mapping its square from 0..63 to available squares 0..61 can be done like:871//872// wR -= (wR > wK) + (wR > bK);873//874// In words: if wR "comes later" than wK, we deduct 1, and the same if wR875// "comes later" than bK. In case of two same pieces like KRRvK we want to876// place the two Rs "together". If we have 62 squares left, we can place two877// Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be878// swapped and still get the same position.)879//880// In case we have at least 3 unique pieces (including kings) we encode them881// together.882if (entry->hasUniquePieces)883{884885int adjust1 = squares[1] > squares[0];886int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]);887888// First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3889// triangle to 0...5. There are 63 squares for second piece and 62890// (mapped to 0...61) for the third.891if (off_A1H8(squares[0]))892idx = (MapA1D1D4[squares[0]] * 63 + (squares[1] - adjust1)) * 62 + squares[2] - adjust2;893894// First piece is on a1-h8 diagonal, second below: map this occurrence to895// 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal896// to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27.897else if (off_A1H8(squares[1]))898idx = (6 * 63 + rank_of(squares[0]) * 28 + MapB1H1H7[squares[1]]) * 62 + squares[2]899- adjust2;900901// First two pieces are on a1-h8 diagonal, third below902else if (off_A1H8(squares[2]))903idx = 6 * 63 * 62 + 4 * 28 * 62 + rank_of(squares[0]) * 7 * 28904+ (rank_of(squares[1]) - adjust1) * 28 + MapB1H1H7[squares[2]];905906// All 3 pieces on the diagonal a1-h8907else908idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + rank_of(squares[0]) * 7 * 6909+ (rank_of(squares[1]) - adjust1) * 6 + (rank_of(squares[2]) - adjust2);910}911else912// We don't have at least 3 unique pieces, like in KRRvKBB, just map913// the kings.914idx = MapKK[MapA1D1D4[squares[0]]][squares[1]];915916encode_remaining:917idx *= d->groupIdx[0];918Square* groupSq = squares + d->groupLen[0];919920// Encode remaining pawns and then pieces according to square, in ascending order921bool remainingPawns = entry->hasPawns && entry->pawnCount[1];922923while (d->groupLen[++next])924{925std::stable_sort(groupSq, groupSq + d->groupLen[next]);926uint64_t n = 0;927928// Map down a square if "comes later" than a square in the previous929// groups (similar to what was done earlier for leading group pieces).930for (int i = 0; i < d->groupLen[next]; ++i)931{932auto f = [&](Square s) { return groupSq[i] > s; };933auto adjust = std::count_if(squares, groupSq, f);934n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns];935}936937remainingPawns = false;938idx += n * d->groupIdx[next];939groupSq += d->groupLen[next];940}941942// Now that we have the index, decompress the pair and get the score943return map_score(entry, tbFile, decompress_pairs(d, idx), wdl);944}945946// Group together pieces that will be encoded together. The general rule is that947// a group contains pieces of the same type and color. The exception is the leading948// group that, in case of positions without pawns, can be formed by 3 different949// pieces (default) or by the king pair when there is not a unique piece apart950// from the kings. When there are pawns, pawns are always first in pieces[].951//952// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K953//954// The actual grouping depends on the TB generator and can be inferred from the955// sequence of pieces in piece[] array.956template<typename T>957void set_groups(T& e, PairsData* d, int order[], File f) {958959int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2;960d->groupLen[n] = 1;961962// Number of pieces per group is stored in groupLen[], for instance in KRKN963// the encoder will default on '111', so groupLen[] will be (3, 1).964for (int i = 1; i < e.pieceCount; ++i)965if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1])966d->groupLen[n]++;967else968d->groupLen[++n] = 1;969970d->groupLen[++n] = 0; // Zero-terminated971972// The sequence in pieces[] defines the groups, but not the order in which973// they are encoded. If the pieces in a group g can be combined on the board974// in N(g) different ways, then the position encoding will be of the form:975//976// g1 * N(g2) * N(g3) + g2 * N(g3) + g3977//978// This ensures unique encoding for the whole position. The order of the979// groups is a per-table parameter and could not follow the canonical leading980// pawns/pieces -> remaining pawns -> remaining pieces. In particular the981// first group is at order[0] position and the remaining pawns, when present,982// are at order[1] position.983bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides984int next = pp ? 2 : 1;985int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0);986uint64_t idx = 1;987988for (int k = 0; next < n || k == order[0] || k == order[1]; ++k)989if (k == order[0]) // Leading pawns or pieces990{991d->groupIdx[0] = idx;992idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] : e.hasUniquePieces ? 31332 : 462;993}994else if (k == order[1]) // Remaining pawns995{996d->groupIdx[1] = idx;997idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]];998}999else // Remaining pieces1000{1001d->groupIdx[next] = idx;1002idx *= Binomial[d->groupLen[next]][freeSquares];1003freeSquares -= d->groupLen[next++];1004}10051006d->groupIdx[n] = idx;1007}10081009// In Recursive Pairing each symbol represents a pair of children symbols. So1010// read d->btree[] symbols data and expand each one in his left and right child1011// symbol until reaching the leaves that represent the symbol value.1012uint8_t set_symlen(PairsData* d, Sym s, std::vector<bool>& visited) {10131014visited[s] = true; // We can set it now because tree is acyclic1015Sym sr = d->btree[s].get<LR::Right>();10161017if (sr == 0xFFF)1018return 0;10191020Sym sl = d->btree[s].get<LR::Left>();10211022if (!visited[sl])1023d->symlen[sl] = set_symlen(d, sl, visited);10241025if (!visited[sr])1026d->symlen[sr] = set_symlen(d, sr, visited);10271028return d->symlen[sl] + d->symlen[sr] + 1;1029}10301031uint8_t* set_sizes(PairsData* d, uint8_t* data) {10321033d->flags = *data++;10341035if (d->flags & TBFlag::SingleValue)1036{1037d->blocksNum = d->blockLengthSize = 0;1038d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init1039d->minSymLen = *data++; // Here we store the single value1040return data;1041}10421043// groupLen[] is a zero-terminated list of group lengths, the last groupIdx[]1044// element stores the biggest index that is the tb size.1045uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen];10461047d->sizeofBlock = 1ULL << *data++;1048d->span = 1ULL << *data++;1049d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up1050auto padding = number<uint8_t, LittleEndian>(data++);1051d->blocksNum = number<uint32_t, LittleEndian>(data);1052data += sizeof(uint32_t);1053d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]1054// does not point out of range.1055d->maxSymLen = *data++;1056d->minSymLen = *data++;1057d->lowestSym = (Sym*) data;1058d->base64.resize(d->maxSymLen - d->minSymLen + 1);10591060// See https://en.wikipedia.org/wiki/Huffman_coding1061// The canonical code is ordered such that longer symbols (in terms of1062// the number of bits of their Huffman code) have a lower numeric value,1063// so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian).1064// Starting from this we compute a base64[] table indexed by symbol length1065// and containing 64 bit values so that d->base64[i] >= d->base64[i+1].10661067// Implementation note: we first cast the unsigned size_t "base64.size()"1068// to a signed int "base64_size" variable and then we are able to subtract 2,1069// avoiding unsigned overflow warnings.10701071int base64_size = static_cast<int>(d->base64.size());1072for (int i = base64_size - 2; i >= 0; --i)1073{1074d->base64[i] = (d->base64[i + 1] + number<Sym, LittleEndian>(&d->lowestSym[i])1075- number<Sym, LittleEndian>(&d->lowestSym[i + 1]))1076/ 2;10771078assert(d->base64[i] * 2 >= d->base64[i + 1]);1079}10801081// Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more1082// than d->base64[i+1] and given the above assert condition, we ensure that1083// d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i1084// and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i].1085for (int i = 0; i < base64_size; ++i)1086d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits10871088data += base64_size * sizeof(Sym);1089d->symlen.resize(number<uint16_t, LittleEndian>(data));1090data += sizeof(uint16_t);1091d->btree = (LR*) data;10921093// The compression scheme used is "Recursive Pairing", that replaces the most1094// frequent adjacent pair of symbols in the source message by a new symbol,1095// reevaluating the frequencies of all of the symbol pairs with respect to1096// the extended alphabet, and then repeating the process.1097// See https://web.archive.org/web/20201106232444/http://www.larsson.dogma.net/dcc99.pdf1098std::vector<bool> visited(d->symlen.size());10991100for (Sym sym = 0; sym < d->symlen.size(); ++sym)1101if (!visited[sym])1102d->symlen[sym] = set_symlen(d, sym, visited);11031104return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1);1105}11061107uint8_t* set_dtz_map(TBTable<WDL>&, uint8_t* data, File) { return data; }11081109uint8_t* set_dtz_map(TBTable<DTZ>& e, uint8_t* data, File maxFile) {11101111e.map = data;11121113for (File f = FILE_A; f <= maxFile; ++f)1114{1115auto flags = e.get(0, f)->flags;1116if (flags & TBFlag::Mapped)1117{1118if (flags & TBFlag::Wide)1119{1120data += uintptr_t(data) & 1; // Word alignment, we may have a mixed table1121for (int i = 0; i < 4; ++i)1122{ // Sequence like 3,x,x,x,1,x,0,2,x,x1123e.get(0, f)->map_idx[i] = uint16_t((uint16_t*) data - (uint16_t*) e.map + 1);1124data += 2 * number<uint16_t, LittleEndian>(data) + 2;1125}1126}1127else1128{1129for (int i = 0; i < 4; ++i)1130{1131e.get(0, f)->map_idx[i] = uint16_t(data - e.map + 1);1132data += *data + 1;1133}1134}1135}1136}11371138return data += uintptr_t(data) & 1; // Word alignment1139}11401141// Populate entry's PairsData records with data from the just memory-mapped file.1142// Called at first access.1143template<typename T>1144void set(T& e, uint8_t* data) {11451146PairsData* d;11471148enum {1149Split = 1,1150HasPawns = 21151};11521153assert(e.hasPawns == bool(*data & HasPawns));1154assert((e.key != e.key2) == bool(*data & Split));11551156data++; // First byte stores flags11571158const int sides = T::Sides == 2 && (e.key != e.key2) ? 2 : 1;1159const File maxFile = e.hasPawns ? FILE_D : FILE_A;11601161bool pp = e.hasPawns && e.pawnCount[1]; // Pawns on both sides11621163assert(!pp || e.pawnCount[0]);11641165for (File f = FILE_A; f <= maxFile; ++f)1166{11671168for (int i = 0; i < sides; i++)1169*e.get(i, f) = PairsData();11701171int order[][2] = {{*data & 0xF, pp ? *(data + 1) & 0xF : 0xF},1172{*data >> 4, pp ? *(data + 1) >> 4 : 0xF}};1173data += 1 + pp;11741175for (int k = 0; k < e.pieceCount; ++k, ++data)1176for (int i = 0; i < sides; i++)1177e.get(i, f)->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF);11781179for (int i = 0; i < sides; ++i)1180set_groups(e, e.get(i, f), order[i], f);1181}11821183data += uintptr_t(data) & 1; // Word alignment11841185for (File f = FILE_A; f <= maxFile; ++f)1186for (int i = 0; i < sides; i++)1187data = set_sizes(e.get(i, f), data);11881189data = set_dtz_map(e, data, maxFile);11901191for (File f = FILE_A; f <= maxFile; ++f)1192for (int i = 0; i < sides; i++)1193{1194(d = e.get(i, f))->sparseIndex = (SparseEntry*) data;1195data += d->sparseIndexSize * sizeof(SparseEntry);1196}11971198for (File f = FILE_A; f <= maxFile; ++f)1199for (int i = 0; i < sides; i++)1200{1201(d = e.get(i, f))->blockLength = (uint16_t*) data;1202data += d->blockLengthSize * sizeof(uint16_t);1203}12041205for (File f = FILE_A; f <= maxFile; ++f)1206for (int i = 0; i < sides; i++)1207{1208data = (uint8_t*) ((uintptr_t(data) + 0x3F) & ~0x3F); // 64 byte alignment1209(d = e.get(i, f))->data = data;1210data += d->blocksNum * d->sizeofBlock;1211}1212}12131214// If the TB file corresponding to the given position is already memory-mapped1215// then return its base address, otherwise, try to memory map and init it. Called1216// at every probe, memory map, and init only at first access. Function is thread1217// safe and can be called concurrently.1218template<TBType Type>1219void* mapped(TBTable<Type>& e, const Position& pos) {12201221static std::mutex mutex;1222// Because TB is the only usage of materialKey, check it here in debug mode1223assert(pos.material_key_is_ok());12241225// Use 'acquire' to avoid a thread reading 'ready' == true while1226// another is still working. (compiler reordering may cause this).1227if (e.ready.load(std::memory_order_acquire))1228return e.baseAddress; // Could be nullptr if file does not exist12291230std::scoped_lock<std::mutex> lk(mutex);12311232if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock1233return e.baseAddress;12341235// Pieces strings in decreasing order for each color, like ("KPP","KR")1236std::string fname, w, b;1237for (PieceType pt = KING; pt >= PAWN; --pt)1238{1239w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]);1240b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]);1241}12421243fname =1244(e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + (Type == WDL ? ".rtbw" : ".rtbz");12451246uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, Type);12471248if (data)1249set(e, data);12501251e.ready.store(true, std::memory_order_release);1252return e.baseAddress;1253}12541255template<TBType Type, typename Ret = typename TBTable<Type>::Ret>1256Ret probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) {12571258if (pos.count<ALL_PIECES>() == 2) // KvK1259return Ret(WDLDraw);12601261TBTable<Type>* entry = TBTables.get<Type>(pos.material_key());12621263if (!entry || !mapped(*entry, pos))1264return *result = FAIL, Ret();12651266return do_probe_table(pos, entry, wdl, result);1267}12681269// For a position where the side to move has a winning capture it is not necessary1270// to store a winning value so the generator treats such positions as "don't care"1271// and tries to assign to it a value that improves the compression ratio. Similarly,1272// if the side to move has a drawing capture, then the position is at least drawn.1273// If the position is won, then the TB needs to store a win value. But if the1274// position is drawn, the TB may store a loss value if that is better for compression.1275// All of this means that during probing, the engine must look at captures and probe1276// their results and must probe the position itself. The "best" result of these1277// probes is the correct result for the position.1278// DTZ tables do not store values when a following move is a zeroing winning move1279// (winning capture or winning pawn move). Also, DTZ store wrong values for positions1280// where the best move is an ep-move (even if losing). So in all these cases set1281// the state to ZEROING_BEST_MOVE.1282template<bool CheckZeroingMoves>1283WDLScore search(Position& pos, ProbeState* result) {12841285WDLScore value, bestValue = WDLLoss;1286StateInfo st;12871288auto moveList = MoveList<LEGAL>(pos);1289size_t totalCount = moveList.size(), moveCount = 0;12901291for (const Move move : moveList)1292{1293if (!pos.capture(move) && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))1294continue;12951296moveCount++;12971298pos.do_move(move, st);1299value = -search<false>(pos, result);1300pos.undo_move(move);13011302if (*result == FAIL)1303return WDLDraw;13041305if (value > bestValue)1306{1307bestValue = value;13081309if (value >= WDLWin)1310{1311*result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move1312return value;1313}1314}1315}13161317// In case we have already searched all the legal moves we don't have to probe1318// the TB because the stored score could be wrong. For instance TB tables1319// do not contain information on position with ep rights, so in this case1320// the result of probe_wdl_table is wrong. Also in case of only capture1321// moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to1322// return with ZEROING_BEST_MOVE set.1323bool noMoreMoves = (moveCount && moveCount == totalCount);13241325if (noMoreMoves)1326value = bestValue;1327else1328{1329value = probe_table<WDL>(pos, result);13301331if (*result == FAIL)1332return WDLDraw;1333}13341335// DTZ stores a "don't care" value if bestValue is a win1336if (bestValue >= value)1337return *result = (bestValue > WDLDraw || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue;13381339return *result = OK, value;1340}13411342} // namespace134313441345// Called at startup and after every change to1346// "SyzygyPath" UCI option to (re)create the various tables. It is not thread1347// safe, nor it needs to be.1348void Tablebases::init(const std::string& paths) {13491350TBTables.clear();1351MaxCardinality = 0;1352TBFile::Paths = paths;13531354if (paths.empty())1355return;13561357// MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..271358int code = 0;1359for (Square s = SQ_A1; s <= SQ_H8; ++s)1360if (off_A1H8(s) < 0)1361MapB1H1H7[s] = code++;13621363// MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..91364std::vector<Square> diagonal;1365code = 0;1366for (Square s = SQ_A1; s <= SQ_D4; ++s)1367if (off_A1H8(s) < 0 && file_of(s) <= FILE_D)1368MapA1D1D4[s] = code++;13691370else if (!off_A1H8(s) && file_of(s) <= FILE_D)1371diagonal.push_back(s);13721373// Diagonal squares are encoded as last ones1374for (auto s : diagonal)1375MapA1D1D4[s] = code++;13761377// MapKK[] encodes all the 462 possible legal positions of two kings where1378// the first is in the a1-d1-d4 triangle. If the first king is on the a1-d41379// diagonal, the other one shall not be above the a1-h8 diagonal.1380std::vector<std::pair<int, Square>> bothOnDiagonal;1381code = 0;1382for (int idx = 0; idx < 10; idx++)1383for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1)1384if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 01385{1386for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2)1387if ((PseudoAttacks[KING][s1] | s1) & s2)1388continue; // Illegal position13891390else if (!off_A1H8(s1) && off_A1H8(s2) > 0)1391continue; // First on diagonal, second above13921393else if (!off_A1H8(s1) && !off_A1H8(s2))1394bothOnDiagonal.emplace_back(idx, s2);13951396else1397MapKK[idx][s2] = code++;1398}13991400// Legal positions with both kings on a diagonal are encoded as last ones1401for (auto p : bothOnDiagonal)1402MapKK[p.first][p.second] = code++;14031404// Binomial[] stores the Binomial Coefficients using Pascal rule. There1405// are Binomial[k][n] ways to choose k elements from a set of n elements.1406Binomial[0][0] = 1;14071408for (int n = 1; n < 64; n++) // Squares1409for (int k = 0; k < 6 && k <= n; ++k) // Pieces1410Binomial[k][n] =1411(k > 0 ? Binomial[k - 1][n - 1] : 0) + (k < n ? Binomial[k][n - 1] : 0);14121413// MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible1414// available squares when the leading one is in 's'. Moreover the pawn with1415// highest MapPawns[] is the leading pawn, the one nearest the edge, and1416// among pawns with the same file, the one with the lowest rank.1417int availableSquares = 47; // Available squares when lead pawn is in a214181419// Init the tables for the encoding of leading pawns group: with 7-men TB we1420// can have up to 5 leading pawns (KPPPPPK).1421for (int leadPawnsCnt = 1; leadPawnsCnt <= 5; ++leadPawnsCnt)1422for (File f = FILE_A; f <= FILE_D; ++f)1423{1424// Restart the index at every file because TB table is split1425// by file, so we can reuse the same index for different files.1426int idx = 0;14271428// Sum all possible combinations for a given file, starting with1429// the leading pawn on rank 2 and increasing the rank.1430for (Rank r = RANK_2; r <= RANK_7; ++r)1431{1432Square sq = make_square(f, r);14331434// Compute MapPawns[] at first pass.1435// If sq is the leading pawn square, any other pawn cannot be1436// below or more toward the edge of sq. There are 47 available1437// squares when sq = a2 and reduced by 2 for any rank increase1438// due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 451439if (leadPawnsCnt == 1)1440{1441MapPawns[sq] = availableSquares--;1442MapPawns[flip_file(sq)] = availableSquares--;1443}1444LeadPawnIdx[leadPawnsCnt][sq] = idx;1445idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];1446}1447// After a file is traversed, store the cumulated per-file index1448LeadPawnsSize[leadPawnsCnt][f] = idx;1449}14501451// Add entries in TB tables if the corresponding ".rtbw" file exists1452for (PieceType p1 = PAWN; p1 < KING; ++p1)1453{1454TBTables.add({KING, p1, KING});14551456for (PieceType p2 = PAWN; p2 <= p1; ++p2)1457{1458TBTables.add({KING, p1, p2, KING});1459TBTables.add({KING, p1, KING, p2});14601461for (PieceType p3 = PAWN; p3 < KING; ++p3)1462TBTables.add({KING, p1, p2, KING, p3});14631464for (PieceType p3 = PAWN; p3 <= p2; ++p3)1465{1466TBTables.add({KING, p1, p2, p3, KING});14671468for (PieceType p4 = PAWN; p4 <= p3; ++p4)1469{1470TBTables.add({KING, p1, p2, p3, p4, KING});14711472for (PieceType p5 = PAWN; p5 <= p4; ++p5)1473TBTables.add({KING, p1, p2, p3, p4, p5, KING});14741475for (PieceType p5 = PAWN; p5 < KING; ++p5)1476TBTables.add({KING, p1, p2, p3, p4, KING, p5});1477}14781479for (PieceType p4 = PAWN; p4 < KING; ++p4)1480{1481TBTables.add({KING, p1, p2, p3, KING, p4});14821483for (PieceType p5 = PAWN; p5 <= p4; ++p5)1484TBTables.add({KING, p1, p2, p3, KING, p4, p5});1485}1486}14871488for (PieceType p3 = PAWN; p3 <= p1; ++p3)1489for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4)1490TBTables.add({KING, p1, p2, KING, p3, p4});1491}1492}14931494TBTables.info();1495}14961497// Probe the WDL table for a particular position.1498// If *result != FAIL, the probe was successful.1499// The return value is from the point of view of the side to move:1500// -2 : loss1501// -1 : loss, but draw under 50-move rule1502// 0 : draw1503// 1 : win, but draw under 50-move rule1504// 2 : win1505WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) {15061507*result = OK;1508return search<false>(pos, result);1509}15101511// Probe the DTZ table for a particular position.1512// If *result != FAIL, the probe was successful.1513// The return value is from the point of view of the side to move:1514// n < -100 : loss, but draw under 50-move rule1515// -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0)1516// -1 : loss, the side to move is mated1517// 0 : draw1518// 1 < n <= 100 : win in n ply (assuming 50-move counter == 0)1519// 100 < n : win, but draw under 50-move rule1520//1521// The return value n can be off by 1: a return value -n can mean a loss1522// in n+1 ply and a return value +n can mean a win in n+1 ply. This1523// cannot happen for tables with positions exactly on the "edge" of1524// the 50-move rule.1525//1526// This implies that if dtz > 0 is returned, the position is certainly1527// a win if dtz + 50-move-counter <= 99. Care must be taken that the engine1528// picks moves that preserve dtz + 50-move-counter <= 99.1529//1530// If n = 100 immediately after a capture or pawn move, then the position1531// is also certainly a win, and during the whole phase until the next1532// capture or pawn move, the inequality to be preserved is1533// dtz + 50-move-counter <= 100.1534//1535// In short, if a move is available resulting in dtz + 50-move-counter <= 99,1536// then do not accept moves leading to dtz + 50-move-counter == 100.1537int Tablebases::probe_dtz(Position& pos, ProbeState* result) {15381539*result = OK;1540WDLScore wdl = search<true>(pos, result);15411542if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws1543return 0;15441545// DTZ stores a 'don't care value in this case, or even a plain wrong1546// one as in case the best move is a losing ep, so it cannot be probed.1547if (*result == ZEROING_BEST_MOVE)1548return dtz_before_zeroing(wdl);15491550int dtz = probe_table<DTZ>(pos, result, wdl);15511552if (*result == FAIL)1553return 0;15541555if (*result != CHANGE_STM)1556return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl);15571558// DTZ stores results for the other side, so we need to do a 1-ply search and1559// find the winning move that minimizes DTZ.1560StateInfo st;1561int minDTZ = 0xFFFF;15621563for (const Move move : MoveList<LEGAL>(pos))1564{1565bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;15661567pos.do_move(move, st);15681569// For zeroing moves we want the dtz of the move _before_ doing it,1570// otherwise we will get the dtz of the next move sequence. Search the1571// position after the move to get the score sign (because even in a1572// winning position we could make a losing capture or go for a draw).1573dtz = zeroing ? -dtz_before_zeroing(search<false>(pos, result)) : -probe_dtz(pos, result);15741575// If the move mates, force minDTZ to 11576if (dtz == 1 && pos.checkers() && MoveList<LEGAL>(pos).size() == 0)1577minDTZ = 1;15781579// Convert result from 1-ply search. Zeroing moves are already accounted1580// by dtz_before_zeroing() that returns the DTZ of the previous move.1581if (!zeroing)1582dtz += sign_of(dtz);15831584// Skip the draws and if we are winning only pick positive dtz1585if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl))1586minDTZ = dtz;15871588pos.undo_move(move);15891590if (*result == FAIL)1591return 0;1592}15931594// When there are no legal moves, the position is mate: we return -11595return minDTZ == 0xFFFF ? -1 : minDTZ;1596}159715981599// Use the DTZ tables to rank root moves.1600//1601// A return value false indicates that not all probes were successful.1602bool Tablebases::root_probe(Position& pos,1603Search::RootMoves& rootMoves,1604bool rule50,1605bool rankDTZ,1606const std::function<bool()>& time_abort) {16071608ProbeState result = OK;1609StateInfo st;16101611// Obtain 50-move counter for the root position1612int cnt50 = pos.rule50_count();16131614// Check whether a position was repeated since the last zeroing move.1615bool rep = pos.has_repeated();16161617int dtz, bound = rule50 ? (MAX_DTZ / 2 - 100) : 1;16181619// Probe and rank each move1620for (auto& m : rootMoves)1621{1622pos.do_move(m.pv[0], st);16231624// Calculate dtz for the current move counting from the root position1625if (pos.rule50_count() == 0)1626{1627// In case of a zeroing move, dtz is one of -101/-1/0/1/1011628WDLScore wdl = -probe_wdl(pos, &result);1629dtz = dtz_before_zeroing(wdl);1630}1631else if ((rule50 && pos.is_draw(1)) || pos.is_repetition(1))1632{1633// In case a root move leads to a draw by repetition or 50-move rule,1634// we set dtz to zero. Note: since we are only 1 ply from the root,1635// this must be a true 3-fold repetition inside the game history.1636dtz = 0;1637}1638else1639{1640// Otherwise, take dtz for the new position and correct by 1 ply1641dtz = -probe_dtz(pos, &result);1642dtz = dtz > 0 ? dtz + 1 : dtz < 0 ? dtz - 1 : dtz;1643}16441645// Make sure that a mating move is assigned a dtz value of 11646if (pos.checkers() && dtz == 2 && MoveList<LEGAL>(pos).size() == 0)1647dtz = 1;16481649pos.undo_move(m.pv[0]);16501651if (time_abort() || result == FAIL)1652return false;16531654// Better moves are ranked higher. Certain wins are ranked equally.1655// Losing moves are ranked equally unless a 50-move draw is in sight.1656int r = dtz > 0 ? (dtz + cnt50 <= 99 && !rep ? MAX_DTZ - (rankDTZ ? dtz : 0)1657: MAX_DTZ / 2 - (dtz + cnt50))1658: dtz < 0 ? (-dtz * 2 + cnt50 < 100 ? -MAX_DTZ - (rankDTZ ? dtz : 0)1659: -MAX_DTZ / 2 + (-dtz + cnt50))1660: 0;1661m.tbRank = r;16621663// Determine the score to be displayed for this move. Assign at least1664// 1 cp to cursed wins and let it grow to 49 cp as the positions gets1665// closer to a real win.1666m.tbScore = r >= bound ? VALUE_MATE - MAX_PLY - 11667: r > 0 ? Value((std::max(3, r - (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200)1668: r == 0 ? VALUE_DRAW1669: r > -bound1670? Value((std::min(-3, r + (MAX_DTZ / 2 - 200)) * int(PawnValue)) / 200)1671: -VALUE_MATE + MAX_PLY + 1;1672}16731674return true;1675}167616771678// Use the WDL tables to rank root moves.1679// This is a fallback for the case that some or all DTZ tables are missing.1680//1681// A return value false indicates that not all probes were successful.1682bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, bool rule50) {16831684static const int WDL_to_rank[] = {-MAX_DTZ, -MAX_DTZ + 101, 0, MAX_DTZ - 101, MAX_DTZ};16851686ProbeState result = OK;1687StateInfo st;1688WDLScore wdl;168916901691// Probe and rank each move1692for (auto& m : rootMoves)1693{1694pos.do_move(m.pv[0], st);16951696if (pos.is_draw(1))1697wdl = WDLDraw;1698else1699wdl = -probe_wdl(pos, &result);17001701pos.undo_move(m.pv[0]);17021703if (result == FAIL)1704return false;17051706m.tbRank = WDL_to_rank[wdl + 2];17071708if (!rule50)1709wdl = wdl > WDLDraw ? WDLWin : wdl < WDLDraw ? WDLLoss : WDLDraw;1710m.tbScore = WDL_to_value[wdl + 2];1711}17121713return true;1714}17151716Config Tablebases::rank_root_moves(const OptionsMap& options,1717Position& pos,1718Search::RootMoves& rootMoves,1719bool rankDTZ,1720const std::function<bool()>& time_abort) {1721Config config;17221723if (rootMoves.empty())1724return config;17251726config.rootInTB = false;1727config.useRule50 = bool(options["Syzygy50MoveRule"]);1728config.probeDepth = int(options["SyzygyProbeDepth"]);1729config.cardinality = int(options["SyzygyProbeLimit"]);17301731bool dtz_available = true;17321733// Tables with fewer pieces than SyzygyProbeLimit are searched with1734// probeDepth == DEPTH_ZERO1735if (config.cardinality > MaxCardinality)1736{1737config.cardinality = MaxCardinality;1738config.probeDepth = 0;1739}17401741if (config.cardinality >= popcount(pos.pieces()) && !pos.can_castle(ANY_CASTLING))1742{1743// Rank moves using DTZ tables, bail out if time_abort flags zeitnot1744config.rootInTB =1745root_probe(pos, rootMoves, options["Syzygy50MoveRule"], rankDTZ, time_abort);17461747if (!config.rootInTB && !time_abort())1748{1749// DTZ tables are missing; try to rank moves using WDL tables1750dtz_available = false;1751config.rootInTB = root_probe_wdl(pos, rootMoves, options["Syzygy50MoveRule"]);1752}1753}17541755if (config.rootInTB)1756{1757// Sort moves according to TB rank1758std::stable_sort(1759rootMoves.begin(), rootMoves.end(),1760[](const Search::RootMove& a, const Search::RootMove& b) { return a.tbRank > b.tbRank; });17611762// Probe during search only if DTZ is not available and we are winning1763if (dtz_available || rootMoves[0].tbScore <= VALUE_DRAW)1764config.cardinality = 0;1765}1766else1767{1768// Clean up if root_probe() and root_probe_wdl() have failed1769for (auto& m : rootMoves)1770m.tbRank = 0;1771}17721773return config;1774}1775} // namespace Stockfish177617771778