/*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 "tt.h"1920#include <cassert>21#include <cstdint>22#include <cstdlib>23#include <cstring>24#include <iostream>2526#include "memory.h"27#include "misc.h"28#include "syzygy/tbprobe.h"29#include "thread.h"3031namespace Stockfish {323334// TTEntry struct is the 10 bytes transposition table entry, defined as below:35//36// key 16 bit37// depth 8 bit38// generation 5 bit39// pv node 1 bit40// bound type 2 bit41// move 16 bit42// value 16 bit43// evaluation 16 bit44//45// These fields are in the same order as accessed by TT::probe(), since memory is fastest sequentially.46// Equally, the store order in save() matches this order.4748struct TTEntry {4950// Convert internal bitfields to external types51TTData read() const {52return TTData{Move(move16), Value(value16),53Value(eval16), Depth(depth8 + DEPTH_ENTRY_OFFSET),54Bound(genBound8 & 0x3), bool(genBound8 & 0x4)};55}5657bool is_occupied() const;58void save(Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8);59// The returned age is a multiple of TranspositionTable::GENERATION_DELTA60uint8_t relative_age(const uint8_t generation8) const;6162private:63friend class TranspositionTable;6465uint16_t key16;66uint8_t depth8;67uint8_t genBound8;68Move move16;69int16_t value16;70int16_t eval16;71};7273// `genBound8` is where most of the details are. We use the following constants to manipulate 5 leading generation bits74// and 3 trailing miscellaneous bits.7576// These bits are reserved for other things.77static constexpr unsigned GENERATION_BITS = 3;78// increment for generation field79static constexpr int GENERATION_DELTA = (1 << GENERATION_BITS);80// cycle length81static constexpr int GENERATION_CYCLE = 255 + GENERATION_DELTA;82// mask to pull out generation number83static constexpr int GENERATION_MASK = (0xFF << GENERATION_BITS) & 0xFF;8485// DEPTH_ENTRY_OFFSET exists because 1) we use `bool(depth8)` as the occupancy check, but86// 2) we need to store negative depths for QS. (`depth8` is the only field with "spare bits":87// we sacrifice the ability to store depths greater than 1<<8 less the offset, as asserted in `save`.)88bool TTEntry::is_occupied() const { return bool(depth8); }8990// Populates the TTEntry with a new node's data, possibly91// overwriting an old position. The update is not atomic and can be racy.92void TTEntry::save(93Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {9495// Preserve the old ttmove if we don't have a new one96if (m || uint16_t(k) != key16)97move16 = m;9899// Overwrite less valuable entries (cheapest checks first)100if (b == BOUND_EXACT || uint16_t(k) != key16 || d - DEPTH_ENTRY_OFFSET + 2 * pv > depth8 - 4101|| relative_age(generation8))102{103assert(d > DEPTH_ENTRY_OFFSET);104assert(d < 256 + DEPTH_ENTRY_OFFSET);105106key16 = uint16_t(k);107depth8 = uint8_t(d - DEPTH_ENTRY_OFFSET);108genBound8 = uint8_t(generation8 | uint8_t(pv) << 2 | b);109value16 = int16_t(v);110eval16 = int16_t(ev);111}112else if (depth8 + DEPTH_ENTRY_OFFSET >= 5 && Bound(genBound8 & 0x3) != BOUND_EXACT)113depth8--;114}115116117uint8_t TTEntry::relative_age(const uint8_t generation8) const {118// Due to our packed storage format for generation and its cyclic119// nature we add GENERATION_CYCLE (256 is the modulus, plus what120// is needed to keep the unrelated lowest n bits from affecting121// the result) to calculate the entry age correctly even after122// generation8 overflows into the next cycle.123return (GENERATION_CYCLE + generation8 - genBound8) & GENERATION_MASK;124}125126127// TTWriter is but a very thin wrapper around the pointer128TTWriter::TTWriter(TTEntry* tte) :129entry(tte) {}130131void TTWriter::write(132Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {133entry->save(k, v, pv, b, d, m, ev, generation8);134}135136137// A TranspositionTable is an array of Cluster, of size clusterCount. Each cluster consists of ClusterSize number138// of TTEntry. Each non-empty TTEntry contains information on exactly one position. The size of a Cluster should139// divide the size of a cache line for best performance, as the cacheline is prefetched when possible.140141static constexpr int ClusterSize = 3;142143struct Cluster {144TTEntry entry[ClusterSize];145char padding[2]; // Pad to 32 bytes146};147148static_assert(sizeof(Cluster) == 32, "Suboptimal Cluster size");149150151// Sets the size of the transposition table,152// measured in megabytes. Transposition table consists153// of clusters and each cluster consists of ClusterSize number of TTEntry.154void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) {155aligned_large_pages_free(table);156157clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);158159table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));160161if (!table)162{163std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl;164exit(EXIT_FAILURE);165}166167clear(threads);168}169170171// Initializes the entire transposition table to zero,172// in a multi-threaded way.173void TranspositionTable::clear(ThreadPool& threads) {174generation8 = 0;175const size_t threadCount = threads.num_threads();176177for (size_t i = 0; i < threadCount; ++i)178{179threads.run_on_thread(i, [this, i, threadCount]() {180// Each thread will zero its part of the hash table181const size_t stride = clusterCount / threadCount;182const size_t start = stride * i;183const size_t len = i + 1 != threadCount ? stride : clusterCount - start;184185std::memset(&table[start], 0, len * sizeof(Cluster));186});187}188189for (size_t i = 0; i < threadCount; ++i)190threads.wait_on_thread(i);191}192193194// Returns an approximation of the hashtable195// occupation during a search. The hash is x permill full, as per UCI protocol.196// Only counts entries which match the current generation.197int TranspositionTable::hashfull(int maxAge) const {198int maxAgeInternal = maxAge << GENERATION_BITS;199int cnt = 0;200for (int i = 0; i < 1000; ++i)201for (int j = 0; j < ClusterSize; ++j)202cnt += table[i].entry[j].is_occupied()203&& table[i].entry[j].relative_age(generation8) <= maxAgeInternal;204205return cnt / ClusterSize;206}207208209void TranspositionTable::new_search() {210// increment by delta to keep lower bits as is211generation8 += GENERATION_DELTA;212}213214215uint8_t TranspositionTable::generation() const { return generation8; }216217218// Looks up the current position in the transposition219// table. It returns true if the position is found.220// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry221// to be replaced later. The replace value of an entry is calculated as its depth222// minus 8 times its relative age. TTEntry t1 is considered more valuable than223// TTEntry t2 if its replace value is greater than that of t2.224std::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) const {225226TTEntry* const tte = first_entry(key);227const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster228229for (int i = 0; i < ClusterSize; ++i)230if (tte[i].key16 == key16)231// This gap is the main place for read races.232// After `read()` completes that copy is final, but may be self-inconsistent.233return {tte[i].is_occupied(), tte[i].read(), TTWriter(&tte[i])};234235// Find an entry to be replaced according to the replacement strategy236TTEntry* replace = tte;237for (int i = 1; i < ClusterSize; ++i)238if (replace->depth8 - replace->relative_age(generation8)239> tte[i].depth8 - tte[i].relative_age(generation8))240replace = &tte[i];241242return {false,243TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_ENTRY_OFFSET, BOUND_NONE, false},244TTWriter(replace)};245}246247248TTEntry* TranspositionTable::first_entry(const Key key) const {249return &table[mul_hi64(key, clusterCount)].entry[0];250}251252} // namespace Stockfish253254255