From cb2111f0b62afec5fd977e1dd4ca5843bd006956 Mon Sep 17 00:00:00 2001 From: Marco Costalba Date: Mon, 2 Mar 2015 08:11:39 +0100 Subject: [PATCH 1/3] Disable spinlocks Now that c++11 branch has been merged in master, disable unconditionally the spinlocks and use mutex instead. This will allow to run fishtest even on HT machines withouth changes. In the future we will reintorduce spinlocks, once we will have took care of fishtest. No functional change. --- src/thread.h | 2 +- src/types.h | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/thread.h b/src/thread.h index a639cf7e..34307297 100644 --- a/src/thread.h +++ b/src/thread.h @@ -39,7 +39,7 @@ const size_t MAX_THREADS = 128; const size_t MAX_SPLITPOINTS_PER_THREAD = 8; const size_t MAX_SLAVES_PER_SPLITPOINT = 4; -#if !defined(NO_SPINLOCK) +#if 0 /// Spinlock class wraps low level atomic operations to provide a spin lock class Spinlock { diff --git a/src/types.h b/src/types.h index c8078546..eebd69e5 100644 --- a/src/types.h +++ b/src/types.h @@ -31,9 +31,6 @@ /// -DNO_PREFETCH | Disable use of prefetch asm-instruction. You may need this to /// | run on some very old machines. /// -/// -DNO_SPINLOCK | Use mutex instead of spinlocks. This is much slower, so you -/// | really don't want to do this in general case. -/// /// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works /// | only in 64-bit mode and requires hardware with popcnt support. /// From 6fa6da3ee13d2b7bdbec3cd24ff8ca43233c74fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicolet?= Date: Mon, 2 Mar 2015 22:01:19 +0100 Subject: [PATCH 2/3] Update Makefile for Mac OS X compilation This change in the Makefile restores the possibility to compile Stockfish on Mac OS X 10.9 and 10.10 after the C++11 has been merged. To use the default (fastest) settings, compile with: make build ARCH=x86-64-modern To test the clang settings, compile with make build ARCH=x86-64-modern COMP=clang Beware that the clang settings may provide a slightly slower (6%) executable. No functional change Resolves #275 --- src/Makefile | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Makefile b/src/Makefile index 8622be32..16eb9c55 100644 --- a/src/Makefile +++ b/src/Makefile @@ -151,7 +151,11 @@ ifeq ($(COMP),gcc) comp=gcc CXX=g++ CXXFLAGS += -pedantic -Wno-long-long -Wextra -Wshadow - LDFLAGS += -Wl,--no-as-needed + ifneq ($(UNAME),Darwin) + LDFLAGS += -Wl,--no-as-needed + else + LDFLAGS += -Wl + endif endif ifeq ($(COMP),mingw) @@ -189,8 +193,8 @@ else endif ifeq ($(UNAME),Darwin) - CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.10 - LDFLAGS += -arch $(arch) -mmacosx-version-min=10.10 + CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.9 + LDFLAGS += -arch $(arch) -mmacosx-version-min=10.9 endif ### On mingw use Windows threads, otherwise POSIX From 856a5f3aaaf8b9d53599963decacd4476b55c034 Mon Sep 17 00:00:00 2001 From: Joona Kiiski Date: Sat, 7 Mar 2015 07:38:22 +0000 Subject: [PATCH 3/3] Revert C++11 merge Restore the state of repo back to commit 'Simplify pawn code a bit' (1e6d21dbb6) No functional change --- src/Makefile | 16 +- src/benchmark.cpp | 19 ++- src/bitbase.cpp | 103 ++++++------ src/endgame.cpp | 17 +- src/endgame.h | 34 ++-- src/evaluate.cpp | 176 +++++++++++--------- src/material.cpp | 4 +- src/material.h | 2 +- src/misc.cpp | 27 ++- src/misc.h | 15 +- src/movegen.cpp | 44 ++--- src/movegen.h | 16 +- src/movepick.cpp | 171 ++++++++++--------- src/movepick.h | 12 +- src/pawns.cpp | 2 +- src/platform.h | 116 +++++++++++++ src/position.cpp | 273 +++++++++++++++++------------- src/position.h | 55 ++++--- src/search.cpp | 366 +++++++++++++++++++++-------------------- src/search.h | 12 +- src/syzygy/tbprobe.cpp | 2 - src/thread.cpp | 103 ++++++------ src/thread.h | 69 ++++---- src/tt.cpp | 2 + src/tt.h | 2 - src/types.h | 32 ++-- src/uci.cpp | 12 +- src/uci.h | 9 +- src/ucioption.cpp | 23 +-- 29 files changed, 987 insertions(+), 747 deletions(-) create mode 100644 src/platform.h diff --git a/src/Makefile b/src/Makefile index 16eb9c55..ce8421a4 100644 --- a/src/Makefile +++ b/src/Makefile @@ -140,7 +140,7 @@ endif ### 3.1 Selecting compiler (default = gcc) -CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -fno-rtti -std=c++11 $(EXTRACXXFLAGS) +CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -fno-rtti $(EXTRACXXFLAGS) LDFLAGS += $(EXTRALDFLAGS) ifeq ($(COMP),) @@ -150,12 +150,7 @@ endif ifeq ($(COMP),gcc) comp=gcc CXX=g++ - CXXFLAGS += -pedantic -Wno-long-long -Wextra -Wshadow - ifneq ($(UNAME),Darwin) - LDFLAGS += -Wl,--no-as-needed - else - LDFLAGS += -Wl - endif + CXXFLAGS += -ansi -pedantic -Wno-long-long -Wextra -Wshadow endif ifeq ($(COMP),mingw) @@ -175,9 +170,6 @@ ifeq ($(COMP),clang) comp=clang CXX=clang++ CXXFLAGS += -pedantic -Wno-long-long -Wextra -Wshadow - ifeq ($(UNAME),Darwin) - CXXFLAGS += -std=c++0x -stdlib=libc++ - endif endif ifeq ($(comp),icc) @@ -193,8 +185,8 @@ else endif ifeq ($(UNAME),Darwin) - CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.9 - LDFLAGS += -arch $(arch) -mmacosx-version-min=10.9 + CXXFLAGS += -arch $(arch) -mmacosx-version-min=10.6 + LDFLAGS += -arch $(arch) -mmacosx-version-min=10.6 endif ### On mingw use Windows threads, otherwise POSIX diff --git a/src/benchmark.cpp b/src/benchmark.cpp index e27e81fe..605c95ad 100644 --- a/src/benchmark.cpp +++ b/src/benchmark.cpp @@ -17,6 +17,7 @@ along with this program. If not, see . */ +#include #include #include #include @@ -33,7 +34,7 @@ using namespace std; namespace { -const vector Defaults = { +const char* Defaults[] = { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11", @@ -107,19 +108,19 @@ void benchmark(const Position& current, istream& is) { TT.clear(); if (limitType == "time") - limits.movetime = stoi(limit); // movetime is in ms + limits.movetime = atoi(limit.c_str()); // movetime is in ms else if (limitType == "nodes") - limits.nodes = stoi(limit); + limits.nodes = atoi(limit.c_str()); else if (limitType == "mate") - limits.mate = stoi(limit); + limits.mate = atoi(limit.c_str()); else - limits.depth = stoi(limit); + limits.depth = atoi(limit.c_str()); if (fenFile == "default") - fens = Defaults; + fens.assign(Defaults, Defaults + 37); else if (fenFile == "current") fens.push_back(current.fen()); @@ -127,7 +128,7 @@ void benchmark(const Position& current, istream& is) { else { string fen; - ifstream file(fenFile); + ifstream file(fenFile.c_str()); if (!file.is_open()) { @@ -144,7 +145,7 @@ void benchmark(const Position& current, istream& is) { uint64_t nodes = 0; Search::StateStackPtr st; - TimePoint elapsed = now(); + Time::point elapsed = Time::now(); for (size_t i = 0; i < fens.size(); ++i) { @@ -163,7 +164,7 @@ void benchmark(const Position& current, istream& is) { } } - elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + elapsed = std::max(Time::now() - elapsed, Time::point(1)); // Avoid a 'divide by zero' dbg_print(); // Just before to exit diff --git a/src/bitbase.cpp b/src/bitbase.cpp index ebf3c59a..a018d3cd 100644 --- a/src/bitbase.cpp +++ b/src/bitbase.cpp @@ -17,9 +17,7 @@ along with this program. If not, see . */ -#include #include -#include #include #include "bitboard.h" @@ -56,17 +54,17 @@ namespace { inline Result& operator|=(Result& r, Result v) { return r = Result(r | v); } struct KPKPosition { - KPKPosition() = default; - explicit KPKPosition(unsigned idx); + + KPKPosition(unsigned idx); operator Result() const { return result; } Result classify(const std::vector& db) { return us == WHITE ? classify(db) : classify(db); } + private: template Result classify(const std::vector& db); - unsigned id; Color us; - Square ksq[COLOR_NB], psq; + Square bksq, wksq, psq; Result result; }; @@ -84,20 +82,24 @@ bool Bitbases::probe(Square wksq, Square wpsq, Square bksq, Color us) { void Bitbases::init() { - std::vector db(MAX_INDEX); + unsigned idx, repeat = 1; + std::vector db; + db.reserve(MAX_INDEX); // Initialize db with known win / draw positions - std::generate(db.begin(), db.end(), [](){ static unsigned id; return KPKPosition(id++); }); + for (idx = 0; idx < MAX_INDEX; ++idx) + db.push_back(KPKPosition(idx)); // Iterate through the positions until none of the unknown positions can be // changed to either wins or draws (15 cycles needed). - while (std::accumulate(db.begin(), db.end(), false, [&](bool repeat, KPKPosition& pos) - { return (pos == UNKNOWN && pos.classify(db) != UNKNOWN) || repeat; })){} + while (repeat) + for (repeat = idx = 0; idx < MAX_INDEX; ++idx) + repeat |= (db[idx] == UNKNOWN && db[idx].classify(db) != UNKNOWN); // Map 32 results into one KPKBitbase[] entry - for (auto& pos : db) - if (pos == WIN) - KPKBitbase[pos.id / 32] |= 1 << (pos.id & 0x1F); + for (idx = 0; idx < MAX_INDEX; ++idx) + if (db[idx] == WIN) + KPKBitbase[idx / 32] |= 1 << (idx & 0x1F); } @@ -105,74 +107,69 @@ namespace { KPKPosition::KPKPosition(unsigned idx) { - id = idx; - ksq[WHITE] = Square((idx >> 0) & 0x3F); - ksq[BLACK] = Square((idx >> 6) & 0x3F); - us = Color ((idx >> 12) & 0x01); - psq = make_square(File((idx >> 13) & 0x3), RANK_7 - Rank((idx >> 15) & 0x7)); + wksq = Square((idx >> 0) & 0x3F); + bksq = Square((idx >> 6) & 0x3F); + us = Color ((idx >> 12) & 0x01); + psq = make_square(File((idx >> 13) & 0x3), RANK_7 - Rank((idx >> 15) & 0x7)); + result = UNKNOWN; // Check if two pieces are on the same square or if a king can be captured - if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 - || ksq[WHITE] == psq - || ksq[BLACK] == psq - || (us == WHITE && (StepAttacksBB[PAWN][psq] & ksq[BLACK]))) + if ( distance(wksq, bksq) <= 1 + || wksq == psq + || bksq == psq + || (us == WHITE && (StepAttacksBB[PAWN][psq] & bksq))) result = INVALID; - // Immediate win if a pawn can be promoted without getting captured - else if ( us == WHITE - && rank_of(psq) == RANK_7 - && ksq[us] != psq + DELTA_N - && ( distance(ksq[~us], psq + DELTA_N) > 1 - || (StepAttacksBB[KING][ksq[us]] & (psq + DELTA_N)))) - result = WIN; - + else if (us == WHITE) + { + // Immediate win if a pawn can be promoted without getting captured + if ( rank_of(psq) == RANK_7 + && wksq != psq + DELTA_N + && ( distance(bksq, psq + DELTA_N) > 1 + ||(StepAttacksBB[KING][wksq] & (psq + DELTA_N)))) + result = WIN; + } // Immediate draw if it is a stalemate or a king captures undefended pawn - else if ( us == BLACK - && ( !(StepAttacksBB[KING][ksq[us]] & ~(StepAttacksBB[KING][ksq[~us]] | StepAttacksBB[PAWN][psq])) - || (StepAttacksBB[KING][ksq[us]] & psq & ~StepAttacksBB[KING][ksq[~us]]))) + else if ( !(StepAttacksBB[KING][bksq] & ~(StepAttacksBB[KING][wksq] | StepAttacksBB[PAWN][psq])) + || (StepAttacksBB[KING][bksq] & psq & ~StepAttacksBB[KING][wksq])) result = DRAW; - - // Position will be classified later - else - result = UNKNOWN; } template Result KPKPosition::classify(const std::vector& db) { - // White to move: If one move leads to a position classified as WIN, the result + // White to Move: If one move leads to a position classified as WIN, the result // of the current position is WIN. If all moves lead to positions classified // as DRAW, the current position is classified as DRAW, otherwise the current // position is classified as UNKNOWN. // - // Black to move: If one move leads to a position classified as DRAW, the result + // Black to Move: If one move leads to a position classified as DRAW, the result // of the current position is DRAW. If all moves lead to positions classified // as WIN, the position is classified as WIN, otherwise the current position is // classified as UNKNOWN. - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Result Good = (Us == WHITE ? WIN : DRAW); - const Result Bad = (Us == WHITE ? DRAW : WIN); + const Color Them = (Us == WHITE ? BLACK : WHITE); Result r = INVALID; - Bitboard b = StepAttacksBB[KING][ksq[Us]]; + Bitboard b = StepAttacksBB[KING][Us == WHITE ? wksq : bksq]; while (b) - r |= Us == WHITE ? db[index(Them, ksq[Them] , pop_lsb(&b), psq)] - : db[index(Them, pop_lsb(&b), ksq[Them] , psq)]; + r |= Us == WHITE ? db[index(Them, bksq, pop_lsb(&b), psq)] + : db[index(Them, pop_lsb(&b), wksq, psq)]; - if (Us == WHITE) + if (Us == WHITE && rank_of(psq) < RANK_7) { - if (rank_of(psq) < RANK_7) // Single push - r |= db[index(Them, ksq[Them], ksq[Us], psq + DELTA_N)]; + Square s = psq + DELTA_N; + r |= db[index(BLACK, bksq, wksq, s)]; // Single push - if ( rank_of(psq) == RANK_2 // Double push - && psq + DELTA_N != ksq[Us] - && psq + DELTA_N != ksq[Them]) - r |= db[index(Them, ksq[Them], ksq[Us], psq + DELTA_N + DELTA_N)]; + if (rank_of(psq) == RANK_2 && s != wksq && s != bksq) + r |= db[index(BLACK, bksq, wksq, s + DELTA_N)]; // Double push } - return result = r & Good ? Good : r & UNKNOWN ? UNKNOWN : Bad; + if (Us == WHITE) + return result = r & WIN ? WIN : r & UNKNOWN ? UNKNOWN : DRAW; + else + return result = r & DRAW ? DRAW : r & UNKNOWN ? UNKNOWN : WIN; } } // namespace diff --git a/src/endgame.cpp b/src/endgame.cpp index 836621d2..2c87b2a1 100644 --- a/src/endgame.cpp +++ b/src/endgame.cpp @@ -96,9 +96,12 @@ namespace { string fen = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/" + sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10"; - return Position(fen, false, nullptr).material_key(); + return Position(fen, false, NULL).material_key(); } + template + void delete_endgame(const typename M::value_type& p) { delete p.second; } + } // namespace @@ -125,11 +128,17 @@ Endgames::Endgames() { add("KRPPKRP"); } +Endgames::~Endgames() { -template + for_each(m1.begin(), m1.end(), delete_endgame); + for_each(m2.begin(), m2.end(), delete_endgame); +} + +template void Endgames::add(const string& code) { - map()[key(code, WHITE)] = std::unique_ptr>(new Endgame(WHITE)); - map()[key(code, BLACK)] = std::unique_ptr>(new Endgame(BLACK)); + + map((Endgame*)0)[key(code, WHITE)] = new Endgame(WHITE); + map((Endgame*)0)[key(code, BLACK)] = new Endgame(BLACK); } diff --git a/src/endgame.h b/src/endgame.h index cd05f0cd..d7a7681a 100644 --- a/src/endgame.h +++ b/src/endgame.h @@ -21,10 +21,7 @@ #define ENDGAME_H_INCLUDED #include -#include #include -#include -#include #include "position.h" #include "types.h" @@ -66,9 +63,11 @@ enum EndgameType { /// Endgame functions can be of two types depending on whether they return a -/// Value or a ScaleFactor. -template using -eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; +/// Value or a ScaleFactor. Type eg_fun::type returns either ScaleFactor +/// or Value depending on whether the template parameter is 0 or 1. + +template struct eg_fun { typedef Value type; }; +template<> struct eg_fun<1> { typedef ScaleFactor type; }; /// Base and derived templates for endgame evaluation and scaling functions @@ -82,7 +81,7 @@ struct EndgameBase { }; -template> +template SCALING_FUNCTIONS)>::type> struct Endgame : public EndgameBase { explicit Endgame(Color c) : strongSide(c), weakSide(~c) {} @@ -100,24 +99,23 @@ private: class Endgames { - template using Map = std::map>>; + typedef std::map::type>*> M1; + typedef std::map::type>*> M2; - template> - void add(const std::string& code); + M1 m1; + M2 m2; - template - Map& map() { - return std::get::value>(maps); - } + M1& map(M1::mapped_type) { return m1; } + M2& map(M2::mapped_type) { return m2; } - std::pair, Map> maps; + template void add(const std::string& code); public: Endgames(); + ~Endgames(); - template - EndgameBase* probe(Key key) { - return map().count(key) ? map()[key].get() : nullptr; + template T probe(Key key, T& eg) { + return eg = map(eg).count(key) ? map(eg)[key] : NULL; } }; diff --git a/src/evaluate.cpp b/src/evaluate.cpp index 30e4c59a..c880b7c7 100644 --- a/src/evaluate.cpp +++ b/src/evaluate.cpp @@ -30,23 +30,6 @@ namespace { - namespace Tracing { - - enum Term { // First 8 entries are for PieceType - MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL, TERM_NB - }; - - Score scores[COLOR_NB][TERM_NB]; - - std::ostream& operator<<(std::ostream& os, Term idx); - - double to_cp(Value v); - void write(int idx, Color c, Score s); - void write(int idx, Score w, Score b = SCORE_ZERO); - std::string do_trace(const Position& pos); - } - - // Struct EvalInfo contains various information computed and collected // by the evaluation functions. struct EvalInfo { @@ -88,19 +71,29 @@ namespace { Bitboard pinnedPieces[COLOR_NB]; }; + namespace Tracing { - // Evaluation weights, indexed by the corresponding evaluation term + enum Terms { // First 8 entries are for PieceType + MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL, TERMS_NB + }; + + Score scores[COLOR_NB][TERMS_NB]; + EvalInfo ei; + ScaleFactor sf; + + double to_cp(Value v); + void write(int idx, Color c, Score s); + void write(int idx, Score w, Score b = SCORE_ZERO); + void print(std::stringstream& ss, const char* name, int idx); + std::string do_trace(const Position& pos); + } + + // Evaluation weights, indexed by evaluation term enum { Mobility, PawnStructure, PassedPawns, Space, KingSafety }; - const struct Weight { int mg, eg; } Weights[] = { {289, 344}, {233, 201}, {221, 273}, {46, 0}, {322, 0} }; - Score operator*(Score s, const Weight& w) { - return make_score(mg_value(s) * w.mg / 256, eg_value(s) * w.eg / 256); - } - - #define V(v) Value(v) #define S(mg, eg) make_score(mg, eg) @@ -124,8 +117,8 @@ namespace { S( 25, 41), S( 25, 41), S(25, 41), S(25, 41) } }; - // Outpost[Bishop/Knight][Square] contains bonuses for knights and bishops - // outposts, indexed by piece type and square (from white's point of view). + // Outpost[PieceType][Square] contains bonuses for knights and bishops outposts, + // indexed by piece type and square (from white's point of view). const Value Outpost[][SQUARE_NB] = { {// A B C D E F G H V(0), V(0), V(0), V(0), V(0), V(0), V(0), V(0), // Knights @@ -154,7 +147,7 @@ namespace { // ThreatenedByPawn[PieceType] contains a penalty according to which piece // type is attacked by an enemy pawn. - const Score ThreatenedByPawn[PIECE_TYPE_NB] = { + const Score ThreatenedByPawn[] = { S(0, 0), S(0, 0), S(107, 138), S(84, 122), S(114, 203), S(121, 217) }; @@ -184,7 +177,7 @@ namespace { // by the space evaluation. In the middlegame, each side is given a bonus // based on how many squares inside this area are safe and available for // friendly minor pieces. - const Bitboard SpaceMask[COLOR_NB] = { + const Bitboard SpaceMask[] = { (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB), (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB) }; @@ -193,12 +186,11 @@ namespace { // in KingDanger[]. Various little "meta-bonuses" measuring the strength // of the enemy attack are added up into an integer, which is used as an // index to KingDanger[]. - Score KingDanger[512]; - + // // KingAttackWeights[PieceType] contains king attack weights by piece type - const int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 7, 5, 4, 1 }; + const int KingAttackWeights[] = { 0, 0, 7, 5, 4, 1 }; - // Penalties for enemy's safe checks + // Bonuses for enemy's safe checks const int QueenContactCheck = 89; const int RookContactCheck = 71; const int QueenCheck = 50; @@ -206,6 +198,15 @@ namespace { const int BishopCheck = 6; const int KnightCheck = 14; + // KingDanger[attackUnits] contains the actual king danger weighted + // scores, indexed by a calculated integer number. + Score KingDanger[512]; + + // apply_weight() weighs score 's' by weight 'w' trying to prevent overflow + Score apply_weight(Score s, const Weight& w) { + return make_score(mg_value(s) * w.mg / 256, eg_value(s) * w.eg / 256); + } + // init_eval_info() initializes king bitboards for given color adding // pawn attacks. To be done at the beginning of the evaluation. @@ -213,12 +214,13 @@ namespace { template void init_eval_info(const Position& pos, EvalInfo& ei) { - const Color Them = (Us == WHITE ? BLACK : WHITE); + const Color Them = (Us == WHITE ? BLACK : WHITE); const Square Down = (Us == WHITE ? DELTA_S : DELTA_N); ei.pinnedPieces[Us] = pos.pinned_pieces(Us); - ei.attackedBy[Us][ALL_PIECES] = ei.attackedBy[Us][PAWN] = ei.pi->pawn_attacks(Us); + Bitboard b = ei.attackedBy[Them][KING] = pos.attacks_from(pos.king_square(Them)); + ei.attackedBy[Us][ALL_PIECES] = ei.attackedBy[Us][PAWN] = ei.pi->pawn_attacks(Us); // Init king safety tables only if we are going to use them if (pos.non_pawn_material(Us) >= QueenValueMg) @@ -301,7 +303,8 @@ namespace { | ei.attackedBy[Them][BISHOP] | ei.attackedBy[Them][ROOK]); - int mob = popcount(b & mobilityArea[Us]); + int mob = Pt != QUEEN ? popcount(b & mobilityArea[Us]) + : popcount(b & mobilityArea[Us]); mobility[Us] += MobilityBonus[Pt][mob]; @@ -505,7 +508,8 @@ namespace { Score score = SCORE_ZERO; // Non-pawn enemies defended by a pawn - defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Them][PAWN]; + defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) + & ei.attackedBy[Them][PAWN]; // Add a bonus according to the kind of attacking pieces if (defended) @@ -647,10 +651,10 @@ namespace { } if (Trace) - Tracing::write(Tracing::PASSED, Us, score * Weights[PassedPawns]); + Tracing::write(Tracing::PASSED, Us, apply_weight(score, Weights[PassedPawns])); // Add the scores to the middlegame and endgame eval - return score * Weights[PassedPawns]; + return apply_weight(score, Weights[PassedPawns]); } @@ -716,7 +720,7 @@ namespace { // Probe the pawn hash table ei.pi = Pawns::probe(pos); - score += ei.pi->pawns_score() * Weights[PawnStructure]; + score += apply_weight(ei.pi->pawns_score(), Weights[PawnStructure]); // Initialize attack and king safety bitboards init_eval_info(pos, ei); @@ -731,7 +735,7 @@ namespace { // Evaluate pieces and mobility score += evaluate_pieces(pos, ei, mobility, mobilityArea); - score += (mobility[WHITE] - mobility[BLACK]) * Weights[Mobility]; + score += apply_weight(mobility[WHITE] - mobility[BLACK], Weights[Mobility]); // Evaluate kings after all other pieces because we need complete attack // information when computing the king safety evaluation. @@ -758,8 +762,11 @@ namespace { } // Evaluate space for both sides, only during opening - if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 11756) - score += (evaluate_space(pos, ei) - evaluate_space(pos, ei)) * Weights[Space]; + if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 2 * QueenValueMg + 4 * RookValueMg + 2 * KnightValueMg) + { + Score s = evaluate_space(pos, ei) - evaluate_space(pos, ei); + score += apply_weight(s, Weights[Space]); + } // Scale winning side if position is more drawish than it appears Color strongSide = eg_value(score) > VALUE_DRAW ? WHITE : BLACK; @@ -797,48 +804,57 @@ namespace { v /= int(PHASE_MIDGAME); - // In case of tracing add all single evaluation terms for both white and black + // In case of tracing add all single evaluation contributions for both white and black if (Trace) { Tracing::write(Tracing::MATERIAL, pos.psq_score()); Tracing::write(Tracing::IMBALANCE, ei.mi->imbalance()); Tracing::write(PAWN, ei.pi->pawns_score()); - Tracing::write(Tracing::MOBILITY, mobility[WHITE] * Weights[Mobility] - , mobility[BLACK] * Weights[Mobility]); - Tracing::write(Tracing::SPACE, evaluate_space(pos, ei) * Weights[Space] - , evaluate_space(pos, ei) * Weights[Space]); + Tracing::write(Tracing::MOBILITY, apply_weight(mobility[WHITE], Weights[Mobility]) + , apply_weight(mobility[BLACK], Weights[Mobility])); + Tracing::write(Tracing::SPACE, apply_weight(evaluate_space(pos, ei), Weights[Space]) + , apply_weight(evaluate_space(pos, ei), Weights[Space])); Tracing::write(Tracing::TOTAL, score); + Tracing::ei = ei; + Tracing::sf = sf; } - return (pos.side_to_move() == WHITE ? v : -v) + Eval::Tempo; // Side to move point of view + return (pos.side_to_move() == WHITE ? v : -v) + Eval::Tempo; } - // Tracing functions + // Tracing function definitions double Tracing::to_cp(Value v) { return double(v) / PawnValueEg; } void Tracing::write(int idx, Color c, Score s) { scores[c][idx] = s; } void Tracing::write(int idx, Score w, Score b) { - scores[WHITE][idx] = w, scores[BLACK][idx] = b; + + write(idx, WHITE, w); + write(idx, BLACK, b); } - std::ostream& Tracing::operator<<(std::ostream& os, Term t) { + void Tracing::print(std::stringstream& ss, const char* name, int idx) { - double wScore[] = { to_cp(mg_value(scores[WHITE][t])), to_cp(eg_value(scores[WHITE][t])) }; - double bScore[] = { to_cp(mg_value(scores[BLACK][t])), to_cp(eg_value(scores[BLACK][t])) }; + Score wScore = scores[WHITE][idx]; + Score bScore = scores[BLACK][idx]; - if (t == MATERIAL || t == IMBALANCE || t == Term(PAWN) || t == TOTAL) - os << " --- --- | --- --- | "; - else - os << std::setw(5) << wScore[MG] << " " << std::setw(5) << wScore[EG] << " | " - << std::setw(5) << bScore[MG] << " " << std::setw(5) << bScore[EG] << " | "; - - os << std::setw(5) << wScore[MG] - bScore[MG] << " " - << std::setw(5) << wScore[EG] - bScore[EG] << " \n"; - - return os; + switch (idx) { + case MATERIAL: case IMBALANCE: case PAWN: case TOTAL: + ss << std::setw(15) << name << " | --- --- | --- --- | " + << std::setw(5) << to_cp(mg_value(wScore - bScore)) << " " + << std::setw(5) << to_cp(eg_value(wScore - bScore)) << " \n"; + break; + default: + ss << std::setw(15) << name << " | " << std::noshowpos + << std::setw(5) << to_cp(mg_value(wScore)) << " " + << std::setw(5) << to_cp(eg_value(wScore)) << " | " + << std::setw(5) << to_cp(mg_value(bScore)) << " " + << std::setw(5) << to_cp(eg_value(bScore)) << " | " + << std::setw(5) << to_cp(mg_value(wScore - bScore)) << " " + << std::setw(5) << to_cp(eg_value(wScore - bScore)) << " \n"; + } } std::string Tracing::do_trace(const Position& pos) { @@ -852,21 +868,23 @@ namespace { ss << std::showpoint << std::noshowpos << std::fixed << std::setprecision(2) << " Eval term | White | Black | Total \n" << " | MG EG | MG EG | MG EG \n" - << "----------------+-------------+-------------+-------------\n" - << " Material | " << Term(MATERIAL) - << " Imbalance | " << Term(IMBALANCE) - << " Pawns | " << Term(PAWN) - << " Knights | " << Term(KNIGHT) - << " Bishop | " << Term(BISHOP) - << " Rooks | " << Term(ROOK) - << " Queens | " << Term(QUEEN) - << " Mobility | " << Term(MOBILITY) - << " King safety | " << Term(KING) - << " Threats | " << Term(THREAT) - << " Passed pawns | " << Term(PASSED) - << " Space | " << Term(SPACE) - << "----------------+-------------+-------------+-------------\n" - << " Total | " << Term(TOTAL); + << "----------------+-------------+-------------+-------------\n"; + + print(ss, "Material", MATERIAL); + print(ss, "Imbalance", IMBALANCE); + print(ss, "Pawns", PAWN); + print(ss, "Knights", KNIGHT); + print(ss, "Bishops", BISHOP); + print(ss, "Rooks", ROOK); + print(ss, "Queens", QUEEN); + print(ss, "Mobility", MOBILITY); + print(ss, "King safety", KING); + print(ss, "Threats", THREAT); + print(ss, "Passed pawns", PASSED); + print(ss, "Space", SPACE); + + ss << "----------------+-------------+-------------+-------------\n"; + print(ss, "Total", TOTAL); ss << "\nTotal Evaluation: " << to_cp(v) << " (white side)\n"; @@ -906,7 +924,7 @@ namespace Eval { for (int i = 0; i < 400; ++i) { t = std::min(Peak, std::min(i * i * 27, t + MaxSlope)); - KingDanger[i] = make_score(t / 1000, 0) * Weights[KingSafety]; + KingDanger[i] = apply_weight(make_score(t / 1000, 0), Weights[KingSafety]); } } diff --git a/src/material.cpp b/src/material.cpp index ebf5adff..9873a44e 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -136,7 +136,7 @@ Entry* probe(const Position& pos) { // Let's look if we have a specialized evaluation function for this particular // material configuration. Firstly we look for a fixed configuration one, then // for a generic one if the previous search failed. - if ((e->evaluationFunction = pos.this_thread()->endgames.probe(key)) != nullptr) + if (pos.this_thread()->endgames.probe(key, e->evaluationFunction)) return e; for (Color c = WHITE; c <= BLACK; ++c) @@ -150,7 +150,7 @@ Entry* probe(const Position& pos) { // configuration. Is there a suitable specialized scaling function? EndgameBase* sf; - if ((sf = pos.this_thread()->endgames.probe(key)) != nullptr) + if (pos.this_thread()->endgames.probe(key, sf)) { e->scalingFunction[sf->strong_side()] = sf; // Only strong color assigned return e; diff --git a/src/material.h b/src/material.h index 67d555a4..0119caab 100644 --- a/src/material.h +++ b/src/material.h @@ -40,7 +40,7 @@ struct Entry { Score imbalance() const { return make_score(value, value); } Phase game_phase() const { return gamePhase; } - bool specialized_eval_exists() const { return evaluationFunction != nullptr; } + bool specialized_eval_exists() const { return evaluationFunction != NULL; } Value evaluate(const Position& pos) const { return (*evaluationFunction)(pos); } // scale_factor takes a position and a color as input and returns a scale factor diff --git a/src/misc.cpp b/src/misc.cpp index f09694cc..aa2b2331 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -146,7 +146,7 @@ void dbg_print() { std::ostream& operator<<(std::ostream& os, SyncCout sc) { - static std::mutex m; + static Mutex m; if (sc == IO_LOCK) m.lock(); @@ -162,16 +162,35 @@ std::ostream& operator<<(std::ostream& os, SyncCout sc) { void start_logger(bool b) { Logger::start(b); } +/// timed_wait() waits for msec milliseconds. It is mainly a helper to wrap +/// the conversion from milliseconds to struct timespec, as used by pthreads. + +void timed_wait(WaitCondition& sleepCond, Lock& sleepLock, int msec) { + +#ifdef _WIN32 + int tm = msec; +#else + timespec ts, *tm = &ts; + uint64_t ms = Time::now() + msec; + + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000LL; +#endif + + cond_timedwait(sleepCond, sleepLock, tm); +} + + /// prefetch() preloads the given address in L1/L2 cache. This is a non-blocking /// function that doesn't stall the CPU waiting for data to be loaded from memory, /// which can be quite slow. #ifdef NO_PREFETCH -void prefetch(void*) {} +void prefetch(char*) {} #else -void prefetch(void* addr) { +void prefetch(char* addr) { # if defined(__INTEL_COMPILER) // This hack prevents prefetches from being optimized away by @@ -180,7 +199,7 @@ void prefetch(void* addr) { # endif # if defined(__INTEL_COMPILER) || defined(_MSC_VER) - _mm_prefetch((char*)addr, _MM_HINT_T0); + _mm_prefetch(addr, _MM_HINT_T0); # else __builtin_prefetch(addr); # endif diff --git a/src/misc.h b/src/misc.h index 37f1e706..246a56a0 100644 --- a/src/misc.h +++ b/src/misc.h @@ -21,7 +21,6 @@ #define MISC_H_INCLUDED #include -#include #include #include #include @@ -29,7 +28,8 @@ #include "types.h" const std::string engine_info(bool to_uci = false); -void prefetch(void* addr); +void timed_wait(WaitCondition&, Lock&, int); +void prefetch(char* addr); void start_logger(bool b); void dbg_hit_on(bool b); @@ -37,19 +37,20 @@ void dbg_hit_on(bool c, bool b); void dbg_mean_of(int v); void dbg_print(); -typedef std::chrono::milliseconds::rep TimePoint; // A value in milliseconds -inline TimePoint now() { - return std::chrono::duration_cast - (std::chrono::steady_clock::now().time_since_epoch()).count(); +namespace Time { + typedef int64_t point; + inline point now() { return system_time_to_msec(); } } + template struct HashTable { + HashTable() : table(Size, Entry()) {} Entry* operator[](Key key) { return &table[(uint32_t)key & (Size - 1)]; } private: - std::vector table = std::vector(Size); + std::vector table; }; diff --git a/src/movegen.cpp b/src/movegen.cpp index 1e14f59b..bfc8858a 100644 --- a/src/movegen.cpp +++ b/src/movegen.cpp @@ -59,9 +59,9 @@ namespace { if (Checks && !pos.gives_check(m, *ci)) return moveList; - *moveList++ = m; + (moveList++)->move = m; - return (void)ci, moveList; // Silence a warning under MSVC + return moveList; } @@ -69,21 +69,23 @@ namespace { inline ExtMove* make_promotions(ExtMove* moveList, Square to, const CheckInfo* ci) { if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) - *moveList++ = make(to - Delta, to, QUEEN); + (moveList++)->move = make(to - Delta, to, QUEEN); if (Type == QUIETS || Type == EVASIONS || Type == NON_EVASIONS) { - *moveList++ = make(to - Delta, to, ROOK); - *moveList++ = make(to - Delta, to, BISHOP); - *moveList++ = make(to - Delta, to, KNIGHT); + (moveList++)->move = make(to - Delta, to, ROOK); + (moveList++)->move = make(to - Delta, to, BISHOP); + (moveList++)->move = make(to - Delta, to, KNIGHT); } // Knight promotion is the only promotion that can give a direct check // that's not already included in the queen promotion. if (Type == QUIET_CHECKS && (StepAttacksBB[W_KNIGHT][to] & ci->ksq)) - *moveList++ = make(to - Delta, to, KNIGHT); + (moveList++)->move = make(to - Delta, to, KNIGHT); + else + (void)ci; // Silence a warning under MSVC - return (void)ci, moveList; // Silence a warning under MSVC + return moveList; } @@ -145,13 +147,13 @@ namespace { while (b1) { Square to = pop_lsb(&b1); - *moveList++ = make_move(to - Up, to); + (moveList++)->move = make_move(to - Up, to); } while (b2) { Square to = pop_lsb(&b2); - *moveList++ = make_move(to - Up - Up, to); + (moveList++)->move = make_move(to - Up - Up, to); } } @@ -187,13 +189,13 @@ namespace { while (b1) { Square to = pop_lsb(&b1); - *moveList++ = make_move(to - Right, to); + (moveList++)->move = make_move(to - Right, to); } while (b2) { Square to = pop_lsb(&b2); - *moveList++ = make_move(to - Left, to); + (moveList++)->move = make_move(to - Left, to); } if (pos.ep_square() != SQ_NONE) @@ -211,7 +213,7 @@ namespace { assert(b1); while (b1) - *moveList++ = make(pop_lsb(&b1), pos.ep_square()); + (moveList++)->move = make(pop_lsb(&b1), pos.ep_square()); } } @@ -245,7 +247,7 @@ namespace { b &= ci->checkSq[Pt]; while (b) - *moveList++ = make_move(from, pop_lsb(&b)); + (moveList++)->move = make_move(from, pop_lsb(&b)); } return moveList; @@ -254,7 +256,7 @@ namespace { template FORCE_INLINE ExtMove* generate_all(const Position& pos, ExtMove* moveList, Bitboard target, - const CheckInfo* ci = nullptr) { + const CheckInfo* ci = NULL) { const bool Checks = Type == QUIET_CHECKS; @@ -269,7 +271,7 @@ namespace { Square ksq = pos.king_square(Us); Bitboard b = pos.attacks_from(ksq) & target; while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); + (moveList++)->move = make_move(ksq, pop_lsb(&b)); } if (Type != CAPTURES && Type != EVASIONS && pos.can_castle(Us)) @@ -348,7 +350,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { b &= ~PseudoAttacks[QUEEN][ci.ksq]; while (b) - *moveList++ = make_move(from, pop_lsb(&b)); + (moveList++)->move = make_move(from, pop_lsb(&b)); } return us == WHITE ? generate_all(pos, moveList, ~pos.pieces(), &ci) @@ -380,7 +382,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { // Generate evasions for king, capture and non capture moves Bitboard b = pos.attacks_from(ksq) & ~pos.pieces(us) & ~sliderAttacks; while (b) - *moveList++ = make_move(ksq, pop_lsb(&b)); + (moveList++)->move = make_move(ksq, pop_lsb(&b)); if (more_than_one(pos.checkers())) return moveList; // Double check, only a king move can save the day @@ -406,9 +408,9 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { moveList = pos.checkers() ? generate(pos, moveList) : generate(pos, moveList); while (cur != moveList) - if ( (pinned || from_sq(*cur) == ksq || type_of(*cur) == ENPASSANT) - && !pos.legal(*cur, pinned)) - *cur = (--moveList)->move; + if ( (pinned || from_sq(cur->move) == ksq || type_of(cur->move) == ENPASSANT) + && !pos.legal(cur->move, pinned)) + cur->move = (--moveList)->move; else ++cur; diff --git a/src/movegen.h b/src/movegen.h index 6a7f7e3a..62a121d7 100644 --- a/src/movegen.h +++ b/src/movegen.h @@ -36,9 +36,6 @@ enum GenType { struct ExtMove { Move move; Value value; - - operator Move() const { return move; } - void operator=(Move m) { move = m; } }; inline bool operator<(const ExtMove& f, const ExtMove& s) { @@ -53,17 +50,18 @@ ExtMove* generate(const Position& pos, ExtMove* moveList); template struct MoveList { - explicit MoveList(const Position& pos) : last(generate(pos, moveList)) {} - const ExtMove* begin() const { return moveList; } - const ExtMove* end() const { return last; } + explicit MoveList(const Position& pos) : cur(moveList), last(generate(pos, moveList)) { last->move = MOVE_NONE; } + void operator++() { ++cur; } + Move operator*() const { return cur->move; } size_t size() const { return last - moveList; } - bool contains(Move move) const { - for (const auto& m : *this) if (m == move) return true; + bool contains(Move m) const { + for (const ExtMove* it(moveList); it != last; ++it) if (it->move == m) return true; return false; } private: - ExtMove moveList[MAX_MOVES], *last; + ExtMove moveList[MAX_MOVES]; + ExtMove *cur, *last; }; #endif // #ifndef MOVEGEN_H_INCLUDED diff --git a/src/movepick.cpp b/src/movepick.cpp index b3f413bf..edee8179 100644 --- a/src/movepick.cpp +++ b/src/movepick.cpp @@ -35,7 +35,7 @@ namespace { STOP }; - // Our insertion sort, which is guaranteed to be stable, as it should be + // Our insertion sort, which is guaranteed (and also needed) to be stable void insertion_sort(ExtMove* begin, ExtMove* end) { ExtMove tmp, *p, *q; @@ -49,15 +49,18 @@ namespace { } } - // pick_best() finds the best move in the range (begin, end) and moves it to - // the front. It's faster than sorting all the moves in advance when there - // are few moves e.g. the possible captures. - inline Move pick_best(ExtMove* begin, ExtMove* end) + // Unary predicate used by std::partition to split positive values from remaining + // ones so as to sort the two sets separately, with the second sort delayed. + inline bool has_positive_value(const ExtMove& move) { return move.value > VALUE_ZERO; } + + // Picks the best move in the range (begin, end) and moves it to the front. + // It's faster than sorting all the moves in advance when there are few + // moves e.g. possible captures. + inline ExtMove* pick_best(ExtMove* begin, ExtMove* end) { std::swap(*begin, *std::max_element(begin, end)); - return *begin; + return begin; } - } // namespace @@ -72,6 +75,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& assert(d > DEPTH_ZERO); + cur = end = moves; endBadCaptures = moves + MAX_MOVES - 1; countermoves = cm; followupmoves = fm; @@ -84,11 +88,11 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& stage = MAIN_SEARCH; ttMove = (ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE); - endMoves += (ttMove != MOVE_NONE); + end += (ttMove != MOVE_NONE); } MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& h, - Square s) : pos(p), history(h) { + Square s) : pos(p), history(h), cur(moves), end(moves) { assert(d <= DEPTH_ZERO); @@ -109,11 +113,11 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const HistoryStats& } ttMove = (ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE); - endMoves += (ttMove != MOVE_NONE); + end += (ttMove != MOVE_NONE); } MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, PieceType pt) - : pos(p), history(h) { + : pos(p), history(h), cur(moves), end(moves) { assert(!pos.checkers()); @@ -127,7 +131,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, const HistoryStats& h, Piece if (ttMove && (!pos.capture(ttMove) || pos.see(ttMove) <= captureThreshold)) ttMove = MOVE_NONE; - endMoves += (ttMove != MOVE_NONE); + end += (ttMove != MOVE_NONE); } @@ -148,22 +152,32 @@ void MovePicker::score() { // badCaptures[] array, but instead of doing it now we delay until the move // has been picked up in pick_move_from_list(). This way we save some SEE // calls in case we get a cutoff. - for (auto& m : *this) + Move m; + + for (ExtMove* it = moves; it != end; ++it) + { + m = it->move; + it->value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(type_of(pos.moved_piece(m))); + if (type_of(m) == ENPASSANT) - m.value = PieceValue[MG][PAWN] - Value(PAWN); + it->value += PieceValue[MG][PAWN]; else if (type_of(m) == PROMOTION) - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - Value(PAWN) - + PieceValue[MG][promotion_type(m)] - PieceValue[MG][PAWN]; - else - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - - Value(type_of(pos.moved_piece(m))); + it->value += PieceValue[MG][promotion_type(m)] - PieceValue[MG][PAWN]; + } } template<> void MovePicker::score() { - for (auto& m : *this) - m.value = history[pos.moved_piece(m)][to_sq(m)]; + + Move m; + + for (ExtMove* it = moves; it != end; ++it) + { + m = it->move; + it->value = history[pos.moved_piece(m)][to_sq(m)]; + } } template<> @@ -171,17 +185,21 @@ void MovePicker::score() { // Try good captures ordered by MVV/LVA, then non-captures if destination square // is not under attack, ordered by history value, then bad-captures and quiet // moves with a negative SEE. This last group is ordered by the SEE value. + Move m; Value see; - for (auto& m : *this) + for (ExtMove* it = moves; it != end; ++it) + { + m = it->move; if ((see = pos.see_sign(m)) < VALUE_ZERO) - m.value = see - HistoryStats::Max; // At the bottom + it->value = see - HistoryStats::Max; // At the bottom else if (pos.capture(m)) - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - - Value(type_of(pos.moved_piece(m))) + HistoryStats::Max; + it->value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(type_of(pos.moved_piece(m))) + HistoryStats::Max; else - m.value = history[pos.moved_piece(m)][to_sq(m)]; + it->value = history[pos.moved_piece(m)][to_sq(m)]; + } } @@ -195,73 +213,74 @@ void MovePicker::generate_next_stage() { switch (++stage) { case CAPTURES_S1: case CAPTURES_S3: case CAPTURES_S4: case CAPTURES_S5: case CAPTURES_S6: - endMoves = generate(pos, moves); + end = generate(pos, moves); score(); - break; + return; case KILLERS_S1: cur = killers; - endMoves = cur + 2; + end = cur + 2; - killers[0] = ss->killers[0]; - killers[1] = ss->killers[1]; + killers[0].move = ss->killers[0]; + killers[1].move = ss->killers[1]; killers[2].move = killers[3].move = MOVE_NONE; killers[4].move = killers[5].move = MOVE_NONE; - // In SMP case countermoves[] and followupmoves[] could have duplicated entries - // in rare cases (less than 1 out of a million). This is harmless. + // Please note that following code is racy and could yield to rare (less + // than 1 out of a million) duplicated entries in SMP case. This is harmless. - // Be sure countermoves and followupmoves are different from killers + // Be sure countermoves are different from killers for (int i = 0; i < 2; ++i) - if ( countermoves[i] != killers[0] - && countermoves[i] != killers[1]) - *endMoves++ = countermoves[i]; + if ( countermoves[i] != (cur+0)->move + && countermoves[i] != (cur+1)->move) + (end++)->move = countermoves[i]; + // Be sure followupmoves are different from killers and countermoves for (int i = 0; i < 2; ++i) - if ( followupmoves[i] != killers[0] - && followupmoves[i] != killers[1] - && followupmoves[i] != killers[2] - && followupmoves[i] != killers[3]) - *endMoves++ = followupmoves[i]; - break; + if ( followupmoves[i] != (cur+0)->move + && followupmoves[i] != (cur+1)->move + && followupmoves[i] != (cur+2)->move + && followupmoves[i] != (cur+3)->move) + (end++)->move = followupmoves[i]; + return; case QUIETS_1_S1: - endQuiets = endMoves = generate(pos, moves); + endQuiets = end = generate(pos, moves); score(); - endMoves = std::partition(cur, endMoves, [](const ExtMove& m) { return m.value > VALUE_ZERO; }); - insertion_sort(cur, endMoves); - break; + end = std::partition(cur, end, has_positive_value); + insertion_sort(cur, end); + return; case QUIETS_2_S1: - cur = endMoves; - endMoves = endQuiets; + cur = end; + end = endQuiets; if (depth >= 3 * ONE_PLY) - insertion_sort(cur, endMoves); - break; + insertion_sort(cur, end); + return; case BAD_CAPTURES_S1: // Just pick them in reverse order to get MVV/LVA ordering cur = moves + MAX_MOVES - 1; - endMoves = endBadCaptures; - break; + end = endBadCaptures; + return; case EVASIONS_S2: - endMoves = generate(pos, moves); - if (endMoves - moves > 1) + end = generate(pos, moves); + if (end > moves + 1) score(); - break; + return; case QUIET_CHECKS_S3: - endMoves = generate(pos, moves); - break; + end = generate(pos, moves); + return; case EVASION: case QSEARCH_0: case QSEARCH_1: case PROBCUT: case RECAPTURE: stage = STOP; /* Fall through */ case STOP: - endMoves = cur + 1; // Avoid another generate_next_stage() call - break; + end = cur + 1; // Avoid another next_phase() call + return; default: assert(false); @@ -280,7 +299,7 @@ Move MovePicker::next_move() { while (true) { - while (cur == endMoves) + while (cur == end) generate_next_stage(); switch (stage) { @@ -290,19 +309,19 @@ Move MovePicker::next_move() { return ttMove; case CAPTURES_S1: - move = pick_best(cur++, endMoves); + move = pick_best(cur++, end)->move; if (move != ttMove) { if (pos.see_sign(move) >= VALUE_ZERO) return move; // Losing capture, move it to the tail of the array - *endBadCaptures-- = move; + (endBadCaptures--)->move = move; } break; case KILLERS_S1: - move = *cur++; + move = (cur++)->move; if ( move != MOVE_NONE && move != ttMove && pos.pseudo_legal(move) @@ -311,40 +330,40 @@ Move MovePicker::next_move() { break; case QUIETS_1_S1: case QUIETS_2_S1: - move = *cur++; + move = (cur++)->move; if ( move != ttMove - && move != killers[0] - && move != killers[1] - && move != killers[2] - && move != killers[3] - && move != killers[4] - && move != killers[5]) + && move != killers[0].move + && move != killers[1].move + && move != killers[2].move + && move != killers[3].move + && move != killers[4].move + && move != killers[5].move) return move; break; case BAD_CAPTURES_S1: - return *cur--; + return (cur--)->move; case EVASIONS_S2: case CAPTURES_S3: case CAPTURES_S4: - move = pick_best(cur++, endMoves); + move = pick_best(cur++, end)->move; if (move != ttMove) return move; break; case CAPTURES_S5: - move = pick_best(cur++, endMoves); + move = pick_best(cur++, end)->move; if (move != ttMove && pos.see(move) > captureThreshold) return move; break; case CAPTURES_S6: - move = pick_best(cur++, endMoves); + move = pick_best(cur++, end)->move; if (to_sq(move) == recaptureSquare) return move; break; case QUIET_CHECKS_S3: - move = *cur++; + move = (cur++)->move; if (move != ttMove) return move; break; diff --git a/src/movepick.h b/src/movepick.h index 44be9636..a7b25a92 100644 --- a/src/movepick.h +++ b/src/movepick.h @@ -80,10 +80,10 @@ typedef Stats > MovesStats; /// to get a cut-off first. class MovePicker { -public: - MovePicker(const MovePicker&) = delete; - MovePicker& operator=(const MovePicker&) = delete; + MovePicker& operator=(const MovePicker&); // Silence a warning under MSVC + +public: MovePicker(const Position&, Move, Depth, const HistoryStats&, Square); MovePicker(const Position&, Move, const HistoryStats&, PieceType); MovePicker(const Position&, Move, Depth, const HistoryStats&, Move*, Move*, Search::Stack*); @@ -93,8 +93,6 @@ public: private: template void score(); void generate_next_stage(); - ExtMove* begin() { return moves; } - ExtMove* end() { return endMoves; } const Position& pos; const HistoryStats& history; @@ -107,8 +105,8 @@ private: Square recaptureSquare; Value captureThreshold; int stage; - ExtMove *endQuiets, *endBadCaptures; - ExtMove moves[MAX_MOVES], *cur = moves, *endMoves = moves; + ExtMove *cur, *end, *endQuiets, *endBadCaptures; + ExtMove moves[MAX_MOVES]; }; #endif // #ifndef MOVEPICK_H_INCLUDED diff --git a/src/pawns.cpp b/src/pawns.cpp index 0b5c59d3..c5f1b22c 100644 --- a/src/pawns.cpp +++ b/src/pawns.cpp @@ -149,7 +149,7 @@ namespace { isolated = !neighbours; // Test for backward pawn. - // If the pawn is passed, isolated, lever or connected it cannot be + // If the pawn is passed, isolated, connected or a lever it cannot be // backward. If there are friendly pawns behind on adjacent files // it cannot be backward either. if ( (passed | isolated | lever | connected) diff --git a/src/platform.h b/src/platform.h new file mode 100644 index 00000000..47c0b117 --- /dev/null +++ b/src/platform.h @@ -0,0 +1,116 @@ +/* + 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 + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef PLATFORM_H_INCLUDED +#define PLATFORM_H_INCLUDED + +#ifdef _MSC_VER + +// Disable some silly and noisy warnings from MSVC compiler +#pragma warning(disable: 4127) // Conditional expression is constant +#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type +#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' +#pragma warning(disable: 4996) // Function _ftime() may be unsafe + +// MSVC does not support +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#else +# include +#endif + +#ifndef _WIN32 // Linux - Unix + +# include + +inline int64_t system_time_to_msec() { + timeval t; + gettimeofday(&t, NULL); + return t.tv_sec * 1000LL + t.tv_usec / 1000; +} + +# include +typedef pthread_mutex_t Lock; +typedef pthread_cond_t WaitCondition; +typedef pthread_t NativeHandle; +typedef void*(*pt_start_fn)(void*); + +# define lock_init(x) pthread_mutex_init(&(x), NULL) +# define lock_grab(x) pthread_mutex_lock(&(x)) +# define lock_release(x) pthread_mutex_unlock(&(x)) +# define lock_destroy(x) pthread_mutex_destroy(&(x)) +# define cond_destroy(x) pthread_cond_destroy(&(x)) +# define cond_init(x) pthread_cond_init(&(x), NULL) +# define cond_signal(x) pthread_cond_signal(&(x)) +# define cond_wait(x,y) pthread_cond_wait(&(x),&(y)) +# define cond_timedwait(x,y,z) pthread_cond_timedwait(&(x),&(y),z) +# define thread_create(x,f,t) pthread_create(&(x),NULL,(pt_start_fn)f,t) +# define thread_join(x) pthread_join(x, NULL) + +#else // Windows and MinGW + +# include + +inline int64_t system_time_to_msec() { + _timeb t; + _ftime(&t); + return t.time * 1000LL + t.millitm; +} + +#ifndef NOMINMAX +# define NOMINMAX // disable macros min() and max() +#endif + +#define WIN32_LEAN_AND_MEAN +#include +#undef WIN32_LEAN_AND_MEAN +#undef NOMINMAX + +// We use critical sections on Windows to support Windows XP and older versions. +// Unfortunately, cond_wait() is racy between lock_release() and WaitForSingleObject() +// but apart from this they have the same speed performance of SRW locks. +typedef CRITICAL_SECTION Lock; +typedef HANDLE WaitCondition; +typedef HANDLE NativeHandle; + +// On Windows 95 and 98 parameter lpThreadId may not be null +inline DWORD* dwWin9xKludge() { static DWORD dw; return &dw; } + +# define lock_init(x) InitializeCriticalSection(&(x)) +# define lock_grab(x) EnterCriticalSection(&(x)) +# define lock_release(x) LeaveCriticalSection(&(x)) +# define lock_destroy(x) DeleteCriticalSection(&(x)) +# define cond_init(x) { x = CreateEvent(0, FALSE, FALSE, 0); } +# define cond_destroy(x) CloseHandle(x) +# define cond_signal(x) SetEvent(x) +# define cond_wait(x,y) { lock_release(y); WaitForSingleObject(x, INFINITE); lock_grab(y); } +# define cond_timedwait(x,y,z) { lock_release(y); WaitForSingleObject(x,z); lock_grab(y); } +# define thread_create(x,f,t) (x = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)f,t,0,dwWin9xKludge())) +# define thread_join(x) { WaitForSingleObject(x, INFINITE); CloseHandle(x); } + +#endif + +#endif // #ifndef PLATFORM_H_INCLUDED diff --git a/src/position.cpp b/src/position.cpp index 9db41b78..eda26014 100644 --- a/src/position.cpp +++ b/src/position.cpp @@ -19,7 +19,7 @@ #include #include -#include // For std::memset, std::memcmp +#include // For std::memset #include #include @@ -182,7 +182,7 @@ void Position::init() { Position& Position::operator=(const Position& pos) { std::memcpy(this, &pos, sizeof(Position)); - std::memcpy(&startState, st, sizeof(StateInfo)); + startState = *st; st = &startState; nodes = 0; @@ -265,7 +265,7 @@ void Position::set(const string& fenStr, bool isChess960, Thread* th) { else if ((idx = PieceToChar.find(token)) != string::npos) { - put_piece(color_of(Piece(idx)), type_of(Piece(idx)), sq); + put_piece(sq, color_of(Piece(idx)), type_of(Piece(idx))); ++sq; } } @@ -375,13 +375,13 @@ void Position::set_state(StateInfo* si) const { si->psq += psq[color_of(pc)][type_of(pc)][s]; } - if (si->epSquare != SQ_NONE) - si->key ^= Zobrist::enpassant[file_of(si->epSquare)]; + if (ep_square() != SQ_NONE) + si->key ^= Zobrist::enpassant[file_of(ep_square())]; if (sideToMove == BLACK) si->key ^= Zobrist::side; - si->key ^= Zobrist::castling[si->castlingRights]; + si->key ^= Zobrist::castling[st->castlingRights]; for (Bitboard b = pieces(PAWN); b; ) { @@ -498,7 +498,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (attacks_from(s, BLACK) & pieces(WHITE, PAWN)) | (attacks_from(s, WHITE) & pieces(BLACK, PAWN)) | (attacks_from(s) & pieces(KNIGHT)) - | (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) + | (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) | (attacks_from(s) & pieces(KING)); } @@ -566,7 +566,7 @@ bool Position::pseudo_legal(const Move m) const { return MoveList(*this).contains(m); // Is not a promotion, so promotion piece must be empty - if (promotion_type(m) - KNIGHT != NO_PIECE_TYPE) + if (promotion_type(m) - 2 != NO_PIECE_TYPE) return false; // If the 'from' square is not occupied by a piece belonging to the side to @@ -587,7 +587,9 @@ bool Position::pseudo_legal(const Move m) const { return false; if ( !(attacks_from(from, us) & 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)) && empty(to) @@ -632,9 +634,10 @@ bool Position::gives_check(Move m, const CheckInfo& ci) const { Square from = from_sq(m); Square to = to_sq(m); + PieceType pt = type_of(piece_on(from)); // Is there a direct check? - if (ci.checkSq[type_of(piece_on(from))] & to) + if (ci.checkSq[pt] & to) return true; // Is there a discovered check? @@ -684,21 +687,31 @@ bool Position::gives_check(Move m, const CheckInfo& ci) const { /// to a StateInfo object. The move is assumed to be legal. Pseudo-legal /// moves should be filtered out before this function is called. -void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { +void Position::do_move(Move m, StateInfo& newSt) { + + CheckInfo ci(*this); + do_move(m, newSt, gives_check(m, ci)); +} + +void Position::do_move(Move m, StateInfo& newSt, bool moveIsCheck) { assert(is_ok(m)); assert(&newSt != st); ++nodes; - Key k = st->key ^ Zobrist::side; + Key k = st->key; // Copy some fields of the old state to our new StateInfo object except the // ones which are going to be recalculated from scratch anyway and then switch // our state pointer to point to the new (ready to be updated) state. - std::memcpy(&newSt, st, offsetof(StateInfo, key)); + std::memcpy(&newSt, st, StateCopySize64 * sizeof(uint64_t)); + newSt.previous = st; st = &newSt; + // Update side to move + k ^= Zobrist::side; + // Increment ply counters. In particular, rule50 will be reset to zero later on // in case of a capture or a pawn move. ++gamePly; @@ -709,19 +722,20 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { Color them = ~us; Square from = from_sq(m); Square to = to_sq(m); - PieceType pt = type_of(piece_on(from)); + Piece pc = piece_on(from); + PieceType pt = type_of(pc); PieceType captured = type_of(m) == ENPASSANT ? PAWN : type_of(piece_on(to)); - assert(color_of(piece_on(from)) == us); - assert(piece_on(to) == NO_PIECE || color_of(piece_on(to)) == (type_of(m) != CASTLING ? them : us)); + assert(color_of(pc) == us); + assert(piece_on(to) == NO_PIECE || color_of(piece_on(to)) == them || type_of(m) == CASTLING); assert(captured != KING); if (type_of(m) == CASTLING) { - assert(pt == KING); + assert(pc == make_piece(us, KING)); Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); + do_castling(from, to, rfrom, rto); captured = NO_PIECE_TYPE; st->psq += psq[us][ROOK][rto] - psq[us][ROOK][rfrom]; @@ -738,7 +752,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { { if (type_of(m) == ENPASSANT) { - capsq -= pawn_push(us); + capsq += pawn_push(them); assert(pt == PAWN); assert(to == st->epSquare); @@ -746,7 +760,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(piece_on(to) == NO_PIECE); assert(piece_on(capsq) == make_piece(them, PAWN)); - board[capsq] = NO_PIECE; // Not done by remove_piece() + board[capsq] = NO_PIECE; } st->pawnKey ^= Zobrist::psq[them][PAWN][capsq]; @@ -755,12 +769,12 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { st->nonPawnMaterial[them] -= PieceValue[MG][captured]; // Update board and piece lists - remove_piece(them, captured, capsq); + remove_piece(capsq, them, captured); // Update material hash key and prefetch access to materialTable k ^= Zobrist::psq[them][captured][capsq]; st->materialKey ^= Zobrist::psq[them][captured][pieceCount[them][captured]]; - prefetch(thisThread->materialTable[st->materialKey]); + prefetch((char*)thisThread->materialTable[st->materialKey]); // Update incremental scores st->psq -= psq[them][captured][capsq]; @@ -789,16 +803,16 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Move the piece. The tricky Chess960 castling is handled earlier if (type_of(m) != CASTLING) - move_piece(us, pt, from, to); + move_piece(from, to, us, pt); // If the moving piece is a pawn do some special extra work if (pt == PAWN) { // Set en-passant square if the moved pawn can be captured if ( (int(to) ^ int(from)) == 16 - && (attacks_from(to - pawn_push(us), us) & pieces(them, PAWN))) + && (attacks_from(from + pawn_push(us), us) & pieces(them, PAWN))) { - st->epSquare = (from + to) / 2; + st->epSquare = Square((from + to) / 2); k ^= Zobrist::enpassant[file_of(st->epSquare)]; } @@ -809,8 +823,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(relative_rank(us, to) == RANK_8); assert(promotion >= KNIGHT && promotion <= QUEEN); - remove_piece(us, PAWN, to); - put_piece(us, promotion, to); + remove_piece(to, us, PAWN); + put_piece(to, us, promotion); // Update hash keys k ^= Zobrist::psq[us][PAWN][to] ^ Zobrist::psq[us][promotion][to]; @@ -827,7 +841,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update pawn hash key and prefetch access to pawnsTable st->pawnKey ^= Zobrist::psq[us][PAWN][from] ^ Zobrist::psq[us][PAWN][to]; - prefetch(thisThread->pawnsTable[st->pawnKey]); + prefetch((char*)thisThread->pawnsTable[st->pawnKey]); // Reset rule 50 draw counter st->rule50 = 0; @@ -842,8 +856,8 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update the key with the final value st->key = k; - // Calculate checkers bitboard (if move gives check) - st->checkersBB = givesCheck ? attackers_to(king_square(them)) & pieces(us) : 0; + // Calculate checkers bitboard (if move is check) + st->checkersBB = moveIsCheck ? attackers_to(king_square(them)) & pieces(us) : 0; sideToMove = ~sideToMove; @@ -870,23 +884,23 @@ void Position::undo_move(Move m) { if (type_of(m) == PROMOTION) { - assert(relative_rank(us, to) == RANK_8); assert(pt == promotion_type(m)); - assert(pt >= KNIGHT && pt <= QUEEN); + assert(relative_rank(us, to) == RANK_8); + assert(promotion_type(m) >= KNIGHT && promotion_type(m) <= QUEEN); - remove_piece(us, pt, to); - put_piece(us, PAWN, to); + remove_piece(to, us, promotion_type(m)); + put_piece(to, us, PAWN); pt = PAWN; } if (type_of(m) == CASTLING) { Square rfrom, rto; - do_castling(us, from, to, rfrom, rto); + do_castling(from, to, rfrom, rto); } else { - move_piece(us, pt, to, from); // Put the piece back at the source square + move_piece(to, from, us, pt); // Put the piece back at the source square if (st->capturedType) { @@ -900,10 +914,9 @@ void Position::undo_move(Move m) { assert(to == st->previous->epSquare); assert(relative_rank(us, to) == RANK_6); assert(piece_on(capsq) == NO_PIECE); - assert(st->capturedType == PAWN); } - put_piece(~us, st->capturedType, capsq); // Restore the captured piece + put_piece(capsq, ~us, st->capturedType); // Restore the captured piece } } @@ -918,19 +931,19 @@ void Position::undo_move(Move m) { /// Position::do_castling() is a helper used to do/undo a castling move. This /// is a bit tricky, especially in Chess960. template -void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto) { +void Position::do_castling(Square from, Square& to, Square& rfrom, Square& rto) { bool kingSide = to > from; rfrom = to; // Castling is encoded as "king captures friendly rook" - rto = relative_square(us, kingSide ? SQ_F1 : SQ_D1); - to = relative_square(us, kingSide ? SQ_G1 : SQ_C1); + rto = relative_square(sideToMove, kingSide ? SQ_F1 : SQ_D1); + to = relative_square(sideToMove, kingSide ? SQ_G1 : SQ_C1); // Remove both pieces first since squares could overlap in Chess960 - remove_piece(us, KING, Do ? from : to); - remove_piece(us, ROOK, Do ? rfrom : rto); + remove_piece(Do ? from : to, sideToMove, KING); + remove_piece(Do ? rfrom : rto, sideToMove, ROOK); board[Do ? from : to] = board[Do ? rfrom : rto] = NO_PIECE; // Since remove_piece doesn't do it for us - put_piece(us, KING, Do ? to : from); - put_piece(us, ROOK, Do ? rto : rfrom); + put_piece(Do ? to : from, sideToMove, KING); + put_piece(Do ? rto : rfrom, sideToMove, ROOK); } @@ -940,9 +953,9 @@ void Position::do_castling(Color us, Square from, Square& to, Square& rfrom, Squ void Position::do_null_move(StateInfo& newSt) { assert(!checkers()); - assert(&newSt != st); - std::memcpy(&newSt, st, sizeof(StateInfo)); + std::memcpy(&newSt, st, sizeof(StateInfo)); // Fully copy here + newSt.previous = st; st = &newSt; @@ -953,7 +966,7 @@ void Position::do_null_move(StateInfo& newSt) { } st->key ^= Zobrist::side; - prefetch(TT.first_entry(st->key)); + prefetch((char*)TT.first_entry(st->key)); ++st->rule50; st->pliesFromNull = 0; @@ -1063,11 +1076,21 @@ Value Position::see(Move m) const { // Locate and remove the next least valuable attacker captured = min_attacker(byTypeBB, to, stmAttackers, occupied, attackers); + + // Stop before processing a king capture + if (captured == KING) + { + if (stmAttackers == attackers) + ++slIndex; + + break; + } + stm = ~stm; stmAttackers = attackers & pieces(stm); ++slIndex; - } while (stmAttackers && (captured != KING || (--slIndex, false))); // Stop before a king capture + } while (stmAttackers); // Having built the swap list, we negamax through it to find the best // achievable score from the point of view of the side to move. @@ -1102,6 +1125,10 @@ bool Position::is_draw() const { /// Position::flip() flips position with the white and black sides reversed. This /// is only useful for debugging e.g. for finding evaluation symmetry bugs. +static char toggle_case(char c) { + return char(islower(c) ? toupper(c) : tolower(c)); +} + void Position::flip() { string f, token; @@ -1119,8 +1146,7 @@ void Position::flip() { ss >> token; // Castling availability f += token + " "; - std::transform(f.begin(), f.end(), f.begin(), - [](char c) { return char(islower(c) ? toupper(c) : tolower(c)); }); + std::transform(f.begin(), f.end(), f.begin(), toggle_case); ss >> token; // En passant square f += (token == "-" ? token : token.replace(1, 1, token[1] == '3' ? "6" : "3")); @@ -1137,77 +1163,96 @@ void Position::flip() { /// Position::pos_is_ok() performs some consistency checks for the position object. /// This is meant to be helpful when debugging. -bool Position::pos_is_ok(int* failedStep) const { +bool Position::pos_is_ok(int* step) const { - const bool Fast = true; // Quick (default) or full check? + // Which parts of the position should be verified? + const bool all = false; - enum { Default, King, Bitboards, State, Lists, Castling }; + const bool testBitboards = all || false; + const bool testState = all || false; + const bool testKingCount = all || false; + const bool testKingCapture = all || false; + const bool testPieceCounts = all || false; + const bool testPieceList = all || false; + const bool testCastlingSquares = all || false; - for (int step = Default; step <= (Fast ? Default : Castling); step++) + if (step) + *step = 1; + + if ( (sideToMove != WHITE && sideToMove != BLACK) + || piece_on(king_square(WHITE)) != W_KING + || piece_on(king_square(BLACK)) != B_KING + || ( ep_square() != SQ_NONE + && relative_rank(sideToMove, ep_square()) != RANK_6)) + return false; + + if (step && ++*step, testBitboards) { - if (failedStep) - *failedStep = step; + // The intersection of the white and black pieces must be empty + if (pieces(WHITE) & pieces(BLACK)) + return false; - if (step == Default) - if ( (sideToMove != WHITE && sideToMove != BLACK) - || piece_on(king_square(WHITE)) != W_KING - || piece_on(king_square(BLACK)) != B_KING - || ( ep_square() != SQ_NONE - && relative_rank(sideToMove, ep_square()) != RANK_6)) - return false; + // The union of the white and black pieces must be equal to all + // occupied squares + if ((pieces(WHITE) | pieces(BLACK)) != pieces()) + return false; - if (step == King) - if ( std::count(board, board + SQUARE_NB, W_KING) != 1 - || std::count(board, board + SQUARE_NB, B_KING) != 1 - || attackers_to(king_square(~sideToMove)) & pieces(sideToMove)) - return false; - - if (step == Bitboards) - { - if ( (pieces(WHITE) & pieces(BLACK)) - ||(pieces(WHITE) | pieces(BLACK)) != pieces()) - return false; - - for (PieceType p1 = PAWN; p1 <= KING; ++p1) - for (PieceType p2 = PAWN; p2 <= KING; ++p2) - if (p1 != p2 && (pieces(p1) & pieces(p2))) - return false; - } - - if (step == State) - { - StateInfo si = *st; - set_state(&si); - if (std::memcmp(&si, st, sizeof(StateInfo))) - return false; - } - - if (step == Lists) - for (Color c = WHITE; c <= BLACK; ++c) - for (PieceType pt = PAWN; pt <= KING; ++pt) - { - if (pieceCount[c][pt] != popcount(pieces(c, pt))) - return false; - - for (int i = 0; i < pieceCount[c][pt]; ++i) - if ( board[pieceList[c][pt][i]] != make_piece(c, pt) - || index[pieceList[c][pt][i]] != i) - return false; - } - - if (step == Castling) - for (Color c = WHITE; c <= BLACK; ++c) - for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) - { - if (!can_castle(c | s)) - continue; - - if ( piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK) - || castlingRightsMask[castlingRookSquare[c | s]] != (c | s) - ||(castlingRightsMask[king_square(c)] & (c | s)) != (c | s)) - return false; - } + // Separate piece type bitboards must have empty intersections + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + return false; } + if (step && ++*step, testState) + { + StateInfo si; + set_state(&si); + if ( st->key != si.key + || st->pawnKey != si.pawnKey + || st->materialKey != si.materialKey + || st->nonPawnMaterial[WHITE] != si.nonPawnMaterial[WHITE] + || st->nonPawnMaterial[BLACK] != si.nonPawnMaterial[BLACK] + || st->psq != si.psq + || st->checkersBB != si.checkersBB) + return false; + } + + if (step && ++*step, testKingCount) + if ( std::count(board, board + SQUARE_NB, W_KING) != 1 + || std::count(board, board + SQUARE_NB, B_KING) != 1) + return false; + + if (step && ++*step, testKingCapture) + if (attackers_to(king_square(~sideToMove)) & pieces(sideToMove)) + return false; + + if (step && ++*step, testPieceCounts) + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + if (pieceCount[c][pt] != popcount(pieces(c, pt))) + return false; + + if (step && ++*step, testPieceList) + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt <= KING; ++pt) + for (int i = 0; i < pieceCount[c][pt]; ++i) + if ( board[pieceList[c][pt][i]] != make_piece(c, pt) + || index[pieceList[c][pt][i]] != i) + return false; + + if (step && ++*step, testCastlingSquares) + for (Color c = WHITE; c <= BLACK; ++c) + for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) + { + if (!can_castle(c | s)) + continue; + + if ( (castlingRightsMask[king_square(c)] & (c | s)) != (c | s) + || piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[c | s]] != (c | s)) + return false; + } + return true; } diff --git a/src/position.h b/src/position.h index ece3f442..14ce8099 100644 --- a/src/position.h +++ b/src/position.h @@ -68,6 +68,11 @@ struct StateInfo { }; +/// When making a move the current StateInfo up to 'key' excluded is copied to +/// the new one. Here we calculate the quad words (64 bit) needed to be copied. +const size_t StateCopySize64 = offsetof(StateInfo, key) / sizeof(uint64_t) + 1; + + /// Position class stores information regarding the board representation as /// pieces, side to move, hash keys, castling info, etc. Important methods are /// do_move() and undo_move(), used by the search to update node info when @@ -77,11 +82,12 @@ class Position { friend std::ostream& operator<<(std::ostream&, const Position&); + Position(const Position&); // Disable the default copy constructor + public: static void init(); - Position() = default; // To define the global object RootPos - Position(const Position&) = delete; + Position() {} // To define the global object RootPos Position(const Position& pos, Thread* th) { *this = pos; thisThread = th; } Position(const std::string& f, bool c960, Thread* th) { set(f, c960, th); } Position& operator=(const Position&); // To assign RootPos from UCI @@ -138,7 +144,8 @@ public: bool opposite_bishops() const; // Doing and undoing moves - void do_move(Move m, StateInfo& st, bool givesCheck); + void do_move(Move m, StateInfo& st); + void do_move(Move m, StateInfo& st, bool moveIsCheck); void undo_move(Move m); void do_null_move(StateInfo& st); void undo_null_move(); @@ -168,7 +175,7 @@ public: Value non_pawn_material(Color c) const; // Position consistency check, for debugging - bool pos_is_ok(int* failedStep = nullptr) const; + bool pos_is_ok(int* step = NULL) const; void flip(); private: @@ -179,11 +186,11 @@ private: // Other helpers Bitboard check_blockers(Color c, Color kingColor) const; - void put_piece(Color c, PieceType pt, Square s); - void remove_piece(Color c, PieceType pt, Square s); - void move_piece(Color c, PieceType pt, Square from, Square to); + void put_piece(Square s, Color c, PieceType pt); + void remove_piece(Square s, Color c, PieceType pt); + void move_piece(Square from, Square to, Color c, PieceType pt); template - void do_castling(Color us, Square from, Square& to, Square& rfrom, Square& rto); + void do_castling(Square from, Square& to, Square& rfrom, Square& rto); // Data members Piece board[SQUARE_NB]; @@ -388,7 +395,7 @@ inline Thread* Position::this_thread() const { return thisThread; } -inline void Position::put_piece(Color c, PieceType pt, Square s) { +inline void Position::put_piece(Square s, Color c, PieceType pt) { board[s] = make_piece(c, pt); byTypeBB[ALL_PIECES] |= s; @@ -399,7 +406,21 @@ inline void Position::put_piece(Color c, PieceType pt, Square s) { pieceCount[c][ALL_PIECES]++; } -inline void Position::remove_piece(Color c, PieceType pt, Square s) { +inline void Position::move_piece(Square from, Square to, Color c, PieceType pt) { + + // index[from] is not updated and becomes stale. This works as long as index[] + // is accessed just by known occupied squares. + Bitboard from_to_bb = SquareBB[from] ^ SquareBB[to]; + byTypeBB[ALL_PIECES] ^= from_to_bb; + byTypeBB[pt] ^= from_to_bb; + byColorBB[c] ^= from_to_bb; + board[from] = NO_PIECE; + board[to] = make_piece(c, pt); + index[to] = index[from]; + pieceList[c][pt][index[to]] = to; +} + +inline void Position::remove_piece(Square s, Color c, PieceType pt) { // WARNING: This is not a reversible operation. If we remove a piece in // do_move() and then replace it in undo_move() we will put it at the end of @@ -416,18 +437,4 @@ inline void Position::remove_piece(Color c, PieceType pt, Square s) { pieceCount[c][ALL_PIECES]--; } -inline void Position::move_piece(Color c, PieceType pt, Square from, Square to) { - - // index[from] is not updated and becomes stale. This works as long as index[] - // is accessed just by known occupied squares. - Bitboard from_to_bb = SquareBB[from] ^ SquareBB[to]; - byTypeBB[ALL_PIECES] ^= from_to_bb; - byTypeBB[pt] ^= from_to_bb; - byColorBB[c] ^= from_to_bb; - board[from] = NO_PIECE; - board[to] = make_piece(c, pt); - index[to] = index[from]; - pieceList[c][pt][index[to]] = to; -} - #endif // #ifndef POSITION_H_INCLUDED diff --git a/src/search.cpp b/src/search.cpp index 681acb81..43a3175b 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -41,7 +41,7 @@ namespace Search { LimitsType Limits; RootMoveVector RootMoves; Position RootPos; - TimePoint SearchTime; + Time::point SearchTime; StateStackPtr SetupStates; } @@ -66,29 +66,22 @@ namespace { // Different node types, used as template parameter enum NodeType { Root, PV, NonPV }; - // Razoring and futility margin based on depth + // Dynamic razoring margin based on depth inline Value razor_margin(Depth d) { return Value(512 + 32 * d); } - inline Value futility_margin(Depth d) { return Value(200 * d); } - // Futility and reductions lookup tables, initialized at startup - int FutilityMoveCounts[2][16]; // [improving][depth] - Depth Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber] + // Futility lookup tables (initialized at startup) and their access functions + int FutilityMoveCounts[2][16]; // [improving][depth] - template inline Depth reduction(bool i, Depth d, int mn) { - return Reductions[PvNode][i][std::min(d, 63 * ONE_PLY)][std::min(mn, 63)]; + inline Value futility_margin(Depth d) { + return Value(200 * d); } - // Skill struct is used to implement strength limiting - struct Skill { - Skill(int l) : level(l) {} - bool enabled() const { return level < 20; } - bool time_to_pick(Depth depth) const { return depth / ONE_PLY == 1 + level; } - Move best_move(size_t multiPV) { return best ? best : pick_best(multiPV); } - Move pick_best(size_t multiPV); + // Reduction lookup tables (initialized at startup) and their access function + int8_t Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber] - int level; - Move best = MOVE_NONE; - }; + template inline Depth reduction(bool i, Depth d, int mn) { + return (Depth) Reductions[PvNode][i][std::min(int(d), 63)][std::min(mn, 63)]; + } size_t PVIdx; TimeManager TimeMgr; @@ -109,6 +102,26 @@ namespace { Value value_from_tt(Value v, int ply); void update_pv(Move* pv, Move move, Move* childPv); void update_stats(const Position& pos, Stack* ss, Move move, Depth depth, Move* quiets, int quietsCnt); + string uci_pv(const Position& pos, Depth depth, Value alpha, Value beta); + + struct Skill { + Skill(int l, size_t rootSize) : level(l), + candidates(l < 20 ? std::min(4, (int)rootSize) : 0), + best(MOVE_NONE) {} + ~Skill() { + if (candidates) // Swap best PV line with the sub-optimal one + std::swap(RootMoves[0], *std::find(RootMoves.begin(), + RootMoves.end(), best ? best : pick_move())); + } + + size_t candidates_size() const { return candidates; } + bool time_to_pick(Depth depth) const { return depth / ONE_PLY == 1 + level; } + Move pick_move(); + + int level; + size_t candidates; + Move best; + }; } // namespace @@ -117,23 +130,25 @@ namespace { void Search::init() { - const double K[][2] = {{ 0.83, 2.25 }, { 0.50, 3.00 }}; + // Init reductions array + for (int d = 1; d < 64; ++d) + for (int mc = 1; mc < 64; ++mc) + { + double pvRed = 0.00 + log(double(d)) * log(double(mc)) / 3.00; + double nonPVRed = 0.33 + log(double(d)) * log(double(mc)) / 2.25; - for (int pv = 0; pv <= 1; ++pv) - for (int imp = 0; imp <= 1; ++imp) - for (int d = 1; d < 64; ++d) - for (int mc = 1; mc < 64; ++mc) - { - double r = K[pv][0] + log(d) * log(mc) / K[pv][1]; + Reductions[1][1][d][mc] = int8_t( pvRed >= 1.0 ? pvRed + 0.5: 0); + Reductions[0][1][d][mc] = int8_t(nonPVRed >= 1.0 ? nonPVRed + 0.5: 0); - if (r >= 1.5) - Reductions[pv][imp][d][mc] = int(r) * ONE_PLY; + Reductions[1][0][d][mc] = Reductions[1][1][d][mc]; + Reductions[0][0][d][mc] = Reductions[0][1][d][mc]; - // Increase reduction when eval is not improving - if (!pv && !imp && Reductions[pv][imp][d][mc] >= 2 * ONE_PLY) - Reductions[pv][imp][d][mc] += ONE_PLY; - } + // Increase reduction when eval is not improving + if (Reductions[0][0][d][mc] >= 2) + Reductions[0][0][d][mc] += 1; + } + // Init futility move count array for (int d = 0; d < 16; ++d) { FutilityMoveCounts[0][d] = int(2.4 + 0.773 * pow(d + 0.00, 1.8)); @@ -152,19 +167,19 @@ uint64_t Search::perft(Position& pos, Depth depth) { CheckInfo ci(pos); const bool leaf = (depth == 2 * ONE_PLY); - for (const auto& m : MoveList(pos)) + for (MoveList it(pos); *it; ++it) { if (Root && depth <= ONE_PLY) cnt = 1, nodes++; else { - pos.do_move(m, st, pos.gives_check(m, ci)); + pos.do_move(*it, st, pos.gives_check(*it, ci)); cnt = leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); nodes += cnt; - pos.undo_move(m); + pos.undo_move(*it); } if (Root) - sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + sync_cout << UCI::move(*it, pos.is_chess960()) << ": " << cnt << sync_endl; } return nodes; } @@ -199,7 +214,7 @@ void Search::think() { if (RootMoves.empty()) { - RootMoves.push_back(RootMove(MOVE_NONE)); + RootMoves.push_back(MOVE_NONE); sync_cout << "info depth 0 score " << UCI::value(RootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; @@ -237,8 +252,8 @@ void Search::think() { } } - for (Thread* th : Threads) - th->maxPly = 0; + for (size_t i = 0; i < Threads.size(); ++i) + Threads[i]->maxPly = 0; Threads.timer->run = true; Threads.timer->notify_one(); // Wake up the recurring timer @@ -294,14 +309,11 @@ namespace { Followupmoves.clear(); size_t multiPV = Options["MultiPV"]; - Skill skill(Options["Skill Level"]); + Skill skill(Options["Skill Level"], RootMoves.size()); - // When playing with strength handicap enable MultiPV search that we will - // use behind the scenes to retrieve a set of possible moves. - if (skill.enabled()) - multiPV = std::max(multiPV, (size_t)4); - - multiPV = std::min(multiPV, RootMoves.size()); + // Do we have to play with skill handicap? In this case enable MultiPV search + // that we will use behind the scenes to retrieve a set of possible moves. + multiPV = std::max(multiPV, skill.candidates_size()); // Iterative deepening loop until requested to stop or target depth reached while (++depth < DEPTH_MAX && !Signals.stop && (!Limits.depth || depth <= Limits.depth)) @@ -311,11 +323,11 @@ namespace { // Save the last iteration's scores before first PV line is searched and // all the move scores except the (new) PV are set to -VALUE_INFINITE. - for (RootMove& rm : RootMoves) - rm.previousScore = rm.score; + for (size_t i = 0; i < RootMoves.size(); ++i) + RootMoves[i].previousScore = RootMoves[i].score; // MultiPV loop. We perform a full root search for each PV line - for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx) + for (PVIdx = 0; PVIdx < std::min(multiPV, RootMoves.size()) && !Signals.stop; ++PVIdx) { // Reset aspiration window starting size if (depth >= 5 * ONE_PLY) @@ -355,8 +367,8 @@ namespace { // the UI) before a re-search. if ( multiPV == 1 && (bestValue <= alpha || bestValue >= beta) - && now() - SearchTime > 3000) - sync_cout << UCI::pv(pos, depth, alpha, beta) << sync_endl; + && Time::now() - SearchTime > 3000) + sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; // In case of failing low/high increase aspiration window and // re-search, otherwise exit the loop. @@ -386,15 +398,16 @@ namespace { if (Signals.stop) sync_cout << "info nodes " << RootPos.nodes_searched() - << " time " << now() - SearchTime << sync_endl; + << " time " << Time::now() - SearchTime << sync_endl; - else if (PVIdx + 1 == multiPV || now() - SearchTime > 3000) - sync_cout << UCI::pv(pos, depth, alpha, beta) << sync_endl; + else if ( PVIdx + 1 == std::min(multiPV, RootMoves.size()) + || Time::now() - SearchTime > 3000) + sync_cout << uci_pv(pos, depth, alpha, beta) << sync_endl; } - // If skill level is enabled and time is up, pick a sub-optimal best move - if (skill.enabled() && skill.time_to_pick(depth)) - skill.pick_best(multiPV); + // If skill levels are enabled and time is up, pick a sub-optimal best move + if (skill.candidates_size() && skill.time_to_pick(depth)) + skill.pick_move(); // Have we found a "mate in x"? if ( Limits.mate @@ -412,7 +425,7 @@ namespace { // Stop the search if only one legal move is available or all // of the available time has been used. if ( RootMoves.size() == 1 - || now() - SearchTime > TimeMgr.available_time()) + || Time::now() - SearchTime > TimeMgr.available_time()) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". @@ -423,11 +436,6 @@ namespace { } } } - - // If skill level is enabled, swap best PV line with the sub-optimal one - if (skill.enabled()) - std::swap(RootMoves[0], *std::find(RootMoves.begin(), - RootMoves.end(), skill.best_move(multiPV))); } @@ -469,7 +477,7 @@ namespace { splitPoint = ss->splitPoint; bestMove = splitPoint->bestMove; bestValue = splitPoint->bestValue; - tte = nullptr; + tte = NULL; ttHit = false; ttMove = excludedMove = MOVE_NONE; ttValue = VALUE_NONE; @@ -532,7 +540,7 @@ namespace { // If ttMove is quiet, update killers, history, counter move and followup move on TT hit if (ttValue >= beta && ttMove && !pos.capture_or_promotion(ttMove) && !inCheck) - update_stats(pos, ss, ttMove, depth, nullptr, 0); + update_stats(pos, ss, ttMove, depth, NULL, 0); return ttValue; } @@ -765,7 +773,7 @@ moves_loop: // When in check and at SpNode search starts from here continue; moveCount = ++splitPoint->moveCount; - splitPoint->spinlock.release(); + splitPoint->mutex.unlock(); } else ++moveCount; @@ -774,14 +782,14 @@ moves_loop: // When in check and at SpNode search starts from here { Signals.firstRootMove = (moveCount == 1); - if (thisThread == Threads.main() && now() - SearchTime > 3000) + if (thisThread == Threads.main() && Time::now() - SearchTime > 3000) sync_cout << "info depth " << depth / ONE_PLY << " currmove " << UCI::move(move, pos.is_chess960()) << " currmovenumber " << moveCount + PVIdx << sync_endl; } if (PvNode) - (ss+1)->pv = nullptr; + (ss+1)->pv = NULL; extension = DEPTH_ZERO; captureOrPromotion = pos.capture_or_promotion(move); @@ -834,7 +842,7 @@ moves_loop: // When in check and at SpNode search starts from here && moveCount >= FutilityMoveCounts[improving][depth]) { if (SpNode) - splitPoint->spinlock.acquire(); + splitPoint->mutex.lock(); continue; } @@ -853,7 +861,7 @@ moves_loop: // When in check and at SpNode search starts from here if (SpNode) { - splitPoint->spinlock.acquire(); + splitPoint->mutex.lock(); if (bestValue > splitPoint->bestValue) splitPoint->bestValue = bestValue; } @@ -865,14 +873,14 @@ moves_loop: // When in check and at SpNode search starts from here if (predictedDepth < 4 * ONE_PLY && pos.see_sign(move) < VALUE_ZERO) { if (SpNode) - splitPoint->spinlock.acquire(); + splitPoint->mutex.lock(); continue; } } // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + prefetch((char*)TT.first_entry(pos.key_after(move))); // Check for legality just before making the move if (!RootNode && !SpNode && !pos.legal(move, ci.pinned)) @@ -965,7 +973,7 @@ moves_loop: // When in check and at SpNode search starts from here // Step 18. Check for new best move if (SpNode) { - splitPoint->spinlock.acquire(); + splitPoint->mutex.lock(); bestValue = splitPoint->bestValue; alpha = splitPoint->alpha; } @@ -1240,7 +1248,7 @@ moves_loop: // When in check and at SpNode search starts from here continue; // Speculative prefetch as early as possible - prefetch(TT.first_entry(pos.key_after(move))); + prefetch((char*)TT.first_entry(pos.key_after(move))); // Check for legality just before making the move if (!pos.legal(move, ci.pinned)) @@ -1346,7 +1354,6 @@ moves_loop: // When in check and at SpNode search starts from here // played quiet moves. Value bonus = Value((depth / ONE_PLY) * (depth / ONE_PLY)); History.update(pos.moved_piece(move), to_sq(move), bonus); - for (int i = 0; i < quietsCnt; ++i) { Move m = quiets[i]; @@ -1367,95 +1374,98 @@ moves_loop: // When in check and at SpNode search starts from here } - // When playing with strength handicap, choose best move among a set of RootMoves - // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. + // When playing with a strength handicap, choose best move among the first 'candidates' + // RootMoves using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. - Move Skill::pick_best(size_t multiPV) { + Move Skill::pick_move() { // PRNG sequence should be non-deterministic, so we seed it with the time at init - static PRNG rng(now()); + static PRNG rng(Time::now()); // RootMoves are already sorted by score in descending order - int variance = std::min(RootMoves[0].score - RootMoves[multiPV - 1].score, PawnValueMg); + int variance = std::min(RootMoves[0].score - RootMoves[candidates - 1].score, PawnValueMg); int weakness = 120 - 2 * level; int maxScore = -VALUE_INFINITE; + best = MOVE_NONE; // Choose best move. For each move score we add two terms both dependent on - // weakness. One deterministic and bigger for weaker levels, and one random, + // weakness. One deterministic and bigger for weaker moves, and one random, // then we choose the move with the resulting highest score. - for (size_t i = 0; i < multiPV; ++i) + for (size_t i = 0; i < candidates; ++i) { - // This is our magic formula - int push = ( weakness * int(RootMoves[0].score - RootMoves[i].score) - + variance * (rng.rand() % weakness)) / 128; + int score = RootMoves[i].score; - if (RootMoves[i].score + push > maxScore) + // This is our magic formula + score += ( weakness * int(RootMoves[0].score - score) + + variance * (rng.rand() % weakness)) / 128; + + if (score > maxScore) { - maxScore = RootMoves[i].score + push; + maxScore = score; best = RootMoves[i].pv[0]; } } return best; } -} // namespace + // uci_pv() formats PV information according to the UCI protocol. UCI + // requires that all (if any) unsearched PV lines are sent using a previous + // search score. -/// UCI::pv() formats PV information according to the UCI protocol. UCI requires -/// that all (if any) unsearched PV lines are sent using a previous search score. + string uci_pv(const Position& pos, Depth depth, Value alpha, Value beta) { -string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { + std::stringstream ss; + Time::point elapsed = Time::now() - SearchTime + 1; + size_t uciPVSize = std::min((size_t)Options["MultiPV"], RootMoves.size()); + int selDepth = 0; - std::stringstream ss; - TimePoint elapsed = now() - SearchTime + 1; - size_t multiPV = std::min((size_t)Options["MultiPV"], RootMoves.size()); - int selDepth = 0; + for (size_t i = 0; i < Threads.size(); ++i) + if (Threads[i]->maxPly > selDepth) + selDepth = Threads[i]->maxPly; - for (Thread* th : Threads) - if (th->maxPly > selDepth) - selDepth = th->maxPly; + for (size_t i = 0; i < uciPVSize; ++i) + { + bool updated = (i <= PVIdx); - for (size_t i = 0; i < multiPV; ++i) - { - bool updated = (i <= PVIdx); + if (depth == ONE_PLY && !updated) + continue; - if (depth == ONE_PLY && !updated) - continue; + Depth d = updated ? depth : depth - ONE_PLY; + Value v = updated ? RootMoves[i].score : RootMoves[i].previousScore; - Depth d = updated ? depth : depth - ONE_PLY; - Value v = updated ? RootMoves[i].score : RootMoves[i].previousScore; + bool tb = TB::RootInTB && abs(v) < VALUE_MATE - MAX_PLY; + v = tb ? TB::Score : v; - bool tb = TB::RootInTB && abs(v) < VALUE_MATE - MAX_PLY; - v = tb ? TB::Score : v; + if (ss.rdbuf()->in_avail()) // Not at first line + ss << "\n"; - if (ss.rdbuf()->in_avail()) // Not at first line - ss << "\n"; + ss << "info depth " << d / ONE_PLY + << " seldepth " << selDepth + << " multipv " << i + 1 + << " score " << UCI::value(v); - ss << "info" - << " depth " << d / ONE_PLY - << " seldepth " << selDepth - << " multipv " << i + 1 - << " score " << UCI::value(v); + if (!tb && i == PVIdx) + ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); - if (!tb && i == PVIdx) - ss << (v >= beta ? " lowerbound" : v <= alpha ? " upperbound" : ""); + ss << " nodes " << pos.nodes_searched() + << " nps " << pos.nodes_searched() * 1000 / elapsed; - ss << " nodes " << pos.nodes_searched() - << " nps " << pos.nodes_searched() * 1000 / elapsed; + if (elapsed > 1000) // Earlier makes little sense + ss << " hashfull " << TT.hashfull(); - if (elapsed > 1000) // Earlier makes little sense - ss << " hashfull " << TT.hashfull(); + ss << " tbhits " << TB::Hits + << " time " << elapsed + << " pv"; - ss << " tbhits " << TB::Hits - << " time " << elapsed - << " pv"; + for (size_t j = 0; j < RootMoves[i].pv.size(); ++j) + ss << " " << UCI::move(RootMoves[i].pv[j], pos.is_chess960()); + } - for (Move m : RootMoves[i].pv) - ss << " " << UCI::move(m, pos.is_chess960()); + return ss.str(); } - return ss.str(); -} +} // namespace /// RootMove::insert_pv_in_tt() is called at the end of a search iteration, and @@ -1465,22 +1475,22 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { void RootMove::insert_pv_in_tt(Position& pos) { StateInfo state[MAX_PLY], *st = state; - bool ttHit; + size_t idx = 0; - for (Move m : pv) + for ( ; idx < pv.size(); ++idx) { - assert(MoveList(pos).contains(m)); - + bool ttHit; TTEntry* tte = TT.probe(pos.key(), ttHit); - if (!ttHit || tte->move() != m) // Don't overwrite correct entries - tte->save(pos.key(), VALUE_NONE, BOUND_NONE, DEPTH_NONE, m, VALUE_NONE, TT.generation()); + if (!ttHit || tte->move() != pv[idx]) // Don't overwrite correct entries + tte->save(pos.key(), VALUE_NONE, BOUND_NONE, DEPTH_NONE, pv[idx], VALUE_NONE, TT.generation()); - pos.do_move(m, *st++, pos.gives_check(m, CheckInfo(pos))); + assert(MoveList(pos).contains(pv[idx])); + + pos.do_move(pv[idx], *st++); } - for (size_t i = pv.size(); i > 0; ) - pos.undo_move(pv[--i]); + while (idx) pos.undo_move(pv[--idx]); } @@ -1489,25 +1499,22 @@ void RootMove::insert_pv_in_tt(Position& pos) { /// root. We try hard to have a ponder move to return to the GUI, otherwise in case of /// 'ponder on' we have nothing to think on. -bool RootMove::extract_ponder_from_tt(Position& pos) +Move RootMove::extract_ponder_from_tt(Position& pos) { StateInfo st; - bool ttHit; + bool found; assert(pv.size() == 1); - pos.do_move(pv[0], st, pos.gives_check(pv[0], CheckInfo(pos))); - TTEntry* tte = TT.probe(pos.key(), ttHit); + pos.do_move(pv[0], st); + TTEntry* tte = TT.probe(pos.key(), found); + Move m = found ? tte->move() : MOVE_NONE; + if (!MoveList(pos).contains(m)) + m = MOVE_NONE; + pos.undo_move(pv[0]); - - if (ttHit) - { - Move m = tte->move(); // Local copy to be SMP safe - if (MoveList(pos).contains(m)) - return pv.push_back(m), true; - } - - return false; + pv.push_back(m); + return m; } @@ -1526,13 +1533,13 @@ void Thread::idle_loop() { // If this thread has been assigned work, launch a search while (searching) { - Threads.spinlock.acquire(); + Threads.mutex.lock(); assert(activeSplitPoint); SplitPoint* sp = activeSplitPoint; - Threads.spinlock.release(); + Threads.mutex.unlock(); Stack stack[MAX_PLY+4], *ss = stack+2; // To allow referencing (ss-2) and (ss+2) Position pos(*sp->pos, this); @@ -1540,9 +1547,9 @@ void Thread::idle_loop() { std::memcpy(ss-2, sp->ss-2, 5 * sizeof(Stack)); ss->splitPoint = sp; - sp->spinlock.acquire(); + sp->mutex.lock(); - assert(activePosition == nullptr); + assert(activePosition == NULL); activePosition = &pos; @@ -1561,7 +1568,7 @@ void Thread::idle_loop() { assert(searching); searching = false; - activePosition = nullptr; + activePosition = NULL; sp->slavesMask.reset(idx); sp->allSlavesSearching = false; sp->nodes += pos.nodes_searched(); @@ -1578,31 +1585,31 @@ void Thread::idle_loop() { // After releasing the lock we can't access any SplitPoint related data // in a safe way because it could have been released under our feet by // the sp master. - sp->spinlock.release(); + sp->mutex.unlock(); // Try to late join to another split point if none of its slaves has // already finished. SplitPoint* bestSp = NULL; int minLevel = INT_MAX; - for (Thread* th : Threads) + for (size_t i = 0; i < Threads.size(); ++i) { - const size_t size = th->splitPointsSize; // Local copy - sp = size ? &th->splitPoints[size - 1] : nullptr; + const size_t size = Threads[i]->splitPointsSize; // Local copy + sp = size ? &Threads[i]->splitPoints[size - 1] : NULL; if ( sp && sp->allSlavesSearching && sp->slavesMask.count() < MAX_SLAVES_PER_SPLITPOINT - && can_join(sp)) + && available_to(Threads[i])) { - assert(this != th); + assert(this != Threads[i]); assert(!(this_sp && this_sp->slavesMask.none())); assert(Threads.size() > 2); // Prefer to join to SP with few parents to reduce the probability // that a cut-off occurs above us, and hence we waste our work. int level = 0; - for (SplitPoint* p = th->activeSplitPoint; p; p = p->parentSplitPoint) + for (SplitPoint* p = Threads[i]->activeSplitPoint; p; p = p->parentSplitPoint) level++; if (level < minLevel) @@ -1618,37 +1625,40 @@ void Thread::idle_loop() { sp = bestSp; // Recheck the conditions under lock protection - Threads.spinlock.acquire(); - sp->spinlock.acquire(); + Threads.mutex.lock(); + sp->mutex.lock(); if ( sp->allSlavesSearching && sp->slavesMask.count() < MAX_SLAVES_PER_SPLITPOINT - && can_join(sp)) + && available_to(sp->master)) { sp->slavesMask.set(idx); activeSplitPoint = sp; searching = true; } - sp->spinlock.release(); - Threads.spinlock.release(); + sp->mutex.unlock(); + Threads.mutex.unlock(); } } // Avoid races with notify_one() fired from last slave of the split point - std::unique_lock lk(mutex); + mutex.lock(); // If we are master and all slaves have finished then exit idle_loop if (this_sp && this_sp->slavesMask.none()) { assert(!searching); + mutex.unlock(); break; } // If we are not searching, wait for a condition to be signaled instead of // wasting CPU time polling for work. if (!searching && !exit) - sleepCondition.wait(lk); + sleepCondition.wait(mutex); + + mutex.unlock(); } } @@ -1659,12 +1669,12 @@ void Thread::idle_loop() { void check_time() { - static TimePoint lastInfoTime = now(); - TimePoint elapsed = now() - SearchTime; + static Time::point lastInfoTime = Time::now(); + Time::point elapsed = Time::now() - SearchTime; - if (now() - lastInfoTime >= 1000) + if (Time::now() - lastInfoTime >= 1000) { - lastInfoTime = now(); + lastInfoTime = Time::now(); dbg_print(); } @@ -1687,18 +1697,18 @@ void check_time() { else if (Limits.nodes) { - Threads.spinlock.acquire(); + Threads.mutex.lock(); int64_t nodes = RootPos.nodes_searched(); // Loop across all split points and sum accumulated SplitPoint nodes plus // all the currently active positions nodes. - for (Thread* th : Threads) - for (size_t i = 0; i < th->splitPointsSize; ++i) + for (size_t i = 0; i < Threads.size(); ++i) + for (size_t j = 0; j < Threads[i]->splitPointsSize; ++j) { - SplitPoint& sp = th->splitPoints[i]; + SplitPoint& sp = Threads[i]->splitPoints[j]; - sp.spinlock.acquire(); + sp.mutex.lock(); nodes += sp.nodes; @@ -1706,10 +1716,10 @@ void check_time() { if (sp.slavesMask.test(idx) && Threads[idx]->activePosition) nodes += Threads[idx]->activePosition->nodes_searched(); - sp.spinlock.release(); + sp.mutex.unlock(); } - Threads.spinlock.release(); + Threads.mutex.unlock(); if (nodes >= Limits.nodes) Signals.stop = true; diff --git a/src/search.h b/src/search.h index e233c1c8..5a0ad5df 100644 --- a/src/search.h +++ b/src/search.h @@ -55,15 +55,15 @@ struct Stack { struct RootMove { - explicit RootMove(Move m) : pv(1, m) {} + RootMove(Move m) : score(-VALUE_INFINITE), previousScore(-VALUE_INFINITE), pv(1, m) {} bool operator<(const RootMove& m) const { return score > m.score; } // Ascending sort bool operator==(const Move& m) const { return pv[0] == m; } void insert_pv_in_tt(Position& pos); - bool extract_ponder_from_tt(Position& pos); + Move extract_ponder_from_tt(Position& pos); - Value score = -VALUE_INFINITE; - Value previousScore = -VALUE_INFINITE; + Value score; + Value previousScore; std::vector pv; }; @@ -96,13 +96,13 @@ struct SignalsType { bool stop, stopOnPonderhit, firstRootMove, failedLowAtRoot; }; -typedef std::unique_ptr> StateStackPtr; +typedef std::auto_ptr > StateStackPtr; extern volatile SignalsType Signals; extern LimitsType Limits; extern RootMoveVector RootMoves; extern Position RootPos; -extern TimePoint SearchTime; +extern Time::point SearchTime; extern StateStackPtr SetupStates; void init(); diff --git a/src/syzygy/tbprobe.cpp b/src/syzygy/tbprobe.cpp index 646176a9..17326970 100644 --- a/src/syzygy/tbprobe.cpp +++ b/src/syzygy/tbprobe.cpp @@ -7,8 +7,6 @@ this code to other chess engines. */ -#define NOMINMAX - #include #include "../position.h" diff --git a/src/thread.cpp b/src/thread.cpp index ff52576b..786258a2 100644 --- a/src/thread.cpp +++ b/src/thread.cpp @@ -33,13 +33,19 @@ extern void check_time(); namespace { + // start_routine() is the C function which is called when a new thread + // is launched. It is a wrapper to the virtual function idle_loop(). + + extern "C" { long start_routine(ThreadBase* th) { th->idle_loop(); return 0; } } + + // Helpers to launch a thread after creation and joining before delete. Must be // outside Thread c'tor and d'tor because the object must be fully initialized // when start_routine (and hence virtual idle_loop) is called and when joining. template T* new_thread() { T* th = new T(); - th->nativeThread = std::thread(&ThreadBase::idle_loop, th); // Will go to sleep + thread_create(th->handle, start_routine, th); // Will go to sleep return th; } @@ -50,7 +56,7 @@ namespace { th->mutex.unlock(); th->notify_one(); - th->nativeThread.join(); // Wait for thread termination + thread_join(th->handle); // Wait for thread termination delete th; } @@ -61,8 +67,9 @@ namespace { void ThreadBase::notify_one() { - std::unique_lock(this->mutex); + mutex.lock(); sleepCondition.notify_one(); + mutex.unlock(); } @@ -70,8 +77,9 @@ void ThreadBase::notify_one() { void ThreadBase::wait_for(volatile const bool& condition) { - std::unique_lock lk(mutex); - sleepCondition.wait(lk, [&]{ return condition; }); + mutex.lock(); + while (!condition) sleepCondition.wait(mutex); + mutex.unlock(); } @@ -83,8 +91,8 @@ Thread::Thread() /* : splitPoints() */ { // Initialization of non POD broken in searching = false; maxPly = 0; splitPointsSize = 0; - activeSplitPoint = nullptr; - activePosition = nullptr; + activeSplitPoint = NULL; + activePosition = NULL; idx = Threads.size(); // Starts from 0 } @@ -102,13 +110,14 @@ bool Thread::cutoff_occurred() const { } -// Thread::can_join() checks whether the thread is available to join the split -// point 'sp'. An obvious requirement is that thread must be idle. With more than -// two threads, this is not sufficient: If the thread is the master of some split -// point, it is only available as a slave for the split points below his active -// one (the "helpful master" concept in YBWC terminology). +// Thread::available_to() checks whether the thread is available to help the +// thread 'master' at a split point. An obvious requirement is that thread must +// be idle. With more than two threads, this is not sufficient: If the thread is +// the master of some split point, it is only available as a slave to the slaves +// which are busy searching the split point at the top of slave's split point +// stack (the "helpful master concept" in YBWC terminology). -bool Thread::can_join(const SplitPoint* sp) const { +bool Thread::available_to(const Thread* master) const { if (searching) return false; @@ -119,7 +128,7 @@ bool Thread::can_join(const SplitPoint* sp) const { // No split points means that the thread is available as a slave for any // other thread otherwise apply the "helpful master" concept if possible. - return !size || splitPoints[size - 1].slavesMask.test(sp->master->idx); + return !size || splitPoints[size - 1].slavesMask.test(master->idx); } @@ -164,21 +173,21 @@ void Thread::split(Position& pos, Stack* ss, Value alpha, Value beta, Value* bes // Try to allocate available threads and ask them to start searching setting // 'searching' flag. This must be done under lock protection to avoid concurrent // allocation of the same slave by another master. - Threads.spinlock.acquire(); - sp.spinlock.acquire(); + Threads.mutex.lock(); + sp.mutex.lock(); sp.allSlavesSearching = true; // Must be set under lock protection ++splitPointsSize; activeSplitPoint = &sp; - activePosition = nullptr; + activePosition = NULL; Thread* slave; while ( sp.slavesMask.count() < MAX_SLAVES_PER_SPLITPOINT - && (slave = Threads.available_slave(activeSplitPoint)) != nullptr) + && (slave = Threads.available_slave(this)) != NULL) { sp.slavesMask.set(slave->idx); - slave->activeSplitPoint = activeSplitPoint; + slave->activeSplitPoint = &sp; slave->searching = true; // Slave leaves idle_loop() slave->notify_one(); // Could be sleeping } @@ -187,8 +196,8 @@ void Thread::split(Position& pos, Stack* ss, Value alpha, Value beta, Value* bes // it will instantly launch a search, because its 'searching' flag is set. // The thread will return from the idle loop when all slaves have finished // their work at this split point. - sp.spinlock.release(); - Threads.spinlock.release(); + sp.mutex.unlock(); + Threads.mutex.unlock(); Thread::idle_loop(); // Force a call to base class idle_loop() @@ -201,8 +210,8 @@ void Thread::split(Position& pos, Stack* ss, Value alpha, Value beta, Value* bes // We have returned from the idle loop, which means that all threads are // finished. Note that setting 'searching' and decreasing splitPointsSize must // be done under lock protection to avoid a race with Thread::available_to(). - Threads.spinlock.acquire(); - sp.spinlock.acquire(); + Threads.mutex.lock(); + sp.mutex.lock(); searching = true; --splitPointsSize; @@ -212,8 +221,8 @@ void Thread::split(Position& pos, Stack* ss, Value alpha, Value beta, Value* bes *bestMove = sp.bestMove; *bestValue = sp.bestValue; - sp.spinlock.release(); - Threads.spinlock.release(); + sp.mutex.unlock(); + Threads.mutex.unlock(); } @@ -224,12 +233,12 @@ void TimerThread::idle_loop() { while (!exit) { - std::unique_lock lk(mutex); + mutex.lock(); if (!exit) - sleepCondition.wait_for(lk, std::chrono::milliseconds(run ? Resolution : INT_MAX)); + sleepCondition.wait_for(mutex, run ? Resolution : INT_MAX); - lk.unlock(); + mutex.unlock(); if (run) check_time(); @@ -244,17 +253,17 @@ void MainThread::idle_loop() { while (!exit) { - std::unique_lock lk(mutex); + mutex.lock(); thinking = false; while (!thinking && !exit) { Threads.sleepCondition.notify_one(); // Wake up the UI thread if needed - sleepCondition.wait(lk); + sleepCondition.wait(mutex); } - lk.unlock(); + mutex.unlock(); if (!exit) { @@ -290,8 +299,8 @@ void ThreadPool::exit() { delete_thread(timer); // As first because check_time() accesses threads data - for (Thread* th : *this) - delete_thread(th); + for (iterator it = begin(); it != end(); ++it) + delete_thread(*it); } @@ -324,15 +333,15 @@ void ThreadPool::read_uci_options() { // ThreadPool::available_slave() tries to find an idle thread which is available -// to join SplitPoint 'sp'. +// as a slave for the thread 'master'. -Thread* ThreadPool::available_slave(const SplitPoint* sp) const { +Thread* ThreadPool::available_slave(const Thread* master) const { - for (Thread* th : *this) - if (th->can_join(sp)) - return th; + for (const_iterator it = begin(); it != end(); ++it) + if ((*it)->available_to(master)) + return *it; - return nullptr; + return NULL; } @@ -340,8 +349,10 @@ Thread* ThreadPool::available_slave(const SplitPoint* sp) const { void ThreadPool::wait_for_think_finished() { - std::unique_lock lk(main()->mutex); - sleepCondition.wait(lk, [&]{ return !main()->thinking; }); + MainThread* th = main(); + th->mutex.lock(); + while (th->thinking) sleepCondition.wait(th->mutex); + th->mutex.unlock(); } @@ -352,7 +363,7 @@ void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits, StateStackPtr& states) { wait_for_think_finished(); - SearchTime = now(); // As early as possible + SearchTime = Time::now(); // As early as possible Signals.stopOnPonderhit = Signals.firstRootMove = false; Signals.stop = Signals.failedLowAtRoot = false; @@ -362,14 +373,14 @@ void ThreadPool::start_thinking(const Position& pos, const LimitsType& limits, Limits = limits; if (states.get()) // If we don't set a new position, preserve current state { - SetupStates = std::move(states); // Ownership transfer here + SetupStates = states; // Ownership transfer here assert(!states.get()); } - for (const auto& m : MoveList(pos)) + for (MoveList it(pos); *it; ++it) if ( limits.searchmoves.empty() - || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) - RootMoves.push_back(RootMove(m)); + || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), *it)) + RootMoves.push_back(RootMove(*it)); main()->thinking = true; main()->notify_one(); // Starts main thread diff --git a/src/thread.h b/src/thread.h index 34307297..f530913c 100644 --- a/src/thread.h +++ b/src/thread.h @@ -20,11 +20,7 @@ #ifndef THREAD_H_INCLUDED #define THREAD_H_INCLUDED -#include #include -#include -#include -#include #include #include "material.h" @@ -39,34 +35,34 @@ const size_t MAX_THREADS = 128; const size_t MAX_SPLITPOINTS_PER_THREAD = 8; const size_t MAX_SLAVES_PER_SPLITPOINT = 4; -#if 0 -/// Spinlock class wraps low level atomic operations to provide a spin lock +/// Mutex and ConditionVariable struct are wrappers of the low level locking +/// machinery and are modeled after the corresponding C++11 classes. -class Spinlock { +struct Mutex { + Mutex() { lock_init(l); } + ~Mutex() { lock_destroy(l); } - std::atomic_int lock; + void lock() { lock_grab(l); } + void unlock() { lock_release(l); } -public: - Spinlock() { lock = 1; } // Init here to workaround a bug with MSVC 2013 - void acquire() { - while (lock.fetch_sub(1, std::memory_order_acquire) != 1) - while (lock.load(std::memory_order_relaxed) <= 0) {} - } - void release() { lock.store(1, std::memory_order_release); } +private: + friend struct ConditionVariable; + + Lock l; }; -#else +struct ConditionVariable { + ConditionVariable() { cond_init(c); } + ~ConditionVariable() { cond_destroy(c); } -class Spinlock { + void wait(Mutex& m) { cond_wait(c, m.l); } + void wait_for(Mutex& m, int ms) { timed_wait(c, m.l, ms); } + void notify_one() { cond_signal(c); } - std::mutex mutex; - -public: - void acquire() { mutex.lock(); } - void release() { mutex.unlock(); } +private: + WaitCondition c; }; -#endif /// SplitPoint struct stores information shared by the threads searching in /// parallel below the same split point. It is populated at splitting time. @@ -87,7 +83,7 @@ struct SplitPoint { SplitPoint* parentSplitPoint; // Shared variable data - Spinlock spinlock; + Mutex mutex; std::bitset slavesMask; volatile bool allSlavesSearching; volatile uint64_t nodes; @@ -104,15 +100,16 @@ struct SplitPoint { struct ThreadBase { - virtual ~ThreadBase() = default; + ThreadBase() : handle(NativeHandle()), exit(false) {} + virtual ~ThreadBase() {} virtual void idle_loop() = 0; void notify_one(); void wait_for(volatile const bool& b); - std::thread nativeThread; - std::mutex mutex; - std::condition_variable sleepCondition; - volatile bool exit = false; + Mutex mutex; + ConditionVariable sleepCondition; + NativeHandle handle; + volatile bool exit; }; @@ -126,7 +123,7 @@ struct Thread : public ThreadBase { Thread(); virtual void idle_loop(); bool cutoff_occurred() const; - bool can_join(const SplitPoint* sp) const; + bool available_to(const Thread* master) const; void split(Position& pos, Search::Stack* ss, Value alpha, Value beta, Value* bestValue, Move* bestMove, Depth depth, int moveCount, MovePicker* movePicker, int nodeType, bool cutNode); @@ -148,17 +145,19 @@ struct Thread : public ThreadBase { /// special threads: the main one and the recurring timer. struct MainThread : public Thread { + MainThread() : thinking(true) {} // Avoid a race with start_thinking() virtual void idle_loop(); - volatile bool thinking = true; // Avoid a race with start_thinking() + volatile bool thinking; }; struct TimerThread : public ThreadBase { static const int Resolution = 5; // Millisec between two check_time() calls + TimerThread() : run(false) {} virtual void idle_loop(); - bool run = false; + bool run; }; @@ -173,13 +172,13 @@ struct ThreadPool : public std::vector { MainThread* main() { return static_cast(at(0)); } void read_uci_options(); - Thread* available_slave(const SplitPoint* sp) const; + Thread* available_slave(const Thread* master) const; void wait_for_think_finished(); void start_thinking(const Position&, const Search::LimitsType&, Search::StateStackPtr&); Depth minimumSplitDepth; - Spinlock spinlock; - std::condition_variable sleepCondition; + Mutex mutex; + ConditionVariable sleepCondition; TimerThread* timer; }; diff --git a/src/tt.cpp b/src/tt.cpp index d0e2d729..19de55ae 100644 --- a/src/tt.cpp +++ b/src/tt.cpp @@ -32,6 +32,8 @@ TranspositionTable TT; // Our global transposition table void TranspositionTable::resize(size_t mbSize) { + assert(sizeof(Cluster) == CacheLineSize / 2); + size_t newClusterCount = size_t(1) << msb((mbSize * 1024 * 1024) / sizeof(Cluster)); if (newClusterCount == clusterCount) diff --git a/src/tt.h b/src/tt.h index 32b4036e..7343c525 100644 --- a/src/tt.h +++ b/src/tt.h @@ -81,8 +81,6 @@ class TranspositionTable { char padding[2]; // Align to the cache line size }; - static_assert(sizeof(Cluster) == CacheLineSize / 2, "Cluster size incorrect"); - public: ~TranspositionTable() { free(mem); } void new_search() { generation8 += 4; } // Lower 2 bits are used by Bound diff --git a/src/types.h b/src/types.h index eebd69e5..7c5776ab 100644 --- a/src/types.h +++ b/src/types.h @@ -33,22 +33,13 @@ /// /// -DUSE_POPCNT | Add runtime support for use of popcnt asm-instruction. Works /// | only in 64-bit mode and requires hardware with popcnt support. -/// -/// -DUSE_PEXT | Add runtime support for use of pext asm-instruction. Works -/// | only in 64-bit mode and requires hardware with pext support. #include #include #include -#include #include -#if defined(_MSC_VER) -// Disable some silly and noisy warning from MSVC compiler -#pragma warning(disable: 4127) // Conditional expression is constant -#pragma warning(disable: 4146) // Unary minus operator applied to unsigned type -#pragma warning(disable: 4800) // Forcing value to bool 'true' or 'false' -#endif +#include "platform.h" /// Predefined macros hell: /// @@ -180,7 +171,7 @@ enum Bound { BOUND_EXACT = BOUND_UPPER | BOUND_LOWER }; -enum Value : int { +enum Value { VALUE_ZERO = 0, VALUE_DRAW = 0, VALUE_KNOWN_WIN = 10000, @@ -191,6 +182,9 @@ enum Value : int { VALUE_MATE_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + 2 * MAX_PLY, + VALUE_ENSURE_INTEGER_SIZE_P = INT_MAX, + VALUE_ENSURE_INTEGER_SIZE_N = INT_MIN, + PawnValueMg = 198, PawnValueEg = 258, KnightValueMg = 817, KnightValueEg = 846, BishopValueMg = 836, BishopValueEg = 857, @@ -261,10 +255,16 @@ enum Rank { }; -/// Score enum stores a middlegame and an endgame value in a single integer -/// (enum). The least significant 16 bits are used to store the endgame value -/// and the upper 16 bits are used to store the middlegame value. -enum Score : int { SCORE_ZERO }; +/// Score enum stores a middlegame and an endgame value in a single integer. +/// The least significant 16 bits are used to store the endgame value and +/// the upper 16 bits are used to store the middlegame value. The compiler +/// is free to choose the enum type as long as it can store the data, so we +/// ensure that Score is an integer type by assigning some big int values. +enum Score { + SCORE_ZERO, + SCORE_ENSURE_INTEGER_SIZE_P = INT_MAX, + SCORE_ENSURE_INTEGER_SIZE_N = INT_MIN +}; inline Score make_score(int mg, int eg) { return Score((mg << 16) + eg); @@ -417,7 +417,7 @@ inline MoveType type_of(Move m) { } inline PieceType promotion_type(Move m) { - return PieceType(((m >> 12) & 3) + KNIGHT); + return PieceType(((m >> 12) & 3) + 2); } inline Move make_move(Square from, Square to) { diff --git a/src/uci.cpp b/src/uci.cpp index b7127b75..1b91b451 100644 --- a/src/uci.cpp +++ b/src/uci.cpp @@ -74,7 +74,7 @@ namespace { while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) { SetupStates->push(StateInfo()); - pos.do_move(m, SetupStates->top(), pos.gives_check(m, CheckInfo(pos))); + pos.do_move(m, SetupStates->top()); } } @@ -232,7 +232,9 @@ string UCI::value(Value v) { /// UCI::square() converts a Square to a string in algebraic notation (g1, a7, etc.) std::string UCI::square(Square s) { - return std::string{ char('a' + file_of(s)), char('1' + rank_of(s)) }; + + char sq[] = { char('a' + file_of(s)), char('1' + rank_of(s)), 0 }; // NULL terminated + return sq; } @@ -272,9 +274,9 @@ Move UCI::to_move(const Position& pos, string& str) { if (str.length() == 5) // Junior could send promotion piece in uppercase str[4] = char(tolower(str[4])); - for (const auto& m : MoveList(pos)) - if (str == UCI::move(m, pos.is_chess960())) - return m; + for (MoveList it(pos); *it; ++it) + if (str == UCI::move(*it, pos.is_chess960())) + return *it; return MOVE_NONE; } diff --git a/src/uci.h b/src/uci.h index 76e6cda6..b928e8ae 100644 --- a/src/uci.h +++ b/src/uci.h @@ -45,10 +45,10 @@ class Option { typedef void (*OnChange)(const Option&); public: - Option(OnChange = nullptr); - Option(bool v, OnChange = nullptr); - Option(const char* v, OnChange = nullptr); - Option(int v, int min, int max, OnChange = nullptr); + Option(OnChange = NULL); + Option(bool v, OnChange = NULL); + Option(const char* v, OnChange = NULL); + Option(int v, int min, int max, OnChange = NULL); Option& operator=(const std::string&); void operator<<(const Option&); @@ -69,7 +69,6 @@ void loop(int argc, char* argv[]); 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, Value alpha, Value beta); Move to_move(const Position& pos, std::string& str); } // namespace UCI diff --git a/src/ucioption.cpp b/src/ucioption.cpp index 06641cf5..1778c718 100644 --- a/src/ucioption.cpp +++ b/src/ucioption.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include "misc.h" @@ -42,10 +43,10 @@ void on_tb_path(const Option& o) { Tablebases::init(o); } /// Our case insensitive less() function as required by UCI protocol -bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { +bool ci_less(char c1, char c2) { return tolower(c1) < tolower(c2); } - return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), - [](char c1, char c2) { return tolower(c1) < tolower(c2); }); +bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), ci_less); } @@ -81,11 +82,11 @@ void init(OptionsMap& o) { std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { for (size_t idx = 0; idx < om.size(); ++idx) - for (const auto& it : om) - if (it.second.idx == idx) + for (OptionsMap::const_iterator it = om.begin(); it != om.end(); ++it) + if (it->second.idx == idx) { - const Option& o = it.second; - os << "\noption name " << it.first << " type " << o.type; + const Option& o = it->second; + os << "\noption name " << it->first << " type " << o.type; if (o.type != "button") os << " default " << o.defaultValue; @@ -95,7 +96,6 @@ std::ostream& operator<<(std::ostream& os, const OptionsMap& om) { break; } - return os; } @@ -112,11 +112,12 @@ Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) {} Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) -{ defaultValue = currentValue = std::to_string(v); } +{ std::ostringstream ss; ss << v; defaultValue = currentValue = ss.str(); } + Option::operator int() const { assert(type == "check" || type == "spin"); - return (type == "spin" ? stoi(currentValue) : currentValue == "true"); + return (type == "spin" ? atoi(currentValue.c_str()) : currentValue == "true"); } Option::operator std::string() const { @@ -146,7 +147,7 @@ Option& Option::operator=(const string& v) { if ( (type != "button" && v.empty()) || (type == "check" && v != "true" && v != "false") - || (type == "spin" && (stoi(v) < min || stoi(v) > max))) + || (type == "spin" && (atoi(v.c_str()) < min || atoi(v.c_str()) > max))) return *this; if (type != "button")