From 04b1f61b3782c5d5095c6c3d1891631e99a6afef Mon Sep 17 00:00:00 2001 From: ab-chesspad Date: Mon, 5 Jun 2023 14:27:43 -0700 Subject: [PATCH 1/4] Create the entry point to execute any SF command for the future shared library. --- src/main.cpp | 59 +++++++++++++++++++++++++++++++++++++++++++++------- src/uci.cpp | 44 +++++++++++++++++---------------------- src/uci.h | 8 +++++-- 3 files changed, 76 insertions(+), 35 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c40e0fa3492..415d56e8e2e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,6 +17,11 @@ */ #include +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif #include "bitboard.h" #include "endgame.h" @@ -28,13 +33,15 @@ #include "tt.h" #include "uci.h" -using namespace Stockfish; - -int main(int argc, char* argv[]) { +int sf_init(); +#ifndef shared_lib +void input_reader(); +#endif - std::cout << engine_info() << std::endl; +using namespace Stockfish; - CommandLine::init(argc, argv); +int sf_init() { + sync_cout << engine_info() << sync_endl; UCI::init(Options); Tune::init(); PSQT::init(); @@ -45,9 +52,45 @@ int main(int argc, char* argv[]) { Threads.set(size_t(Options["Threads"])); Search::clear(); // After threads are up Eval::NNUE::init(); + UCI::init_pos(); + return 0; +} - UCI::loop(argc, argv); +#ifndef shared_lib +/// input_reader() waits for a command from stdin and invokes UCI::execute() +/// Also intercepts EOF from stdin to ensure gracefully exiting if the +/// GUI dies unexpectedly. +void input_reader() { + std::string cmd; + while (getline(std::cin, cmd)) { + UCI::execute(cmd); + if (cmd == "quit") + break; + } +} - Threads.set(0); - return 0; +/// When SF is called with some command line arguments, e.g. to +/// run 'bench', once the command is executed the program stops. +int main(int argc, char* argv[]) { + + CommandLine::init(argc, argv); + int res = sf_init(); + + if (argc > 1) { + std::string cmd; + for (int i = 1; i < argc; ++i) + cmd += std::string(argv[i]) + " "; + UCI::execute(cmd); +#if defined(__MINGW32__) || defined(__MINGW64__) + Sleep(1); +#else + sleep(1); +#endif + UCI::execute("quit"); + } else { + std::thread input_reader_thread(input_reader); + input_reader_thread.join(); + } + return res; } +#endif diff --git a/src/uci.cpp b/src/uci.cpp index 523d551e0c0..72cdfb6b89a 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -225,36 +225,30 @@ namespace { } // namespace +static StateListPtr states(new std::deque(1)); +static Position pos; -/// UCI::loop() waits for a command from the stdin, parses it and then calls the appropriate -/// function. It also intercepts an end-of-file (EOF) indication from the stdin to ensure a -/// graceful exit if the GUI dies unexpectedly. When called with some command-line arguments, -/// like running 'bench', the function returns immediately after the command is executed. -/// In addition to the UCI ones, some additional debug commands are also supported. - -void UCI::loop(int argc, char* argv[]) { - - Position pos; - string token, cmd; - StateListPtr states(new std::deque(1)); - - pos.set(StartFEN, false, &states->back(), Threads.main()); - - for (int i = 1; i < argc; ++i) - cmd += std::string(argv[i]) + " "; +void UCI::init_pos() { + pos.set(StartFEN, false, &states->back(), Threads.main()); +} - do { - if (argc == 1 && !getline(cin, cmd)) // Wait for an input or an end-of-file (EOF) indication - cmd = "quit"; +/// UCI::execute parses the command and calls the appropriate function. +/// In addition to the UCI ones, also some additional debug commands are supported. +void UCI::execute(std::string cmd) { + { // to minimize diff, in the future it will be indented back anyway + string token; istringstream is(cmd); token.clear(); // Avoid a stale if getline() returns nothing or a blank line is >> skipws >> token; - if ( token == "quit" - || token == "stop") + if (token == "quit") { + Threads.stop = true; + Threads.set(0); + } else if (token == "stop") { Threads.stop = true; + } // The GUI sends 'ponderhit' to tell that the user has played the expected move. // So, 'ponderhit' is sent if pondering was done on the same move that the user @@ -299,7 +293,7 @@ void UCI::loop(int argc, char* argv[]) { else if (!token.empty() && token[0] != '#') sync_cout << "Unknown command: '" << cmd << "'. Type help for more information." << sync_endl; - } while (token != "quit" && argc == 1); // The command-line arguments are one-shot + } } @@ -383,13 +377,13 @@ string UCI::move(Move m, bool chess960) { /// UCI::to_move() converts a string representing a move in coordinate notation /// (g1f3, a7a8q) to the corresponding legal Move, if any. -Move UCI::to_move(const Position& pos, string& str) { +Move UCI::to_move(const Position& _pos, string& str) { if (str.length() == 5) str[4] = char(tolower(str[4])); // The promotion piece character must be lowercased - for (const auto& m : MoveList(pos)) - if (str == UCI::move(m, pos.is_chess960())) + for (const auto& m : MoveList(_pos)) + if (str == UCI::move(m, _pos.is_chess960())) return m; return MOVE_NONE; diff --git a/src/uci.h b/src/uci.h index 680d2d2cc8c..55ac6e151b9 100644 --- a/src/uci.h +++ b/src/uci.h @@ -24,6 +24,9 @@ #include "types.h" +int sf_init(); +void unblock_readers(); + namespace Stockfish { class Position; @@ -75,13 +78,14 @@ class Option { }; void init(OptionsMap&); -void loop(int argc, char* argv[]); +void init_pos(); +void execute(std::string cmd); std::string value(Value v); std::string square(Square s); std::string move(Move m, bool chess960); std::string pv(const Position& pos, Depth depth); std::string wdl(Value v, int ply); -Move to_move(const Position& pos, std::string& str); +Move to_move(const Position& _pos, std::string& str); } // namespace UCI From e3fc0e0a7e0bc176be6e74933857ba924a5ef3a7 Mon Sep 17 00:00:00 2001 From: ab-chesspad Date: Thu, 29 Jun 2023 22:22:13 -0400 Subject: [PATCH 2/4] To make SF testing easier I added a new "wait " command in debug mode. This will allow using a "script" as SF input, e.g. ./stockfish <../test and something like wait 5 gives SF a chance to execute previous command(s). Also '#' at the beginning is considered a comment and ignored. --- src/main.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 415d56e8e2e..5aca5b81d84 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -63,6 +63,16 @@ int sf_init() { void input_reader() { std::string cmd; while (getline(std::cin, cmd)) { +#ifndef NDEBUG + if (cmd.substr(0, 1) == "#") { + continue; + } + if (cmd.substr(0, 5) == "wait ") { + int time = std::stoi(cmd.substr(5)); + sleep(time); + continue; + } +#endif UCI::execute(cmd); if (cmd == "quit") break; From 5696afe423b52b82e9b92ea704441857122de6f8 Mon Sep 17 00:00:00 2001 From: ab-chesspad Date: Fri, 30 Jun 2023 08:22:28 -0400 Subject: [PATCH 3/4] compatibility with MINGW --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 5aca5b81d84..fc3f84af42e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -69,7 +69,11 @@ void input_reader() { } if (cmd.substr(0, 5) == "wait ") { int time = std::stoi(cmd.substr(5)); +#if defined(__MINGW32__) || defined(__MINGW64__) + Sleep(time); +#else sleep(time); +#endif continue; } #endif From ab6576ba8889407e08935db9ee214dca410577ed Mon Sep 17 00:00:00 2001 From: ab-chesspad Date: Wed, 5 Jul 2023 14:31:29 -0400 Subject: [PATCH 4/4] wrap std::cout and std::cerr with macros sync_cout and sync_cerr for easier future transition to string-based output --- src/benchmark.cpp | 3 ++- src/misc.cpp | 26 ++++++++++++++------------ src/misc.h | 1 + src/search.cpp | 7 ++++--- src/syzygy/tbprobe.cpp | 23 +++++++++++++++-------- src/tt.cpp | 7 +++++-- src/tune.cpp | 4 ++-- src/uci.cpp | 10 +++++----- 8 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/benchmark.cpp b/src/benchmark.cpp index a1ad055057b..72ef68f217e 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -138,7 +138,8 @@ vector setup_bench(const Position& current, istream& is) { if (!file.is_open()) { - cerr << "Unable to open file " << fenFile << endl; + std::string msg = "Unable to open file " + fenFile; + sync_cerr << msg << sync_endl; exit(EXIT_FAILURE); } diff --git a/src/misc.cpp b/src/misc.cpp index f1554060d5e..e97f314079f 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -129,7 +129,7 @@ class Logger { if (!l.file.is_open()) { - cerr << "Unable to open debug log file " << fname << endl; + sync_cerr << "Unable to open debug log file " << fname << sync_endl; exit(EXIT_FAILURE); } @@ -357,26 +357,26 @@ void dbg_print() { for (int i = 0; i < MaxDebugSlots; ++i) if ((n = hit[i][0])) - std::cerr << "Hit #" << i + sync_cerr << "Hit #" << i << ": Total " << n << " Hits " << hit[i][1] << " Hit Rate (%) " << 100.0 * E(hit[i][1]) - << std::endl; + << sync_endl; for (int i = 0; i < MaxDebugSlots; ++i) if ((n = mean[i][0])) { - std::cerr << "Mean #" << i + sync_cerr << "Mean #" << i << ": Total " << n << " Mean " << E(mean[i][1]) - << std::endl; + << sync_endl; } for (int i = 0; i < MaxDebugSlots; ++i) if ((n = stdev[i][0])) { double r = sqrtl(E(stdev[i][2]) - sqr(E(stdev[i][1]))); - std::cerr << "Stdev #" << i + sync_cerr << "Stdev #" << i << ": Total " << n << " Stdev " << r - << std::endl; + << sync_endl; } for (int i = 0; i < MaxDebugSlots; ++i) @@ -385,9 +385,9 @@ void dbg_print() { double r = (E(correl[i][5]) - E(correl[i][1]) * E(correl[i][3])) / ( sqrtl(E(correl[i][2]) - sqr(E(correl[i][1]))) * sqrtl(E(correl[i][4]) - sqr(E(correl[i][3])))); - std::cerr << "Correl. #" << i + sync_cerr << "Correl. #" << i << ": Total " << n << " Coefficient " << r - << std::endl; + << sync_endl; } } @@ -589,9 +589,11 @@ void aligned_large_pages_free(void* mem) { if (mem && !VirtualFree(mem, 0, MEM_RELEASE)) { DWORD err = GetLastError(); - std::cerr << "Failed to free large page memory. Error code: 0x" - << std::hex << err - << std::dec << std::endl; + std::stringstream stream; + stream << "Failed to free large page memory. Error code: 0x" + << std::hex << err + << std::dec; + sync_cerr << stream.str() << sync_endl; exit(EXIT_FAILURE); } } diff --git a/src/misc.h b/src/misc.h index 69d470c22f8..2070a7e519c 100644 --- a/src/misc.h +++ b/src/misc.h @@ -68,6 +68,7 @@ enum SyncCout { IO_LOCK, IO_UNLOCK }; std::ostream& operator<<(std::ostream&, SyncCout); #define sync_cout std::cout << IO_LOCK +#define sync_cerr std::cerr << IO_LOCK #define sync_endl std::endl << IO_UNLOCK diff --git a/src/search.cpp b/src/search.cpp index 740ad71efee..f5a54e8fb52 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -247,12 +247,13 @@ void MainThread::search() { if (bestThread != this) sync_cout << UCI::pv(bestThread->rootPos, bestThread->completedDepth) << sync_endl; - sync_cout << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); + std::stringstream stream; + stream << "bestmove " << UCI::move(bestThread->rootMoves[0].pv[0], rootPos.is_chess960()); if (bestThread->rootMoves[0].pv.size() > 1 || bestThread->rootMoves[0].extract_ponder_from_tt(rootPos)) - std::cout << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); + stream << " ponder " << UCI::move(bestThread->rootMoves[0].pv[1], rootPos.is_chess960()); - std::cout << sync_endl; + sync_cout << stream.str() << sync_endl; } diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 9cb0bfdbc0f..07cec84666e 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -216,7 +216,8 @@ class TBFile : public std::ifstream { if (statbuf.st_size % 64 != 16) { - std::cerr << "Corrupt tablebase file " << fname << std::endl; + std::string msg = "Corrupt tablebase file " + fname; + sync_cerr << msg << sync_endl; exit(EXIT_FAILURE); } @@ -229,7 +230,8 @@ class TBFile : public std::ifstream { if (*baseAddress == MAP_FAILED) { - std::cerr << "Could not mmap() " << fname << std::endl; + std::string msg = "Could not mmap() " + fname; + sync_cerr << msg << sync_endl; exit(EXIT_FAILURE); } #else @@ -245,7 +247,8 @@ class TBFile : public std::ifstream { if (size_low % 64 != 16) { - std::cerr << "Corrupt tablebase file " << fname << std::endl; + std::string msg = "Corrupt tablebase file " + fname; + sync_cerr << msg << sync_endl; exit(EXIT_FAILURE); } @@ -254,7 +257,8 @@ class TBFile : public std::ifstream { if (!mmap) { - std::cerr << "CreateFileMapping() failed" << std::endl; + std::string msg = "CreateFileMapping() failed"; + sync_cerr << msg << sync_endl; exit(EXIT_FAILURE); } @@ -263,8 +267,10 @@ class TBFile : public std::ifstream { if (!*baseAddress) { - std::cerr << "MapViewOfFile() failed, name = " << fname - << ", error = " << GetLastError() << std::endl; + std::stringstream stream; + stream << "MapViewOfFile() failed, name = " << fname + << ", error = " << GetLastError(); + sync_cerr << stream.str() << sync_endl; exit(EXIT_FAILURE); } #endif @@ -275,7 +281,7 @@ class TBFile : public std::ifstream { if (memcmp(data, Magics[type == WDL], 4)) { - std::cerr << "Corrupted table in file " << fname << std::endl; + sync_cerr << "Corrupted table in file " << fname << sync_endl; unmap(*baseAddress, *mapping); return *baseAddress = nullptr, nullptr; } @@ -444,7 +450,8 @@ class TBTables { homeBucket = otherHomeBucket; } } - std::cerr << "TB hash table size too low!" << std::endl; + std::string msg = "TB hash table size too low!"; + sync_cerr << msg << sync_endl; exit(EXIT_FAILURE); } diff --git a/src/tt.cpp b/src/tt.cpp index 3339c993c41..9942788ca47 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -18,6 +18,7 @@ #include // For std::memset #include +#include #include #include "bitboard.h" @@ -71,8 +72,10 @@ void TranspositionTable::resize(size_t mbSize) { table = static_cast(aligned_large_pages_alloc(clusterCount * sizeof(Cluster))); if (!table) { - std::cerr << "Failed to allocate " << mbSize - << "MB for transposition table." << std::endl; + std::stringstream stream; + stream << "Failed to allocate " << mbSize + << "MB for transposition table."; + sync_cerr << stream.str() << sync_endl; exit(EXIT_FAILURE); } diff --git a/src/tune.cpp b/src/tune.cpp index 41f6664d9ca..5265965220a 100644 --- a/src/tune.cpp +++ b/src/tune.cpp @@ -70,12 +70,12 @@ static void make_option(const string& n, int v, const SetRange& r) { LastOption = &Options[n]; // Print formatted parameters, ready to be copy-pasted in Fishtest - std::cout << n << "," + sync_cout << n << "," << v << "," << r(v).first << "," << r(v).second << "," << (r(v).second - r(v).first) / 20.0 << "," << "0.0020" - << std::endl; + << sync_endl; } template<> void Tune::Entry::init_option() { make_option(name, value, range); } diff --git a/src/uci.cpp b/src/uci.cpp index 8522c7e82a6..52e221e4fe1 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -172,7 +172,7 @@ namespace { if (token == "go" || token == "eval") { - cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << endl; + sync_cerr << "\nPosition: " << cnt++ << '/' << num << " (" << pos.fen() << ")" << sync_endl; if (token == "go") { go(pos, is, states); @@ -191,10 +191,10 @@ namespace { dbg_print(); - cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed - << "\nNodes searched : " << nodes - << "\nNodes/second : " << 1000 * nodes / elapsed << endl; + sync_cerr << "\n===========================" + << "\nTotal time (ms) : " << elapsed + << "\nNodes searched : " << nodes + << "\nNodes/second : " << 1000 * nodes / elapsed << sync_endl; } // The win rate model returns the probability of winning (in per mille units) given an