diff --git a/src/Makefile b/src/Makefile index cdf0ee1a8..4aaf46ce1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -878,7 +878,7 @@ ifeq ($(lsx),yes) endif ifeq ($(arch),wasm32) - CXXFLAGS += -pthread -msimd128 -DUSE_POPCNT -DNNUE_EMBEDDING_OFF + CXXFLAGS += -pthread -msimd128 -DUSE_POPCNT -DNNUE_EMBEDDING_OFF -DUSE_SLOPPY_ATOMICS LDFLAGS += -pthread -sINITIAL_MEMORY=64MB -sALLOW_MEMORY_GROWTH -sSTACK_SIZE=3MB ifeq ($(relaxedsimd),yes) CXXFLAGS += -mrelaxed-simd diff --git a/src/history.h b/src/history.h index 4e0dbeb03..09041d4ad 100644 --- a/src/history.h +++ b/src/history.h @@ -57,28 +57,12 @@ struct StatsEntry { static_assert(std::is_arithmetic_v, "Not an arithmetic type"); private: - static constexpr bool Atomic = -#ifdef __wasm__ - false; // don't use atomics on WASM as they are always seq_cst -#else - Shared; -#endif - std::conditional_t, T> entry; + std::conditional_t, T> entry; public: - void operator=(const T& v) { - if constexpr (Atomic) - entry.store(v, std::memory_order_relaxed); - else - entry = v; - } + void operator=(const T& v) { entry = v; } - operator T() const { - if constexpr (Atomic) - return entry.load(std::memory_order_relaxed); - else - return entry; - } + operator T() const { return entry; } void operator<<(int bonus) { // Make sure that bonus is in range [-D, D] diff --git a/src/misc.h b/src/misc.h index 219e75ab8..0a6a846c2 100644 --- a/src/misc.h +++ b/src/misc.h @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -339,6 +340,102 @@ class MultiArray { constexpr void swap(MultiArray& other) noexcept { data_.swap(other.data_); } }; +// Wrapper around std::atomic which uses relaxed accesses or plain +// accesses, depending on the config. Intended use is e.g. wasm where +// the overhead of atomic instructions can be significant, and we only +// require non-tearing for the updates, while ensuring we use relaxed +// accesses otherwise. +template +class RelaxedAtomic { + static constexpr bool UseAtomic = +#ifdef USE_SLOPPY_ATOMICS + !std::atomic::is_always_lock_free || sizeof(T) > sizeof(size_t); +#else + true; +#endif + + public: + RelaxedAtomic() = default; + RelaxedAtomic(T val) : + inner(val) {} + RelaxedAtomic(const RelaxedAtomic& a) : + inner(static_cast(a)) {} + + T operator=(T val) { + if constexpr (UseAtomic) + inner.store(val, std::memory_order_relaxed); + else + inner = val; + return val; + } + + RelaxedAtomic& operator=(const RelaxedAtomic& a) { + this->store(static_cast(a), std::memory_order_relaxed); + return *this; + } + + operator T() const { + if constexpr (UseAtomic) + return inner.load(std::memory_order_relaxed); + else + return inner; + } + + RelaxedAtomic& operator+=(int val) { + T res = this->load(std::memory_order_relaxed) + val; + this->store(res, std::memory_order_relaxed); + return *this; + } + + RelaxedAtomic& operator++() { + T res = this->load(std::memory_order_relaxed) + 1; + this->store(res, std::memory_order_relaxed); + return *this; + } + + RelaxedAtomic& operator--() { + T res = this->load(std::memory_order_relaxed) - 1; + this->store(res, std::memory_order_relaxed); + return *this; + } + + T operator++(int) { + T val = this->load(std::memory_order_relaxed); + this->store(val + 1, std::memory_order_relaxed); + return val; + } + + T operator--(int) { + T val = this->load(std::memory_order_relaxed); + this->store(val - 1, std::memory_order_relaxed); + return val; + } + + RelaxedAtomic& operator-=(int val) { + T res = this->load(std::memory_order_relaxed) - val; + this->store(res, std::memory_order_relaxed); + return *this; + } + + T load(std::memory_order order) const { + assert(order == std::memory_order_relaxed); + if constexpr (UseAtomic) + return inner.load(order); + else + return inner; + } + + void store(T val, std::memory_order order) { + assert(order == std::memory_order_relaxed); + if constexpr (UseAtomic) + inner.store(val, order); + else + inner = val; + } + + private: + std::conditional_t, T> inner; +}; // xorshift64star Pseudo-Random Number Generator // This class is based on original code written and dedicated diff --git a/src/search.cpp b/src/search.cpp index aa48c21d3..6d05d2c8a 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -623,8 +623,7 @@ void Search::Worker::do_move(Position& pos, const Move move, StateInfo& st, Stac void Search::Worker::do_move( Position& pos, const Move move, StateInfo& st, const bool givesCheck, Stack* const ss) { bool capture = pos.capture_stage(move); - // Preferable over fetch_add to avoid locking instructions - nodes.store(nodes.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); + ++nodes; auto [dirtyPiece, dirtyThreats] = accumulatorStack.push(); pos.do_move(move, st, givesCheck, dirtyPiece, dirtyThreats, &tt, &sharedHistory); @@ -901,8 +900,7 @@ Value Search::Worker::search( if (err != TB::ProbeState::FAIL) { - // Preferable over fetch_add to avoid locking instructions - tbHits.store(tbHits.load(std::memory_order_relaxed) + 1, std::memory_order_relaxed); + ++tbHits; int drawScore = tbConfig.useRule50 ? 1 : 0; diff --git a/src/search.h b/src/search.h index 05853631d..04c69606f 100644 --- a/src/search.h +++ b/src/search.h @@ -375,9 +375,9 @@ class Worker { LimitsType limits; - usize pvIdx, pvLast; - std::atomic nodes, tbHits, bestMoveChanges; - int selDepth, nmpMinPly; + size_t pvIdx, pvLast; + RelaxedAtomic nodes, tbHits, bestMoveChanges; + int selDepth, nmpMinPly; Value optimism[COLOR_NB]; diff --git a/src/thread.h b/src/thread.h index 1c85327f5..6b7501710 100644 --- a/src/thread.h +++ b/src/thread.h @@ -26,8 +26,8 @@ #include #include -#include "misc.h" #include "memory.h" +#include "misc.h" #include "numa.h" #include "position.h" #include "search.h" @@ -168,7 +168,7 @@ class ThreadPool { std::vector> threads; std::vector boundThreadToNumaNode; - u64 accumulate(std::atomic Search::Worker::* member) const { + u64 accumulate(RelaxedAtomic Search::Worker::* member) const { u64 sum = 0; for (auto&& th : threads) diff --git a/src/tt.cpp b/src/tt.cpp index ea3554978..045f4ecb9 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -79,12 +79,12 @@ struct TTEntry { friend class TranspositionTable; friend struct TTWriter; - u16 key16; - u8 depth8; - u8 genBound8; - Move move16; - i16 value16; - i16 eval16; + RelaxedAtomic key16; + RelaxedAtomic depth8; + RelaxedAtomic genBound8; + RelaxedAtomic move16; + RelaxedAtomic value16; + RelaxedAtomic eval16; }; // Populates the TTEntry with a new node's data, possibly @@ -214,7 +214,7 @@ void TranspositionTable::clear(ThreadPool& threads) { const usize start = stride * i; const usize len = i + 1 != threadCount ? stride : clusterCount - start; - std::memset(&table[start], 0, len * sizeof(Cluster)); + std::memset(static_cast(&table[start]), 0, len * sizeof(Cluster)); }); } diff --git a/tests/instrumented.py b/tests/instrumented.py index ed8b40c82..900151340 100644 --- a/tests/instrumented.py +++ b/tests/instrumented.py @@ -8,7 +8,6 @@ import fnmatch from testing import ( EPD, - TSAN, Stockfish as Engine, MiniTestFramework, OrderedClassMembers, @@ -591,7 +590,6 @@ if __name__ == "__main__": args = parse_args() EPD.create_bench_epd() - TSAN.set_tsan_option() Syzygy.download_syzygy() framework = MiniTestFramework() @@ -600,7 +598,6 @@ if __name__ == "__main__": framework.run([TestCLI, TestInteractive, TestSyzygy, TestEnPassantSanitization]) EPD.delete_bench_epd() - TSAN.unset_tsan_option() if framework.has_failed(): sys.exit(1) diff --git a/tests/testing.py b/tests/testing.py index 452e5cb05..a005df677 100644 --- a/tests/testing.py +++ b/tests/testing.py @@ -43,28 +43,6 @@ class Valgrind: return ["valgrind", "--error-exitcode=42", "--fair-sched=try"] -class TSAN: - @staticmethod - def set_tsan_option(): - with open(f"tsan.supp", "w") as f: - f.write( - """ -race:Stockfish::TTEntry::read -race:Stockfish::TTEntry::save -race:Stockfish::TTWriter::penalize -race:Stockfish::TranspositionTable::probe -race:Stockfish::TranspositionTable::hashfull -""" - ) - - os.environ["TSAN_OPTIONS"] = "suppressions=./tsan.supp" - - @staticmethod - def unset_tsan_option(): - os.environ.pop("TSAN_OPTIONS", None) - os.remove(f"tsan.supp") - - class EPD: @staticmethod def create_bench_epd():