diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 0000000..522a818 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,235 @@ +/* hash.c - hash management. + * + * Copyright (C) 2024 Bruno Raoult ("br") + * Licensed under the GNU General Public License v3.0 or later. + * Some rights reserved. See COPYING. + * + * You should have received a copy of the GNU General Public License along with this + * program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#include + +#include +#include + +#include "chessdefs.h" +#include "util.h" +#include "position.h" +#include "piece.h" +#include "hash.h" + +u64 zobrist_pieces[16][64]; +u64 zobrist_castling[4 * 4 + 1]; +u64 zobrist_turn; /* for black, XOR each ply */ +u64 zobrist_ep[9]; /* 0-7: ep file, 8: SQUARE_NONE */ + +hasht_t hash_tt; /* main transposition table */ + +/** + * zobrist_init() - initialize zobrist tables. + * + * Initialize all zobrist random bitmasks. + */ +void zobrist_init(void) +{ + for (color_t c = WHITE; c <= BLACK; ++c) { + for (piece_type_t p = PAWN; p <= KING; ++p) + for (square_t sq = A1; sq <= H8; ++sq) + zobrist_pieces[MAKE_PIECE(p, c)][sq] = rand64(); + } + for (castle_rights_t c = CASTLE_NONE; c <= CASTLE_ALL; ++c) + zobrist_castling[c] = rand64(); + for (file_t f = FILE_A; f <= FILE_H; ++f) + zobrist_ep[f] = rand64(); + zobrist_ep[8] = 0; + zobrist_turn = rand64(); +} + +/** + * zobrist_calc() - calculate a position zobrist hash. + * + * Normally, Zobrist keys are incrementally calculated when doing or + * undoing a move. + * This function should normally only be called: + * - When starting a new position + * - To verify incremental Zobrist calculation is correct + * + */ +key zobrist_calc(pos_t *pos) +{ + key key = 0; + + if (pos->turn == BLACK) + key ^= zobrist_turn; + + for (color_t c = WHITE; c <= BLACK; ++c) { + for (piece_type_t pt = PAWN; pt <= KING; ++pt) { + piece_t piece = MAKE_PIECE(pt, c); + bitboard_t bb = pos->bb[c][pt]; + while (bb) { + square_t sq = bb_next(&bb); + key ^= zobrist_pieces[piece][sq]; + } + } + } + key ^= pos->castle; + key ^= EP_ZOBRIST_IDX(pos->en_passant); + + return key; +} + +/** + * hash_create() - hashtable creation. + * @sizemb: s32 size of hash table in Mb + * + * Create a hash table of max @sizemb (or HASH_SIZE_MBif @sizemb <= 0) Mb size. + * This function must be called at startup. + * + * The number of bucket_t entries fitting in @sizemb is calculated, and rounded + * (down) to a power of 2. + * This means the actual size could be lower than @sizemb (nearly halved in + * worst case). + * + * If transposition hashtable already exists and new size would not change, + * the old one is cleared. + * If transposition hashtable already exists and new size is different, + * the old one is destroyed first (old data is not preserved). + * + * TODO: + * - Rebuild old hashtable data ? + * + * @return: hash table size in Mb. If memory allocation fails, the function does + * not return. + */ +#include +#include + +static void zob(char *str, size_t alloc) +{ + extern char etext, edata, end; + printf("%s %lu ", str, alloc); + //printf(" program text (etext) %10p\n", &etext); + //printf(" initialized data (edata) %10p\n", &edata); + //printf(" uninitialized data (end) %10p\n", &end); + + fflush(stdout); + + printf("\n\tpress a key..."); + getchar(); +} + +int hash_create(s32 sizemb) +{ + size_t bytes, target_nbuckets; + u32 nbits; + + static_assert(sizeof(hentry_t) == 16, "fatal: hentry_t size != 16"); + + printf("mb = %'7u ", sizemb); + /* adjust tt size */ + if (sizemb <= 0) + sizemb = HASH_SIZE_DEFAULT; + sizemb = clamp(sizemb, HASH_SIZE_MIN, HASH_SIZE_MAX); + printf("-> %'6d ", sizemb); + + bytes = sizemb * 1024ull * 1024ull; /* bytes wanted */ + target_nbuckets = bytes / sizeof(bucket_t); /* target buckets */ + + nbits = msb64(target_nbuckets); /* adjust to power of 2 */ + + if (hash_tt.nbits != nbits) { + if (hash_tt.nbits) + hash_delete(); + + hash_tt.nbits = nbits; + + hash_tt.nbuckets = BIT(hash_tt.nbits); + hash_tt.nkeys = hash_tt.nbuckets * NBUCKETS; + + hash_tt.bytes = hash_tt.nbuckets * sizeof(bucket_t); + hash_tt.mb = hash_tt.bytes / 1024 / 1024; + + hash_tt.mask = -1ull >> (64 - nbits); + + hash_tt.keys = safe_malloc(hash_tt.bytes); + + //printf("bits=%2d size=%'15lu/%'6d Mb/%'14lu buckets ", + // hash_tt.nbits, hash_tt.bytes, hash_tt.mb, hash_tt.nbuckets); + //printf("mask=%9x\n", hash_tt.mask); + } else { + printf("unchanged (cleared)\n"); + //hash_clear(); + } + /* attention - may fail ! */ + hash_clear(); + + return hash_tt.nbits; +} + +/** + * hash_clear() - clear hashtable data. + * + * Reset hashtable entries (if available) and statistic information. + */ +void hash_clear() +{ + if (hash_tt.keys) + memset(hash_tt.keys, 0, hash_tt.bytes); + + hash_tt.used_buckets = 0; + hash_tt.used_keys = 0; + hash_tt.collisions = 0; +} + +/** + * hash_delete() - delete hashtable data. + * + * free hashtable data. + */ +void hash_delete() +{ + if (hash_tt.keys) + safe_free(hash_tt.keys); + memset(&hash_tt, 0, sizeof(hash_tt)); +} + +// #define zob() + +int main() +{ + printf("entry=%lu bucket=%lu\n", sizeof(hentry_t), sizeof(bucket_t)); + zob("before alloc", 0); + setlocale(LC_NUMERIC, ""); + hash_create(-1); + zob("after", -1); + hash_create(1); + hash_create(2); + hash_create(3); + hash_create(4); + hash_create(10); + zob("after", 10); + hash_create(20); + hash_create(40); + hash_create(100); + zob("after", 100); + hash_create(200); + hash_create(400); + hash_create(1000); + zob("after", 1000); + hash_create(1000); + zob("after", 1024); + //hash_create(2000); + //zob("after", 100); + hash_create(4096); + zob("after", 4096); + //hash_create(32767); + //hash_create(32768); + //hash_create(32769); + //hash_create(65536); + //hash_create(400000); + return 0; +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 0000000..9fc014f --- /dev/null +++ b/src/hash.h @@ -0,0 +1,89 @@ +/* hash.h - hash management. + * + * Copyright (C) 2024 Bruno Raoult ("br") + * Licensed under the GNU General Public License v3.0 or later. + * Some rights reserved. See COPYING. + * + * You should have received a copy of the GNU General Public License along with this + * program. If not, see . + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef HASH_H +#define HASH_H + +#include + +#include "chessdefs.h" + +#define NBUCKETS 4 /* buckets per hash table entry */ + +#define HASH_SIZE_DEFAULT 32 /* default: 32Mb */ +#define HASH_SIZE_MIN 4 +#define HASH_SIZE_MAX 32768 /* 32Gb */ + +typedef u64 key; + +/** + * hentry_t: hashtable bucket. + * + * Size should be exactly 16 bytes. If impossible to fit necessary data in + * 16 bytes in future, it should be updated to be exactly 32 bytes. + */ +typedef struct { + key key; /* zobrist */ + union { + u64 data; + struct { + u16 depth; /* ply in search */ + s16 eval; + u16 move; + u8 flags; /* maybe for locking, etc... */ + u8 filler; + }; + }; +} hentry_t; + +typedef struct { + hentry_t buckets[NBUCKETS]; +} bucket_t; + +typedef struct { + bucket_t *keys; /* &hashtable entries */ + + /* memory size in bytes/mb */ + size_t bytes; + u32 mb; + + /* size in buckets/keys */ + size_t nbuckets; + size_t nkeys; /* nbuckets * NBUCKETS */ + + /* internal representation */ + u32 nbits; /* #buckets in bits, power of 2 */ + u32 mask; /* nbuckets - 1, key mask */ + + /* stats - unsure about usage */ + size_t used_buckets; + size_t used_keys; + u64 collisions; +} hasht_t; + +/* hack: + * ep zobrist key index is 0-7 for each en-passant file, 8 for SQUARE_NONE. + * To transform : + * - ep == 64 (SQUARE_NONE) to id = 8 + * - ep == 0~63 to idx = sq_file(ep), i.e. (ep & 7) + * we use the formula: + * idx = ( ( ep & SQUARE_NONE ) >> 3 ) | sq_file(ep); + */ +#define EP_ZOBRIST_IDX(ep) ( ( (ep) >> 3 ) | sq_file(ep) ) + +void zobrist_init(void); +int hash_create(int Mb); +void hash_clear(); +void hash_delete(); + +#endif /* HASH_H */