### Built-in benchmark for pgo-builds
PGOBENCH = ./$(EXE) bench
- ### Object files
- OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \
- material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \
- search.o thread.o timeman.o tt.o uci.o ucioption.o syzygy/tbprobe.o \
- hashprobe.grpc.pb.o hashprobe.pb.o
- CLIOBJS = client.o hashprobe.grpc.pb.o hashprobe.pb.o uci.o
+ ### Source and object files
+ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp \
+ material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp \
- search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp
++ search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp tune.cpp syzygy/tbprobe.cpp \
++ hashprobe.grpc.pb.cc hashprobe.pb.cc
++CLISRCS = client.cpp hashprobe.grpc.pb.cc hashprobe.pb.cc uci.cpp
+
+ OBJS = $(notdir $(SRCS:.cpp=.o))
++CLIOBJS = $(notdir $(CLISRCS:.cpp=.o))
+
+ VPATH = syzygy
### Establish the operating system name
KERNEL = $(shell uname -s)
### Section 2. High-level Configuration
### ==========================================================================
#
- # flag --- Comp switch --- Description
+ # flag --- Comp switch --- Description
# ----------------------------------------------------------------------------
#
# debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode
optimize = yes
debug = no
sanitize = no
- bits = 32
+ bits = 64
prefetch = no
popcnt = no
sse = no
pext = no
### 2.2 Architecture specific
-
ifeq ($(ARCH),general-32)
arch = any
+ bits = 32
endif
ifeq ($(ARCH),x86-32-old)
arch = i386
+ bits = 32
endif
ifeq ($(ARCH),x86-32)
arch = i386
+ bits = 32
prefetch = yes
sse = yes
endif
ifeq ($(ARCH),general-64)
arch = any
- bits = 64
endif
ifeq ($(ARCH),x86-64)
arch = x86_64
- bits = 64
prefetch = yes
sse = yes
endif
ifeq ($(ARCH),x86-64-modern)
arch = x86_64
- bits = 64
prefetch = yes
popcnt = yes
sse = yes
ifeq ($(ARCH),x86-64-bmi2)
arch = x86_64
- bits = 64
prefetch = yes
popcnt = yes
sse = yes
ifeq ($(ARCH),armv7)
arch = armv7
prefetch = yes
+ bits = 32
+ endif
+
+ ifeq ($(ARCH),armv8)
+ arch = armv8-a
+ prefetch = yes
+ popcnt = yes
endif
ifeq ($(ARCH),ppc-32)
arch = ppc
+ bits = 32
endif
ifeq ($(ARCH),ppc-64)
arch = ppc64
- bits = 64
popcnt = yes
prefetch = yes
endif
-
### ==========================================================================
- ### Section 3. Low-level configuration
+ ### Section 3. Low-level Configuration
### ==========================================================================
### 3.1 Selecting compiler (default = gcc)
-
CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++11 $(EXTRACXXFLAGS)
DEPENDFLAGS += -std=c++11
LDFLAGS += $(EXTRALDFLAGS)
ifeq ($(COMP),gcc)
comp=gcc
CXX=g++
- CXXFLAGS += -pedantic -Wextra -Wshadow
+ CXXFLAGS += -pedantic -Wextra
- ifeq ($(ARCH),armv7)
+ ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8))
ifeq ($(OS),Android)
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
endif
endif
- ifeq ($(ARCH),armv7)
+ ifeq ($(ARCH),$(filter $(ARCH),armv7 armv8))
ifeq ($(OS),Android)
CXXFLAGS += -m$(bits)
LDFLAGS += -m$(bits)
### 3.3 Optimization
ifeq ($(optimize),yes)
- CXXFLAGS += -O3
+ CXXFLAGS += -O3 -g
ifeq ($(comp),gcc)
ifeq ($(OS), Android)
### 3.6 popcnt
ifeq ($(popcnt),yes)
- ifeq ($(arch),ppc64)
+ ifeq ($(arch),$(filter $(arch),ppc64 armv8-a))
CXXFLAGS += -DUSE_POPCNT
else ifeq ($(comp),icc)
CXXFLAGS += -msse3 -DUSE_POPCNT
endif
endif
- ### 3.8 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows.
+ ### 3.8 Link Time Optimization
### This is a mix of compile and link time options because the lto link phase
### needs access to the optimization flags.
ifeq ($(optimize),yes)
LDFLAGS += $(CXXFLAGS)
endif
+ # To use LTO and static linking on windows, the tool chain requires a recent gcc:
+ # gcc version 10.1 in msys2 or TDM-GCC version 9.2 are know to work, older might not.
+ # So, only enable it for a cross from Linux by default.
ifeq ($(comp),mingw)
ifeq ($(KERNEL),Linux)
CXXFLAGS += -flto
LDFLAGS += -fPIE -pie
endif
-
### ==========================================================================
- ### Section 4. Public targets
+ ### Section 4. Public Targets
### ==========================================================================
help:
@echo "ppc-64 > PPC 64-bit"
@echo "ppc-32 > PPC 32-bit"
@echo "armv7 > ARMv7 32-bit"
+ @echo "armv8 > ARMv8 64-bit"
@echo "general-64 > unspecified 64-bit"
@echo "general-32 > unspecified 32-bit"
@echo ""
@echo ""
- .PHONY: help build profile-build strip install clean objclean profileclean help \
+ .PHONY: help build profile-build strip install clean objclean profileclean \
config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \
clang-profile-use clang-profile-make
# clean auxiliary profiling files
profileclean:
@rm -rf profdir
- @rm -f bench.txt *.gcda ./syzygy/*.gcda *.gcno ./syzygy/*.gcno
+ @rm -f bench.txt *.gcda *.gcno
@rm -f stockfish.profdata *.profraw
default:
help
### ==========================================================================
- ### Section 5. Private targets
+ ### Section 5. Private Targets
### ==========================================================================
-all: $(EXE) .depend
+all: $(EXE) client .depend
config-sanity:
@echo ""
@test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "address" || test "$(sanitize)" = "no"
@test "$(optimize)" = "yes" || test "$(optimize)" = "no"
@test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \
- test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7"
+ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || \
+ test "$(arch)" = "armv7" || test "$(arch)" = "armv8-a"
@test "$(bits)" = "32" || test "$(bits)" = "64"
@test "$(prefetch)" = "yes" || test "$(prefetch)" = "no"
@test "$(popcnt)" = "yes" || test "$(popcnt)" = "no"
EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \
all
+### GRPC
+
+PROTOS_PATH = .
+PROTOC = protoc
+GRPC_CPP_PLUGIN = grpc_cpp_plugin
+GRPC_CPP_PLUGIN_PATH ?= `which $(GRPC_CPP_PLUGIN)`
+
+%.grpc.pb.h %.grpc.pb.cc: %.proto
+ $(PROTOC) -I $(PROTOS_PATH) --grpc_out=. --plugin=protoc-gen-grpc=$(GRPC_CPP_PLUGIN_PATH) $<
+
+# oh my
+%.cpp: %.cc
+ cp $< $@
+
+%.pb.h %.pb.cc: %.proto
+ $(PROTOC) -I $(PROTOS_PATH) --cpp_out=. $<
+
+#LDFLAGS += -Wl,-Bstatic -Wl,-\( -lprotobuf -lgrpc++_unsecure -lgrpc_unsecure -lgrpc -lz -Wl,-\) -Wl,-Bdynamic -ldl
+LDFLAGS += /usr/lib/x86_64-linux-gnu/libprotobuf.a /usr/lib/x86_64-linux-gnu/libgrpc++_unsecure.a /usr/lib/x86_64-linux-gnu/libgrpc_unsecure.a /usr/lib/x86_64-linux-gnu/libgrpc.a /usr/lib/x86_64-linux-gnu/libcares.a -ldl -lz
+#LDFLAGS += /usr/lib/x86_64-linux-gnu/libprotobuf.a /usr/lib/libgrpc++_unsecure.a /usr/lib/libgrpc_unsecure.a /usr/lib/libgrpc.a /usr/lib/x86_64-linux-gnu/libcares.a -ldl -lz
+
+client: $(CLIOBJS)
+ $(CXX) -o $@ $(CLIOBJS) $(LDFLAGS)
+
+# Other stuff
+
.depend:
- -@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) $(OBJS:.o=.cc) > $@ 2> /dev/null
+ -@$(CXX) $(DEPENDFLAGS) -MM $(SRCS) > $@ 2> /dev/null
-include .depend
-
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <deque>
#include <iostream>
+#include <stack>
+#include <thread>
#include "bitboard.h"
+ #include "endgame.h"
#include "position.h"
#include "search.h"
#include "thread.h"
#include "tt.h"
#include "uci.h"
- #include "endgame.h"
#include "syzygy/tbprobe.h"
+#include <grpc/grpc.h>
+#include <grpc++/server.h>
+#include <grpc++/server_builder.h>
+#include "hashprobe.h"
+#include "hashprobe.grpc.pb.h"
+#include "tt.h"
+
+using grpc::Server;
+using grpc::ServerBuilder;
+using grpc::ServerContext;
+using grpc::Status;
+using grpc::StatusCode;
+using namespace hashprobe;
+
+Status HashProbeImpl::Probe(ServerContext* context,
+ const HashProbeRequest* request,
+ HashProbeResponse *response) {
+ Position pos;
+ StateInfo st;
+ pos.set(request->fen(), /*isChess960=*/false, &st, Threads.main());
+ if (!pos.pos_is_ok()) {
+ return Status(StatusCode::INVALID_ARGUMENT, "Invalid FEN");
+ }
+
+ bool invert = (pos.side_to_move() == BLACK);
+ StateListPtr setup_states = StateListPtr(new std::deque<StateInfo>(1));
+
+ ProbeMove(&pos, setup_states.get(), invert, response->mutable_root());
+
+ MoveList<LEGAL> moves(pos);
+ for (const ExtMove* em = moves.begin(); em != moves.end(); ++em) {
+ HashProbeLine *line = response->add_line();
+ FillMove(&pos, em->move, line->mutable_move());
+ setup_states->push_back(StateInfo());
+ pos.do_move(em->move, setup_states->back());
+ ProbeMove(&pos, setup_states.get(), !invert, line);
+ pos.undo_move(em->move);
+ }
+
+ return Status::OK;
+}
+
+void HashProbeImpl::FillMove(Position *pos, Move move, HashProbeMove* decoded) {
+ if (!is_ok(move)) return;
+
+ Square from = from_sq(move);
+ Square to = to_sq(move);
+
+ if (type_of(move) == CASTLING) {
+ to = make_square(to > from ? FILE_G : FILE_C, rank_of(from));
+ }
+
+ Piece moved_piece = pos->moved_piece(move);
+ std::string pretty;
+ if (type_of(move) == CASTLING) {
+ if (to > from) {
+ pretty = "O-O";
+ } else {
+ pretty = "O-O-O";
+ }
+ } else if (type_of(moved_piece) == PAWN) {
+ if (type_of(move) == ENPASSANT || pos->piece_on(to) != NO_PIECE) {
+ // Capture.
+ pretty = char('a' + file_of(from));
+ pretty += "x";
+ }
+ pretty += UCI::square(to);
+ if (type_of(move) == PROMOTION) {
+ pretty += "=";
+ pretty += " PNBRQK"[promotion_type(move)];
+ }
+ } else {
+ pretty = " PNBRQK"[type_of(moved_piece)];
+ Bitboard attackers = pos->attackers_to(to) & pos->pieces(color_of(moved_piece), type_of(moved_piece));
+ if (more_than_one(attackers)) {
+ // Remove all illegal moves to disambiguate.
+ Bitboard att_copy = attackers;
+ while (att_copy) {
+ Square s = pop_lsb(&att_copy);
+ Move m = make_move(s, to);
+ if (!pos->pseudo_legal(m) || !pos->legal(m)) {
+ attackers &= ~SquareBB[s];
+ }
+ }
+ }
+ if (more_than_one(attackers)) {
+ // Disambiguate by file if possible.
+ Bitboard attackers_this_file = attackers & file_bb(file_of(from));
+ if (attackers != attackers_this_file) {
+ pretty += char('a' + file_of(from));
+ attackers = attackers_this_file;
+ }
+ if (more_than_one(attackers)) {
+ // Still ambiguous, so need to disambiguate by rank.
+ pretty += char('1' + rank_of(from));
+ }
+ }
+
+ if (type_of(move) == ENPASSANT || pos->piece_on(to) != NO_PIECE) {
+ pretty += "x";
+ }
+
+ pretty += UCI::square(to);
+ }
+
+ if (pos->gives_check(move)) {
+ // Check if mate.
+ StateInfo si;
+ pos->do_move(move, si, true);
+ if (MoveList<LEGAL>(*pos).size() > 0) {
+ pretty += "+";
+ } else {
+ pretty += "#";
+ }
+ pos->undo_move(move);
+ }
+
+ decoded->set_pretty(pretty);
+}
+
+void HashProbeImpl::ProbeMove(Position* pos, std::deque<StateInfo>* setup_states, bool invert, HashProbeLine* response) {
+ bool found;
+ TTEntry *entry = TT.probe(pos->key(), found);
+ response->set_found(found);
+ if (found) {
+ Value value = entry->value();
+ Value eval = entry->eval();
+ Bound bound = entry->bound();
+
+ if (invert) {
+ value = -value;
+ eval = -eval;
+ if (bound == BOUND_UPPER) {
+ bound = BOUND_LOWER;
+ } else if (bound == BOUND_LOWER) {
+ bound = BOUND_UPPER;
+ }
+ }
+
+ response->set_depth(entry->depth());
+ FillValue(eval, response->mutable_eval());
+ if (entry->depth() > DEPTH_NONE) {
+ FillValue(value, response->mutable_value());
+ }
+ response->set_bound(HashProbeLine::ValueBound(bound));
+
+ // Follow the PV until we hit an illegal move.
+ std::stack<Move> pv;
+ std::set<Key> seen;
+ while (found && is_ok(entry->move()) &&
+ pos->pseudo_legal(entry->move()) &&
+ pos->legal(entry->move())) {
+ FillMove(pos, entry->move(), response->add_pv());
+ if (seen.count(pos->key())) break;
+ pv.push(entry->move());
+ seen.insert(pos->key());
+ setup_states->push_back(StateInfo());
+ pos->do_move(entry->move(), setup_states->back());
+ entry = TT.probe(pos->key(), found);
+ }
+
+ // Unroll the PV back again, so the Position object remains unchanged.
+ while (!pv.empty()) {
+ pos->undo_move(pv.top());
+ pv.pop();
+ }
+ }
+}
+
+void HashProbeImpl::FillValue(Value value, HashProbeScore* score) {
+ if (abs(value) < VALUE_MATE - MAX_PLY) {
+ score->set_score_type(HashProbeScore::SCORE_CP);
+ score->set_score_cp(value * 100 / PawnValueEg);
+ } else {
+ score->set_score_type(HashProbeScore::SCORE_MATE);
+ score->set_score_mate((value > 0 ? VALUE_MATE - value + 1 : -VALUE_MATE - value) / 2);
+ }
+}
+
+HashProbeThread::HashProbeThread(const std::string &server_address) {
+ builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
+ builder.RegisterService(&service);
+ server = std::move(builder.BuildAndStart());
+ std::cout << "Server listening on " << server_address << std::endl;
+ std::thread([this]{ server->Wait(); }).detach();
+}
+
+void HashProbeThread::Shutdown() {
+ server->Shutdown();
+}
+
namespace PSQT {
void init();
}
std::cout << engine_info() << std::endl;
UCI::init(Options);
+ Tune::init();
PSQT::init();
Bitboards::init();
Position::init();
Bitbases::init();
Endgames::init();
- Threads.set(Options["Threads"]);
+ Threads.set(size_t(Options["Threads"]));
Search::clear(); // After threads are up
UCI::loop(argc, argv);
#include <sstream>
#include <vector>
+ #if defined(__linux__) && !defined(__ANDROID__)
+ #include <stdlib.h>
+ #include <sys/mman.h>
+ #endif
+
#include "misc.h"
#include "thread.h"
/// Version number. If Version is left empty, then compile date in the format
/// DD-MM-YY and show in engine_info.
- const string Version = "11";
+ const string Version = "";
/// Our fancy logging facility. The trick here is to replace cin.rdbuf() and
/// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We
{
date >> month >> day >> year;
ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2);
+ ss << "-asn";
}
ss << (Is64Bit ? " 64" : "")
const std::string compiler_info() {
- #define STRINGIFY2(x) #x
- #define STRINGIFY(x) STRINGIFY2(x)
- #define VER_STRING(major, minor, patch) STRINGIFY(major) "." STRINGIFY(minor) "." STRINGIFY(patch)
+ #define stringify2(x) #x
+ #define stringify(x) stringify2(x)
+ #define make_version_string(major, minor, patch) stringify(major) "." stringify(minor) "." stringify(patch)
/// Predefined macros hell:
///
#ifdef __clang__
compiler += "clang++ ";
- compiler += VER_STRING(__clang_major__, __clang_minor__, __clang_patchlevel__);
+ compiler += make_version_string(__clang_major__, __clang_minor__, __clang_patchlevel__);
#elif __INTEL_COMPILER
compiler += "Intel compiler ";
compiler += "(version ";
- compiler += STRINGIFY(__INTEL_COMPILER) " update " STRINGIFY(__INTEL_COMPILER_UPDATE);
+ compiler += stringify(__INTEL_COMPILER) " update " stringify(__INTEL_COMPILER_UPDATE);
compiler += ")";
#elif _MSC_VER
compiler += "MSVC ";
compiler += "(version ";
- compiler += STRINGIFY(_MSC_FULL_VER) "." STRINGIFY(_MSC_BUILD);
+ compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD);
compiler += ")";
#elif __GNUC__
compiler += "g++ (GNUC) ";
- compiler += VER_STRING(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
+ compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__);
#else
compiler += "Unknown compiler ";
compiler += "(unknown version)";
#endif
- #if defined(__APPLE__)
+ #if defined(__APPLE__)
compiler += " on Apple";
#elif defined(__CYGWIN__)
compiler += " on Cygwin";
#endif
+
+ /// aligned_ttmem_alloc() will return suitably aligned memory, and if possible use large pages.
+ /// The returned pointer is the aligned one, while the mem argument is the one that needs
+ /// to be passed to free. With c++17 some of this functionality could be simplified.
+
+ #if defined(__linux__) && !defined(__ANDROID__)
+
+ void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
+
+ constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page sizes
+ size_t size = ((allocSize + alignment - 1) / alignment) * alignment; // multiple of alignment
+ if (posix_memalign(&mem, alignment, size))
+ mem = nullptr;
+ madvise(mem, allocSize, MADV_HUGEPAGE);
+ return mem;
+ }
+
+ #elif defined(_WIN64)
+
+ static void* aligned_ttmem_alloc_large_pages(size_t allocSize) {
+
+ HANDLE hProcessToken { };
+ LUID luid { };
+ void* mem = nullptr;
+
+ const size_t largePageSize = GetLargePageMinimum();
+ if (!largePageSize)
+ return nullptr;
+
+ // We need SeLockMemoryPrivilege, so try to enable it for the process
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken))
+ return nullptr;
+
+ if (LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid))
+ {
+ TOKEN_PRIVILEGES tp { };
+ TOKEN_PRIVILEGES prevTp { };
+ DWORD prevTpLen = 0;
+
+ tp.PrivilegeCount = 1;
+ tp.Privileges[0].Luid = luid;
+ tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ // Try to enable SeLockMemoryPrivilege. Note that even if AdjustTokenPrivileges() succeeds,
+ // we still need to query GetLastError() to ensure that the privileges were actually obtained.
+ if (AdjustTokenPrivileges(
+ hProcessToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), &prevTp, &prevTpLen) &&
+ GetLastError() == ERROR_SUCCESS)
+ {
+ // Round up size to full pages and allocate
+ allocSize = (allocSize + largePageSize - 1) & ~size_t(largePageSize - 1);
+ mem = VirtualAlloc(
+ NULL, allocSize, MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE);
+
+ // Privilege no longer needed, restore previous state
+ AdjustTokenPrivileges(hProcessToken, FALSE, &prevTp, 0, NULL, NULL);
+ }
+ }
+
+ CloseHandle(hProcessToken);
+
+ return mem;
+ }
+
+ void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
+
+ static bool firstCall = true;
+
+ // Try to allocate large pages
+ mem = aligned_ttmem_alloc_large_pages(allocSize);
+
+ // Suppress info strings on the first call. The first call occurs before 'uci'
+ // is received and in that case this output confuses some GUIs.
+ if (!firstCall)
+ {
+ if (mem)
+ sync_cout << "info string Hash table allocation: Windows large pages used." << sync_endl;
+ else
+ sync_cout << "info string Hash table allocation: Windows large pages not used." << sync_endl;
+ }
+ firstCall = false;
+
+ // Fall back to regular, page aligned, allocation if necessary
+ if (!mem)
+ mem = VirtualAlloc(NULL, allocSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+
+ return mem;
+ }
+
+ #else
+
+ void* aligned_ttmem_alloc(size_t allocSize, void*& mem) {
+
+ constexpr size_t alignment = 64; // assumed cache line size
+ size_t size = allocSize + alignment - 1; // allocate some extra space
+ mem = malloc(size);
+ void* ret = reinterpret_cast<void*>((uintptr_t(mem) + alignment - 1) & ~uintptr_t(alignment - 1));
+ return ret;
+ }
+
+ #endif
+
+
+ /// aligned_ttmem_free() will free the previously allocated ttmem
+
+ #if defined(_WIN64)
+
+ void aligned_ttmem_free(void* mem) {
+
+ if (mem && !VirtualFree(mem, 0, MEM_RELEASE))
+ {
+ DWORD err = GetLastError();
+ std::cerr << "Failed to free transposition table. Error code: 0x" <<
+ std::hex << err << std::dec << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ #else
+
+ void aligned_ttmem_free(void *mem) {
+ free(mem);
+ }
+
+ #endif
+
+
namespace WinProcGroup {
#ifndef _WIN32
for (File f = FILE_A; f <= FILE_H; ++f)
os << " | " << PieceToChar[pos.piece_on(make_square(f, r))];
- os << " |\n +---+---+---+---+---+---+---+---+\n";
+ os << " | " << (1 + r) << "\n +---+---+---+---+---+---+---+---+\n";
}
- os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
+ os << " a b c d e f g h\n"
+ << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase
<< std::setfill('0') << std::setw(16) << pos.key()
<< std::setfill(' ') << std::dec << "\nCheckers: ";
Move cuckooMove[8192];
- /// Position::init() initializes at startup the various arrays used to compute
- /// hash keys.
+ /// Position::init() initializes at startup the various arrays used to compute hash keys
void Position::init() {
Zobrist::enpassant[f] = rng.rand<Key>();
for (int cr = NO_CASTLING; cr <= ANY_CASTLING; ++cr)
- {
- Zobrist::castling[cr] = 0;
- Bitboard b = cr;
- while (b)
- {
- Key k = Zobrist::castling[1ULL << pop_lsb(&b)];
- Zobrist::castling[cr] ^= k ? k : rng.rand<Key>();
- }
- }
+ Zobrist::castling[cr] = rng.rand<Key>();
Zobrist::side = rng.rand<Key>();
Zobrist::noPawns = rng.rand<Key>();
for (Piece pc : Pieces)
for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1)
for (Square s2 = Square(s1 + 1); s2 <= SQ_H8; ++s2)
- if (PseudoAttacks[type_of(pc)][s1] & s2)
+ if ((type_of(pc) != PAWN) && (attacks_bb(type_of(pc), s1, 0) & s2))
{
Move move = make_move(s1, s2);
Key key = Zobrist::psq[pc][s1] ^ Zobrist::psq[pc][s2] ^ Zobrist::side;
4) En passant target square (in algebraic notation). If there's no en passant
target square, this is "-". If a pawn has just made a 2-square move, this
- is the position "behind" the pawn. This is recorded only if there is a pawn
- in position to make an en passant capture, and if there really is a pawn
- that might have advanced two squares.
+ is the position "behind" the pawn. Following X-FEN standard, this is recorded only
+ if there is a pawn in position to make an en passant capture, and if there really
+ is a pawn that might have advanced two squares.
5) Halfmove clock. This is the number of halfmoves since the last pawn advance
or capture. This is used to determine if a draw can be claimed under the
set_castling_right(c, rsq);
}
- // 4. En passant square. Ignore if no pawn capture is possible
+ // 4. En passant square.
+ // Ignore if square is invalid or not on side to move relative rank 6.
+ bool enpassant = false;
+
if ( ((ss >> col) && (col >= 'a' && col <= 'h'))
- && ((ss >> row) && (row == '3' || row == '6')))
+ && ((ss >> row) && (row == (sideToMove == WHITE ? '6' : '3'))))
{
st->epSquare = make_square(File(col - 'a'), Rank(row - '1'));
- if ( !(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))
- || !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove))))
- st->epSquare = SQ_NONE;
+ // En passant square will be considered only if
+ // a) side to move have a pawn threatening epSquare
+ // b) there is an enemy pawn in front of epSquare
+ // c) there is no piece on epSquare or behind epSquare
+ enpassant = pawn_attacks_bb(~sideToMove, st->epSquare) & pieces(sideToMove, PAWN)
+ && (pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))
+ && !(pieces() & (st->epSquare | (st->epSquare + pawn_push(sideToMove))));
}
- else
+
+ if (!enpassant)
st->epSquare = SQ_NONE;
// 5-6. Halfmove clock and fullmove number
thisThread = th;
set_state(st);
- assert(pos_is_ok());
-
return *this;
}
Square rto = relative_square(c, cr & KING_SIDE ? SQ_F1 : SQ_D1);
castlingPath[cr] = (between_bb(rfrom, rto) | between_bb(kfrom, kto) | rto | kto)
- & ~(square_bb(kfrom) | rfrom);
+ & ~(kfrom | rfrom);
}
Square ksq = square<KING>(~sideToMove);
- si->checkSquares[PAWN] = attacks_from<PAWN>(ksq, ~sideToMove);
- si->checkSquares[KNIGHT] = attacks_from<KNIGHT>(ksq);
- si->checkSquares[BISHOP] = attacks_from<BISHOP>(ksq);
- si->checkSquares[ROOK] = attacks_from<ROOK>(ksq);
+ si->checkSquares[PAWN] = pawn_attacks_bb(~sideToMove, ksq);
+ si->checkSquares[KNIGHT] = attacks_bb<KNIGHT>(ksq);
+ si->checkSquares[BISHOP] = attacks_bb<BISHOP>(ksq, pieces());
+ si->checkSquares[ROOK] = attacks_bb<ROOK>(ksq, pieces());
si->checkSquares[QUEEN] = si->checkSquares[BISHOP] | si->checkSquares[ROOK];
si->checkSquares[KING] = 0;
}
Position& Position::set(const string& code, Color c, StateInfo* si) {
- assert(code.length() > 0 && code.length() < 8);
assert(code[0] == 'K');
string sides[] = { code.substr(code.find('K', 1)), // Weak
- code.substr(0, code.find('K', 1)) }; // Strong
+ code.substr(0, std::min(code.find('v'), code.find('K', 1))) }; // Strong
+
+ assert(sides[0].length() > 0 && sides[0].length() < 8);
+ assert(sides[1].length() > 0 && sides[1].length() < 8);
std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower);
pinners = 0;
// Snipers are sliders that attack 's' when a piece and other snipers are removed
- Bitboard snipers = ( (PseudoAttacks[ ROOK][s] & pieces(QUEEN, ROOK))
- | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders;
+ Bitboard snipers = ( (attacks_bb< ROOK>(s) & pieces(QUEEN, ROOK))
+ | (attacks_bb<BISHOP>(s) & pieces(QUEEN, BISHOP))) & sliders;
Bitboard occupancy = pieces() ^ snipers;
while (snipers)
Bitboard Position::attackers_to(Square s, Bitboard occupied) const {
- return (attacks_from<PAWN>(s, BLACK) & pieces(WHITE, PAWN))
- | (attacks_from<PAWN>(s, WHITE) & pieces(BLACK, PAWN))
- | (attacks_from<KNIGHT>(s) & pieces(KNIGHT))
+ return (pawn_attacks_bb(BLACK, s) & pieces(WHITE, PAWN))
+ | (pawn_attacks_bb(WHITE, s) & pieces(BLACK, PAWN))
+ | (attacks_bb<KNIGHT>(s) & pieces(KNIGHT))
| (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN))
| (attacks_bb<BISHOP>(s, occupied) & pieces(BISHOP, QUEEN))
- | (attacks_from<KING>(s) & pieces(KING));
+ | (attacks_bb<KING>(s) & pieces(KING));
}
if ((Rank8BB | Rank1BB) & to)
return false;
- if ( !(attacks_from<PAWN>(from, us) & pieces(~us) & to) // Not a capture
+ if ( !(pawn_attacks_bb(us, from) & pieces(~us) & to) // Not a capture
&& !((from + pawn_push(us) == to) && empty(to)) // Not a single push
&& !( (from + 2 * pawn_push(us) == to) // Not a double push
- && (rank_of(from) == relative_rank(us, RANK_2))
+ && (relative_rank(us, from) == RANK_2)
&& empty(to)
&& empty(to - pawn_push(us))))
return false;
}
- else if (!(attacks_from(type_of(pc), from) & to))
+ else if (!(attacks_bb(type_of(pc), from, pieces()) & to))
return false;
// Evasions generator already takes care to avoid some kind of illegal moves
Square to = to_sq(m);
// Is there a direct check?
- if (st->checkSquares[type_of(piece_on(from))] & to)
+ if (check_squares(type_of(piece_on(from))) & to)
return true;
// Is there a discovered check?
- if ( (st->blockersForKing[~sideToMove] & from)
+ if ( (blockers_for_king(~sideToMove) & from)
&& !aligned(from, to, square<KING>(~sideToMove)))
return true;
case CASTLING:
{
Square kfrom = from;
- Square rfrom = to; // Castling is encoded as 'King captures the rook'
+ Square rfrom = to; // Castling is encoded as 'king captures the rook'
Square kto = relative_square(sideToMove, rfrom > kfrom ? SQ_G1 : SQ_C1);
Square rto = relative_square(sideToMove, rfrom > kfrom ? SQ_F1 : SQ_D1);
- return (PseudoAttacks[ROOK][rto] & square<KING>(~sideToMove))
+ return (attacks_bb<ROOK>(rto) & square<KING>(~sideToMove))
&& (attacks_bb<ROOK>(rto, (pieces() ^ kfrom ^ rfrom) | rto | kto) & square<KING>(~sideToMove));
}
default:
assert(relative_rank(us, to) == RANK_6);
assert(piece_on(to) == NO_PIECE);
assert(piece_on(capsq) == make_piece(them, PAWN));
-
- board[capsq] = NO_PIECE; // Not done by remove_piece()
}
st->pawnKey ^= Zobrist::psq[captured][capsq];
st->nonPawnMaterial[them] -= PieceValue[MG][captured];
// Update board and piece lists
- remove_piece(captured, capsq);
+ remove_piece(capsq);
+
+ if (type_of(m) == ENPASSANT)
+ board[capsq] = NO_PIECE;
// Update material hash key and prefetch access to materialTable
k ^= Zobrist::psq[captured][capsq];
// Update castling rights if needed
if (st->castlingRights && (castlingRightsMask[from] | castlingRightsMask[to]))
{
- int cr = castlingRightsMask[from] | castlingRightsMask[to];
- k ^= Zobrist::castling[st->castlingRights & cr];
- st->castlingRights &= ~cr;
+ k ^= Zobrist::castling[st->castlingRights];
+ st->castlingRights &= ~(castlingRightsMask[from] | castlingRightsMask[to]);
+ k ^= Zobrist::castling[st->castlingRights];
}
// Move the piece. The tricky Chess960 castling is handled earlier
if (type_of(m) != CASTLING)
- move_piece(pc, from, to);
+ move_piece(from, to);
// If the moving piece is a pawn do some special extra work
if (type_of(pc) == PAWN)
{
// Set en-passant square if the moved pawn can be captured
if ( (int(to) ^ int(from)) == 16
- && (attacks_from<PAWN>(to - pawn_push(us), us) & pieces(them, PAWN)))
+ && (pawn_attacks_bb(us, to - pawn_push(us)) & pieces(them, PAWN)))
{
st->epSquare = to - pawn_push(us);
k ^= Zobrist::enpassant[file_of(st->epSquare)];
assert(relative_rank(us, to) == RANK_8);
assert(type_of(promotion) >= KNIGHT && type_of(promotion) <= QUEEN);
- remove_piece(pc, to);
+ remove_piece(to);
put_piece(promotion, to);
// Update hash keys
assert(type_of(pc) == promotion_type(m));
assert(type_of(pc) >= KNIGHT && type_of(pc) <= QUEEN);
- remove_piece(pc, to);
+ remove_piece(to);
pc = make_piece(us, PAWN);
put_piece(pc, to);
}
}
else
{
- move_piece(pc, to, from); // Put the piece back at the source square
+ move_piece(to, from); // Put the piece back at the source square
if (st->capturedPiece)
{
to = relative_square(us, kingSide ? SQ_G1 : SQ_C1);
// Remove both pieces first since squares could overlap in Chess960
- remove_piece(make_piece(us, KING), Do ? from : to);
- remove_piece(make_piece(us, ROOK), Do ? rfrom : rto);
- board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us
+ remove_piece(Do ? from : to);
+ remove_piece(Do ? rfrom : rto);
+ board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do this for us
put_piece(make_piece(us, KING), Do ? to : from);
put_piece(make_piece(us, ROOK), Do ? rto : rfrom);
}
return bool(res);
}
+
/// Position::is_draw() tests whether the position is drawn by 50-move rule
/// or by repetition. It does not detect stalemates.
// Return a draw score if a position repeats once earlier but strictly
// after the root, or repeats twice before or at the root.
- if (st->repetition && st->repetition < ply)
- return true;
-
- return false;
+ return st->repetition && st->repetition < ply;
}
constexpr int TBPIECES = 7; // Max number of supported pieces
enum { BigEndian, LittleEndian };
- enum TBType { KEY, WDL, DTZ }; // Used as template parameter
+ enum TBType { WDL, DTZ }; // Used as template parameter
// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables
enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, Wide = 16, SingleValue = 128 };
inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); }
- inline Square operator^=(Square& s, int i) { return s = Square(int(s) ^ i); }
inline Square operator^(Square s, int i) { return Square(int(s) ^ i); }
const std::string PieceToChar = " PNBRQK pnbrqk";
int MapA1D1D4[SQUARE_NB];
int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB]
-int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements
+int Binomial[7][SQUARE_NB]; // [k][n] k elements from a set of n elements
int LeadPawnIdx[6][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB]
int LeadPawnsSize[6][4]; // [leadPawnsCnt][FILE_A..FILE_D]
// at init time, accessed at probe time.
class TBTables {
- typedef std::tuple<Key, TBTable<WDL>*, TBTable<DTZ>*> Entry;
+ struct Entry
+ {
+ Key key;
+ TBTable<WDL>* wdl;
+ TBTable<DTZ>* dtz;
+
+ template <TBType Type>
+ TBTable<Type>* get() const {
+ return (TBTable<Type>*)(Type == WDL ? (void*)wdl : (void*)dtz);
+ }
+ };
static constexpr int Size = 1 << 12; // 4K table, indexed by key's 12 lsb
static constexpr int Overflow = 1; // Number of elements allowed to map to the last bucket
void insert(Key key, TBTable<WDL>* wdl, TBTable<DTZ>* dtz) {
uint32_t homeBucket = (uint32_t)key & (Size - 1);
- Entry entry = std::make_tuple(key, wdl, dtz);
+ Entry entry{ key, wdl, dtz };
// Ensure last element is empty to avoid overflow when looking up
for (uint32_t bucket = homeBucket; bucket < Size + Overflow - 1; ++bucket) {
- Key otherKey = std::get<KEY>(hashTable[bucket]);
- if (otherKey == key || !std::get<WDL>(hashTable[bucket])) {
+ Key otherKey = hashTable[bucket].key;
+ if (otherKey == key || !hashTable[bucket].get<WDL>()) {
hashTable[bucket] = entry;
return;
}
// insert here and search for a new spot for the other element instead.
uint32_t otherHomeBucket = (uint32_t)otherKey & (Size - 1);
if (otherHomeBucket > homeBucket) {
- swap(entry, hashTable[bucket]);
+ std::swap(entry, hashTable[bucket]);
key = otherKey;
homeBucket = otherHomeBucket;
}
template<TBType Type>
TBTable<Type>* get(Key key) {
for (const Entry* entry = &hashTable[(uint32_t)key & (Size - 1)]; ; ++entry) {
- if (std::get<KEY>(*entry) == key || !std::get<Type>(*entry))
- return std::get<Type>(*entry);
+ if (entry->key == key || !entry->get<Type>())
+ return entry->get<Type>();
}
}
// I(k) = k * d->span + d->span / 2 (1)
// First step is to get the 'k' of the I(k) nearest to our idx, using definition (1)
- uint32_t k = idx / d->span;
+ uint32_t k = uint32_t(idx / d->span);
// Then we read the corresponding SparseIndex[] entry
uint32_t block = number<uint32_t, LittleEndian>(&d->sparseIndex[k].block);
// All the symbols of a given length are consecutive integers (numerical
// sequence property), so we can compute the offset of our symbol of
// length len, stored at the beginning of buf64.
- sym = (buf64 - d->base64[len]) >> (64 - len - d->minSymLen);
+ sym = Sym((buf64 - d->base64[len]) >> (64 - len - d->minSymLen));
// Now add the value of the lowest symbol of length len to get our symbol
sym += number<Sym, LittleEndian>(&d->lowestSym[len]);
std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp));
- tbFile = map_to_queenside(file_of(squares[0]));
+ tbFile = File(edge_distance(file_of(squares[0])));
}
// DTZ tables are one-sided, i.e. they store positions only for white to
// the triangle A1-D1-D4.
if (file_of(squares[0]) > FILE_D)
for (int i = 0; i < size; ++i)
- squares[i] ^= 7; // Horizontal flip: SQ_H1 -> SQ_A1
+ squares[i] = flip_file(squares[i]);
// Encode leading pawns starting with the one with minimum MapPawns[] and
// proceeding in ascending order.
// piece is below RANK_5.
if (rank_of(squares[0]) > RANK_4)
for (int i = 0; i < size; ++i)
- squares[i] ^= SQ_A8; // Vertical flip: SQ_A8 -> SQ_A1
+ squares[i] = flip_rank(squares[i]);
// Look for the first piece of the leading group not on the A1-D4 diagonal
// and ensure it is mapped below the diagonal.
if (!off_A1H8(squares[i]))
continue;
- if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C3
+ if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C1
for (int j = i; j < size; ++j)
squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63);
break;
d->sizeofBlock = 1ULL << *data++;
d->span = 1ULL << *data++;
- d->sparseIndexSize = (tbSize + d->span - 1) / d->span; // Round up
+ d->sparseIndexSize = size_t((tbSize + d->span - 1) / d->span); // Round up
auto padding = number<uint8_t, LittleEndian>(data++);
d->blocksNum = number<uint32_t, LittleEndian>(data); data += sizeof(uint32_t);
d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[]
auto moveList = MoveList<LEGAL>(pos);
size_t totalCount = moveList.size(), moveCount = 0;
- for (const Move& move : moveList)
+ for (const Move move : moveList)
{
if ( !pos.capture(move)
&& (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN))
Binomial[0][0] = 1;
for (int n = 1; n < 64; n++) // Squares
- for (int k = 0; k < 6 && k <= n; ++k) // Pieces
+ for (int k = 0; k < 7 && k <= n; ++k) // Pieces
Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0)
+ (k < n ? Binomial[k ][n - 1] : 0);
if (leadPawnsCnt == 1)
{
MapPawns[sq] = availableSquares--;
- MapPawns[sq ^ 7] = availableSquares--; // Horizontal flip
+ MapPawns[flip_file(sq)] = availableSquares--;
}
LeadPawnIdx[leadPawnsCnt][sq] = idx;
idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]];
LeadPawnsSize[leadPawnsCnt][f] = idx;
}
- // Add entries in TB tables if the corresponding ".rtbw" file exsists
+ // Add entries in TB tables if the corresponding ".rtbw" file exists
for (PieceType p1 = PAWN; p1 < KING; ++p1) {
TBTables.add({KING, p1, KING});
StateInfo st;
int minDTZ = 0xFFFF;
- for (const Move& move : MoveList<LEGAL>(pos))
+ for (const Move move : MoveList<LEGAL>(pos))
{
bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN;
#include "thread.h"
#include "tt.h"
#include "uci.h"
+#include "hashprobe.h"
#include "syzygy/tbprobe.h"
using std::string;
UCI::OptionsMap Options; // Global object
+std::unique_ptr<HashProbeThread> hash_probe_thread;
namespace UCI {
/// 'On change' actions, triggered by an option's value change
void on_clear_hash(const Option&) { Search::clear(); }
- void on_hash_size(const Option& o) { TT.resize(o); }
+ void on_hash_size(const Option& o) { TT.resize(size_t(o)); }
void on_logger(const Option& o) { start_logger(o); }
- void on_threads(const Option& o) { Threads.set(o); }
+ void on_threads(const Option& o) { Threads.set(size_t(o)); }
void on_tb_path(const Option& o) { Tablebases::init(o); }
-
+void on_rpc_server_address(const Option& o) {
+ if (hash_probe_thread) {
+ hash_probe_thread->Shutdown();
+ }
+ std::string addr = o;
+ hash_probe_thread.reset(new HashProbeThread(addr));
+}
/// Our case insensitive less() function as required by UCI protocol
bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const {
}
- /// init() initializes the UCI options to their hard-coded default values
+ /// UCI::init() initializes the UCI options to their hard-coded default values
void init(OptionsMap& o) {
- // at most 2^32 clusters.
- constexpr int MaxHashMB = Is64Bit ? 131072 : 2048;
+ constexpr int MaxHashMB = Is64Bit ? 33554432 : 2048;
o["Debug Log File"] << Option("", on_logger);
o["Contempt"] << Option(24, -100, 100);
o["Ponder"] << Option(false);
o["MultiPV"] << Option(1, 1, 500);
o["Skill Level"] << Option(20, 0, 20);
- o["Move Overhead"] << Option(30, 0, 5000);
- o["Minimum Thinking Time"] << Option(20, 0, 5000);
- o["Slow Mover"] << Option(84, 10, 1000);
+ o["Move Overhead"] << Option(10, 0, 5000);
+ o["Slow Mover"] << Option(100, 10, 1000);
o["nodestime"] << Option(0, 0, 10000);
o["UCI_Chess960"] << Option(false);
o["UCI_AnalyseMode"] << Option(false);
o["UCI_LimitStrength"] << Option(false);
o["UCI_Elo"] << Option(1350, 1350, 2850);
+ o["UCI_ShowWDL"] << Option(false);
o["SyzygyPath"] << Option("<empty>", on_tb_path);
o["SyzygyProbeDepth"] << Option(1, 1, 100);
o["Syzygy50MoveRule"] << Option(true);
o["SyzygyProbeLimit"] << Option(7, 0, 7);
+ o["RPCServerAddress"] << Option("<empty>", on_rpc_server_address);
}