X-Git-Url: https://git.sesse.net/?p=stockfish;a=blobdiff_plain;f=src%2Fmisc.cpp;h=832a9ac18f93465baca9eb9c54de107453c71916;hp=11833440e0c229c85bce7aefeae411f4d34d89f6;hb=f2e94d6d35c14b274ed29fb67475acea5adc285f;hpb=d01b66ae8f4090c8e12e72423ff9165e6bc3a2f8 diff --git a/src/misc.cpp b/src/misc.cpp index 11833440..832a9ac1 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -1,8 +1,6 @@ /* Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,6 +21,11 @@ #undef _WIN32_WINNT #define _WIN32_WINNT 0x0601 // Force to include needed API prototypes #endif + +#ifndef NOMINMAX +#define NOMINMAX +#endif + #include // The needed Windows API for processor groups could be missed from old Windows // versions, so instead of calling them directly (forcing the linker to resolve @@ -41,6 +44,17 @@ typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); #include #include #include +#include + +#if defined(__linux__) && !defined(__ANDROID__) +#include +#include +#endif + +#if defined(__APPLE__) || defined(__ANDROID__) || defined(__OpenBSD__) || (defined(__GLIBCXX__) && !defined(_GLIBCXX_HAVE_ALIGNED_ALLOC) && !defined(_WIN32)) +#define POSIXALIGNEDALLOC +#include +#endif #include "misc.h" #include "thread.h" @@ -63,10 +77,10 @@ struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {} - int sync() { return logBuf->pubsync(), buf->pubsync(); } - int overflow(int c) { return log(buf->sputc((char)c), "<< "); } - int underflow() { return buf->sgetc(); } - int uflow() { return log(buf->sbumpc(), ">> "); } + int sync() override { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } + int underflow() override { return buf->sgetc(); } + int uflow() override { return log(buf->sbumpc(), ">> "); } streambuf *buf, *logBuf; @@ -97,6 +111,13 @@ public: if (!fname.empty() && !l.file.is_open()) { l.file.open(fname, ifstream::out); + + if (!l.file.is_open()) + { + cerr << "Unable to open debug log file " << fname << endl; + exit(EXIT_FAILURE); + } + cin.rdbuf(&l.in); cout.rdbuf(&l.out); } @@ -111,6 +132,7 @@ public: } // namespace + /// engine_info() returns the full name of the current Stockfish version. This /// will be either "Stockfish DD-MM-YY" (where DD-MM-YY is the date when /// the program was compiled) or "Stockfish ", depending on whether @@ -128,19 +150,122 @@ const string engine_info(bool to_uci) { { date >> month >> day >> year; ss << setw(2) << day << setw(2) << (1 + months.find(month) / 4) << year.substr(2); + ss << "-asn"; } - ss << (Is64Bit ? " 64" : "") - << (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : "")) - << (to_uci ? "\nid author ": " by ") - << "T. Romstad, M. Costalba, J. Kiiski, G. Linscott"; + ss << (to_uci ? "\nid author ": " by ") + << "the Stockfish developers (see AUTHORS file)"; return ss.str(); } +/// compiler_info() returns a string trying to describe the compiler we use + +const std::string compiler_info() { + + #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: +/// +/// __GNUC__ Compiler is gcc, Clang or Intel on Linux +/// __INTEL_COMPILER Compiler is Intel +/// _MSC_VER Compiler is MSVC or Intel on Windows +/// _WIN32 Building on Windows (any) +/// _WIN64 Building on Windows 64 bit + + std::string compiler = "\nCompiled by "; + + #ifdef __clang__ + compiler += "clang++ "; + 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 += ")"; + #elif _MSC_VER + compiler += "MSVC "; + compiler += "(version "; + compiler += stringify(_MSC_FULL_VER) "." stringify(_MSC_BUILD); + compiler += ")"; + #elif __GNUC__ + compiler += "g++ (GNUC) "; + compiler += make_version_string(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); + #else + compiler += "Unknown compiler "; + compiler += "(unknown version)"; + #endif + + #if defined(__APPLE__) + compiler += " on Apple"; + #elif defined(__CYGWIN__) + compiler += " on Cygwin"; + #elif defined(__MINGW64__) + compiler += " on MinGW64"; + #elif defined(__MINGW32__) + compiler += " on MinGW32"; + #elif defined(__ANDROID__) + compiler += " on Android"; + #elif defined(__linux__) + compiler += " on Linux"; + #elif defined(_WIN64) + compiler += " on Microsoft Windows 64-bit"; + #elif defined(_WIN32) + compiler += " on Microsoft Windows 32-bit"; + #else + compiler += " on unknown system"; + #endif + + compiler += "\nCompilation settings include: "; + compiler += (Is64Bit ? " 64bit" : " 32bit"); + #if defined(USE_VNNI) + compiler += " VNNI"; + #endif + #if defined(USE_AVX512) + compiler += " AVX512"; + #endif + compiler += (HasPext ? " BMI2" : ""); + #if defined(USE_AVX2) + compiler += " AVX2"; + #endif + #if defined(USE_SSE41) + compiler += " SSE41"; + #endif + #if defined(USE_SSSE3) + compiler += " SSSE3"; + #endif + #if defined(USE_SSE2) + compiler += " SSE2"; + #endif + compiler += (HasPopCnt ? " POPCNT" : ""); + #if defined(USE_MMX) + compiler += " MMX"; + #endif + #if defined(USE_NEON) + compiler += " NEON"; + #endif + + #if !defined(NDEBUG) + compiler += " DEBUG"; + #endif + + compiler += "\n__VERSION__ macro expands to: "; + #ifdef __VERSION__ + compiler += __VERSION__; + #else + compiler += "(undefined macro)"; + #endif + compiler += "\n"; + + return compiler; +} + + /// Debug functions used mainly to collect run-time statistics -static int64_t hits[2], means[2]; +static std::atomic hits[2], means[2]; void dbg_hit_on(bool b) { ++hits[0]; if (b) ++hits[1]; } void dbg_hit_on(bool c, bool b) { if (c) dbg_hit_on(b); } @@ -163,7 +288,7 @@ void dbg_print() { std::ostream& operator<<(std::ostream& os, SyncCout sc) { - static Mutex m; + static std::mutex m; if (sc == IO_LOCK) m.lock(); @@ -205,12 +330,141 @@ void prefetch(void* addr) { #endif -void prefetch2(void* addr) { - prefetch(addr); - prefetch((uint8_t*)addr + 64); +/// std_aligned_alloc() is our wrapper for systems where the c++17 implementation +/// does not guarantee the availability of aligned_alloc(). Memory allocated with +/// std_aligned_alloc() must be freed with std_aligned_free(). + +void* std_aligned_alloc(size_t alignment, size_t size) { + +#if defined(POSIXALIGNEDALLOC) + void *mem; + return posix_memalign(&mem, alignment, size) ? nullptr : mem; +#elif defined(_WIN32) + return _mm_malloc(size, alignment); +#else + return std::aligned_alloc(alignment, size); +#endif +} + +void std_aligned_free(void* ptr) { + +#if defined(POSIXALIGNEDALLOC) + free(ptr); +#elif defined(_WIN32) + _mm_free(ptr); +#else + free(ptr); +#endif +} + +/// aligned_large_pages_alloc() will return suitably aligned memory, if possible using large pages. + +#if defined(_WIN32) + +static void* aligned_large_pages_alloc_win(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_large_pages_alloc(size_t allocSize) { + + // Try to allocate large pages + void* mem = aligned_large_pages_alloc_win(allocSize); + + // 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_large_pages_alloc(size_t allocSize) { + +#if defined(__linux__) + constexpr size_t alignment = 2 * 1024 * 1024; // assumed 2MB page size +#else + constexpr size_t alignment = 4096; // assumed small page size +#endif + + // round up to multiples of alignment + size_t size = ((allocSize + alignment - 1) / alignment) * alignment; + void *mem = std_aligned_alloc(alignment, size); +#if defined(MADV_HUGEPAGE) + madvise(mem, size, MADV_HUGEPAGE); +#endif + return mem; +} + +#endif + + +/// aligned_large_pages_free() will free the previously allocated ttmem + +#if defined(_WIN32) + +void aligned_large_pages_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_large_pages_free(void *mem) { + std_aligned_free(mem); } +#endif + + namespace WinProcGroup { #ifndef _WIN32 @@ -219,11 +473,11 @@ void bindThisThread(size_t) {} #else -/// get_group() retrieves logical processor information using Windows specific +/// best_group() retrieves logical processor information using Windows specific /// API and returns the best group id for the thread with index idx. Original /// code from Texel by Peter Österlund. -int get_group(size_t idx) { +int best_group(size_t idx) { int threads = 0; int nodes = 0; @@ -233,7 +487,7 @@ int get_group(size_t idx) { // Early exit if the needed API is not available at runtime HMODULE k32 = GetModuleHandle("Kernel32.dll"); - auto fun1 = (fun1_t)GetProcAddress(k32, "GetLogicalProcessorInformationEx"); + auto fun1 = (fun1_t)(void(*)())GetProcAddress(k32, "GetLogicalProcessorInformationEx"); if (!fun1) return -1; @@ -252,7 +506,7 @@ int get_group(size_t idx) { return -1; } - while (ptr->Size > 0 && byteOffset + ptr->Size <= returnLength) + while (byteOffset < returnLength) { if (ptr->Relationship == RelationNumaNode) nodes++; @@ -263,6 +517,7 @@ int get_group(size_t idx) { threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; } + assert(ptr->Size); byteOffset += ptr->Size; ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); } @@ -293,24 +548,16 @@ int get_group(size_t idx) { void bindThisThread(size_t idx) { - // If OS already scheduled us on a different group than 0 then don't overwrite - // the choice, eventually we are one of many one-threaded processes running on - // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case all this - // NUMA machinery is not needed. - if (Threads.size() < 8) - return; - // Use only local variables to be thread-safe - int group = get_group(idx); + int group = best_group(idx); if (group == -1) return; // Early exit if the needed API are not available at runtime HMODULE k32 = GetModuleHandle("Kernel32.dll"); - auto fun2 = (fun2_t)GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); - auto fun3 = (fun3_t)GetProcAddress(k32, "SetThreadGroupAffinity"); + auto fun2 = (fun2_t)(void(*)())GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); + auto fun3 = (fun3_t)(void(*)())GetProcAddress(k32, "SetThreadGroupAffinity"); if (!fun2 || !fun3) return; @@ -323,3 +570,60 @@ void bindThisThread(size_t idx) { #endif } // namespace WinProcGroup + +#ifdef _WIN32 +#include +#define GETCWD _getcwd +#else +#include +#define GETCWD getcwd +#endif + +namespace CommandLine { + +string argv0; // path+name of the executable binary, as given by argv[0] +string binaryDirectory; // path of the executable directory +string workingDirectory; // path of the working directory + +void init(int argc, char* argv[]) { + (void)argc; + string pathSeparator; + + // extract the path+name of the executable binary + argv0 = argv[0]; + +#ifdef _WIN32 + pathSeparator = "\\"; + #ifdef _MSC_VER + // Under windows argv[0] may not have the extension. Also _get_pgmptr() had + // issues in some windows 10 versions, so check returned values carefully. + char* pgmptr = nullptr; + if (!_get_pgmptr(&pgmptr) && pgmptr != nullptr && *pgmptr) + argv0 = pgmptr; + #endif +#else + pathSeparator = "/"; +#endif + + // extract the working directory + workingDirectory = ""; + char buff[40000]; + char* cwd = GETCWD(buff, 40000); + if (cwd) + workingDirectory = cwd; + + // extract the binary directory path from argv0 + binaryDirectory = argv0; + size_t pos = binaryDirectory.find_last_of("\\/"); + if (pos == std::string::npos) + binaryDirectory = "." + pathSeparator; + else + binaryDirectory.resize(pos + 1); + + // pattern replacement: "./" at the start of path is replaced by the working directory + if (binaryDirectory.find("." + pathSeparator) == 0) + binaryDirectory.replace(0, 1, workingDirectory); +} + + +} // namespace CommandLine