/*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 "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}112}113114115uint8_t TTEntry::relative_age(const uint8_t generation8) const {116// Due to our packed storage format for generation and its cyclic117// nature we add GENERATION_CYCLE (256 is the modulus, plus what118// is needed to keep the unrelated lowest n bits from affecting119// the result) to calculate the entry age correctly even after120// generation8 overflows into the next cycle.121return (GENERATION_CYCLE + generation8 - genBound8) & GENERATION_MASK;122}123124125// TTWriter is but a very thin wrapper around the pointer126TTWriter::TTWriter(TTEntry* tte) :127entry(tte) {}128129void TTWriter::write(130Key k, Value v, bool pv, Bound b, Depth d, Move m, Value ev, uint8_t generation8) {131entry->save(k, v, pv, b, d, m, ev, generation8);132}133134135// A TranspositionTable is an array of Cluster, of size clusterCount. Each cluster consists of ClusterSize number136// of TTEntry. Each non-empty TTEntry contains information on exactly one position. The size of a Cluster should137// divide the size of a cache line for best performance, as the cacheline is prefetched when possible.138139static constexpr int ClusterSize = 3;140141struct Cluster {142TTEntry entry[ClusterSize];143char padding[2]; // Pad to 32 bytes144};145146static_assert(sizeof(Cluster) == 32, "Suboptimal Cluster size");147148149// Sets the size of the transposition table,150// measured in megabytes. Transposition table consists151// of clusters and each cluster consists of ClusterSize number of TTEntry.152void TranspositionTable::resize(size_t mbSize, ThreadPool& threads) {153aligned_large_pages_free(table);154155clusterCount = mbSize * 1024 * 1024 / sizeof(Cluster);156157table = static_cast<Cluster*>(aligned_large_pages_alloc(clusterCount * sizeof(Cluster)));158159if (!table)160{161std::cerr << "Failed to allocate " << mbSize << "MB for transposition table." << std::endl;162exit(EXIT_FAILURE);163}164165clear(threads);166}167168169// Initializes the entire transposition table to zero,170// in a multi-threaded way.171void TranspositionTable::clear(ThreadPool& threads) {172generation8 = 0;173const size_t threadCount = threads.num_threads();174175for (size_t i = 0; i < threadCount; ++i)176{177threads.run_on_thread(i, [this, i, threadCount]() {178// Each thread will zero its part of the hash table179const size_t stride = clusterCount / threadCount;180const size_t start = stride * i;181const size_t len = i + 1 != threadCount ? stride : clusterCount - start;182183std::memset(&table[start], 0, len * sizeof(Cluster));184});185}186187for (size_t i = 0; i < threadCount; ++i)188threads.wait_on_thread(i);189}190191192// Returns an approximation of the hashtable193// occupation during a search. The hash is x permill full, as per UCI protocol.194// Only counts entries which match the current generation.195int TranspositionTable::hashfull(int maxAge) const {196int maxAgeInternal = maxAge << GENERATION_BITS;197int cnt = 0;198for (int i = 0; i < 1000; ++i)199for (int j = 0; j < ClusterSize; ++j)200cnt += table[i].entry[j].is_occupied()201&& table[i].entry[j].relative_age(generation8) <= maxAgeInternal;202203return cnt / ClusterSize;204}205206207void TranspositionTable::new_search() {208// increment by delta to keep lower bits as is209generation8 += GENERATION_DELTA;210}211212213uint8_t TranspositionTable::generation() const { return generation8; }214215216// Looks up the current position in the transposition217// table. It returns true if the position is found.218// Otherwise, it returns false and a pointer to an empty or least valuable TTEntry219// to be replaced later. The replace value of an entry is calculated as its depth220// minus 8 times its relative age. TTEntry t1 is considered more valuable than221// TTEntry t2 if its replace value is greater than that of t2.222std::tuple<bool, TTData, TTWriter> TranspositionTable::probe(const Key key) const {223224TTEntry* const tte = first_entry(key);225const uint16_t key16 = uint16_t(key); // Use the low 16 bits as key inside the cluster226227for (int i = 0; i < ClusterSize; ++i)228if (tte[i].key16 == key16)229// This gap is the main place for read races.230// After `read()` completes that copy is final, but may be self-inconsistent.231return {tte[i].is_occupied(), tte[i].read(), TTWriter(&tte[i])};232233// Find an entry to be replaced according to the replacement strategy234TTEntry* replace = tte;235for (int i = 1; i < ClusterSize; ++i)236if (replace->depth8 - replace->relative_age(generation8)237> tte[i].depth8 - tte[i].relative_age(generation8))238replace = &tte[i];239240return {false,241TTData{Move::none(), VALUE_NONE, VALUE_NONE, DEPTH_ENTRY_OFFSET, BOUND_NONE, false},242TTWriter(replace)};243}244245246TTEntry* TranspositionTable::first_entry(const Key key) const {247return &table[mul_hi64(key, clusterCount)].entry[0];248}249250} // namespace Stockfish251252253