diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f80f75c..4e1a665 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -51,11 +51,18 @@ jobs: - name: Configure CMake shell: bash run: | + if [[ "${{ matrix.os }}" == "ubuntu-latest" ]]; then + if "${{ matrix.cpp_compiler }}" == "clang++" ]]; then + SANITIZERS="memory,undefined" + else + SANITIZERS="address,undefined" + fi + fi cmake -B "${{ steps.vars.outputs.dir }}" \ -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} \ -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \ -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ - -DSANITIZERS="address,undefined" \ + -DSANITIZERS=${SANITIZERS} \ -DDART_TESTING_TIMEOUT=0 \ -S "${{ github.workspace }}" diff --git a/.github/workflows/try_compile.yml b/.github/workflows/try_compile.yml index 730a31e..e436057 100644 --- a/.github/workflows/try_compile.yml +++ b/.github/workflows/try_compile.yml @@ -1,7 +1,7 @@ name: Compilation on: - pull_request: + push: jobs: build: diff --git a/movegen.cpp b/movegen.cpp index 5bdfcc5..d92e78a 100644 --- a/movegen.cpp +++ b/movegen.cpp @@ -4,7 +4,39 @@ namespace chess { namespace _chess { -#if defined(__AVX512BW__) + +#if defined(USE_AVX512ICL) + +// clang-format off +const __m512i AllSquares = _mm512_set_epi8( + 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, + 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, + 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0); +// clang-format on + +template inline Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { + assert(popcount(to_bb) <= 8); // <= 8 pawns per side + + const __m128i toSquares = _mm_cvtepi8_epi16(_mm512_castsi512_si128(_mm512_maskz_compress_epi8(to_bb, AllSquares))); + const __m128i fromSquares = _mm_subs_epi16(toSquares, _mm_set1_epi16(offset)); + const __m128i moves = _mm_or_si128(_mm_slli_epi16(fromSquares, 6), _mm_slli_epi16(toSquares, 0)); + + _mm_storeu_si128(reinterpret_cast<__m128i *>(moveList), moves); + return moveList + popcount(to_bb); +} + +inline Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { + assert(popcount(to_bb) <= 32); // Q can attack up to 27 squares + + const __m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw()); + const __m512i toSquares = _mm512_cvtepi8_epi16(_mm512_castsi512_si256(_mm512_maskz_compress_epi8(to_bb, AllSquares))); + const __m512i moves = _mm512_or_si512(fromVec, _mm512_slli_epi16(toSquares, Move::ToSqShift)); + + _mm512_storeu_si512(moveList, moves); + return moveList + popcount(to_bb); +} + +#elif defined(__AVX512BW__) template struct alignas(64) SplatTable { std::array data; constexpr int clamp64(int x) { return (x < 0) ? 0 : (x > 63 ? 63 : x); } @@ -213,7 +245,7 @@ void movegen::genPawnSingleMoves( } template void movegen::genKnightMoves(const _Position &pos, Movelist &list, Bitboard _pin_mask, Bitboard _check_mask) { - Bitboard knights = pos.template pieces() & ~_pin_mask; // yes, unconditionally. + Bitboard knights = pos.template pieces() & ~_pin_mask; while (knights) { Square x = static_cast(pop_lsb(knights)); Bitboard moves = attacks::knight(x) & ~pos.occ(c); @@ -253,7 +285,6 @@ void movegen::genKingMoves(const _Position &pos, Movelist &out, Bitboar // Enemy king (adjacent control squares) enemyAttacks |= attacks::king(pos.kingSq(them)); - // Candidate king moves = legal squares not attacked by enemy Bitboard moves = attacks::king(kingSq) & ~myOcc & ~enemyAttacks; if constexpr (capturesOnly) moves &= pos.occ(~c); @@ -289,8 +320,7 @@ void movegen::genSlidingMoves( static_assert(pt == BISHOP || pt == ROOK || pt == QUEEN, "Sliding pieces only."); Bitboard sliders = pos.template pieces(); Bitboard occ_all = pos.occ(); - // Square king_sq = current_state.kings[c]; - Bitboard rook_pinners = _rook_pin; // bitboard of enemy rooks/queens pinning + Bitboard rook_pinners = _rook_pin; Bitboard bishop_pinners = _bishop_pin; if constexpr (pt == BISHOP) sliders &= ~rook_pinners; @@ -305,7 +335,6 @@ void movegen::genSlidingMoves( Bitboard bishop_hit = bishop_pinners & from_bb; Bitboard pin_mask = rook_hit ? rook_pinners : bishop_hit ? bishop_pinners : ~0ULL; - // Bitboard blockers = occ() ^ from_bb; // remove piece temporarily auto func = attacks::queen; if constexpr (pt == BISHOP) func = attacks::bishop; diff --git a/non_core_tests.cpp b/non_core_tests.cpp index b9536ac..a5116a7 100644 --- a/non_core_tests.cpp +++ b/non_core_tests.cpp @@ -893,9 +893,13 @@ TEST_SUITE("SAN Parser") { } TEST_SUITE("misc tests") { - TEST_CASE("FEN reconstruction") { + TEST_CASE("FEN reconstruction (Chess960)") { Position pos(Position::START_CHESS960_FEN, true); - REQUIRE(pos.fen() == Position::START_CHESS960_FEN); + REQUIRE(pos.fen(false) == Position::START_CHESS960_FEN); + REQUIRE(pos.fen() == Position::START_FEN); + pos.setFEN(Position::START_CHESS960_FEN, true, chess::MODE_SMK); + REQUIRE(pos.fen(false) == Position::START_CHESS960_FEN); + REQUIRE(pos.fen() == Position::START_FEN); } } int main(int argc, char **argv) { diff --git a/position.cpp b/position.cpp index c8bc9ec..d3bdbeb 100644 --- a/position.cpp +++ b/position.cpp @@ -197,7 +197,8 @@ template template void _Position void _Position::setFEN(const std::string &str, bool chess960) { +template +void _Position::setFEN(const std::string &str, bool chess960, FENParsingMode mode) { current_state = HistoryEntry(); history.clear(); _chess960 = chess960; @@ -329,6 +330,8 @@ template void _Position::setFEN(const s } return SQ_NONE; }; + bool allow_xfen = (mode == MODE_XFEN || mode == MODE_AUTO); + bool allow_smk = (mode == MODE_SMK || mode == MODE_AUTO); auto apply = [&](char c) { Square king_sq = findKing(); if (king_sq == SQ_NONE) @@ -368,24 +371,28 @@ template void _Position::setFEN(const s }; if (c == 'K' && color == WHITE) { + INVALID_ARG_IF(chess960 && !allow_xfen, "shredder fen into xfen parser"); if (rook_ks == SQ_NONE) return; if (rank_of(king_sq) != rank_of(rook_ks)) return; setKS(rook_ks); } else if (c == 'Q' && color == WHITE) { + INVALID_ARG_IF(chess960 && !allow_xfen, "shredder fen into xfen parser"); if (rook_qs == SQ_NONE) return; if (rank_of(king_sq) != rank_of(rook_qs)) return; setQS(rook_qs); } else if (c == 'k' && color == BLACK) { + INVALID_ARG_IF(chess960 && !allow_xfen, "shredder fen into xfen parser"); if (rook_ks == SQ_NONE) return; if (rank_of(king_sq) != rank_of(rook_ks)) return; setKS(rook_ks); } else if (c == 'q' && color == BLACK) { + INVALID_ARG_IF(chess960 && !allow_xfen, "shredder fen into xfen parser"); if (rook_qs == SQ_NONE) return; if (rank_of(king_sq) != rank_of(rook_qs)) @@ -394,6 +401,7 @@ template void _Position::setFEN(const s } else if (c >= 'A' && c <= 'H' && color == WHITE) { + INVALID_ARG_IF(chess960 && !allow_smk, "xfen into shredder fen parser"); File f = static_cast(c - 'A'); Square rook_sq = make_sq(RANK_1, f); @@ -403,6 +411,7 @@ template void _Position::setFEN(const s (f > file_of(king_sq)) ? setKS(rook_sq) : setQS(rook_sq); } else if (c >= 'a' && c <= 'h' && color == BLACK) { + INVALID_ARG_IF(chess960 && !allow_smk, "xfen into shredder fen parser"); File f = static_cast(c - 'a'); Square rook_sq = make_sq(RANK_8, f); @@ -474,7 +483,7 @@ template void _Position::setFEN(const s current_state.repetition = current_state.pliesFromNull = 0; } -template std::string _Position::fen() const { +template std::string _Position::fen(bool xfen) const { std::ostringstream ss; // 1) Piece placement @@ -508,13 +517,21 @@ template std::string _Position::fen() c std::string castlingStr; if (chess960()) { if (castlingRights() & WHITE_OO) - castlingStr += static_cast('A' + file_of(current_state.castlingMetadata[WHITE].rook_start_ks)); + castlingStr += (xfen && current_state.castlingMetadata[WHITE].rook_start_ks == SQ_H1) + ? 'K' + : static_cast('A' + file_of(current_state.castlingMetadata[WHITE].rook_start_ks)); if (castlingRights() & WHITE_OOO) - castlingStr += static_cast('A' + file_of(current_state.castlingMetadata[WHITE].rook_start_qs)); + castlingStr += (xfen && current_state.castlingMetadata[WHITE].rook_start_qs == SQ_A1) + ? 'Q' + : static_cast('A' + file_of(current_state.castlingMetadata[WHITE].rook_start_qs)); if (castlingRights() & BLACK_OO) - castlingStr += static_cast('a' + file_of(current_state.castlingMetadata[BLACK].rook_start_ks)); + castlingStr += (xfen && current_state.castlingMetadata[BLACK].rook_start_ks == SQ_H8) + ? 'k' + : static_cast('a' + file_of(current_state.castlingMetadata[BLACK].rook_start_ks)); if (castlingRights() & BLACK_OOO) - castlingStr += static_cast('a' + file_of(current_state.castlingMetadata[BLACK].rook_start_qs)); + castlingStr += (xfen && current_state.castlingMetadata[BLACK].rook_start_qs == SQ_A8) + ? 'q' + : static_cast('a' + file_of(current_state.castlingMetadata[BLACK].rook_start_qs)); } else { if (castlingRights() & WHITE_OO) castlingStr += 'K'; @@ -858,8 +875,8 @@ template CastlingRights _Position::clea } // clang-format off #define INSTANTIATE(PieceC) \ -template void _Position::setFEN(const std::string &, bool); \ -template std::string _Position::fen() const; \ +template void _Position::setFEN(const std::string &, bool, FENParsingMode); \ +template std::string _Position::fen(bool) const; \ template void _Position::doMove(const Move &move); \ template void _Position::doMove(const Move &move); \ template void _Position::refresh_attacks(); \ diff --git a/position.h b/position.h index 482fd33..4fd1fc3 100644 --- a/position.h +++ b/position.h @@ -36,6 +36,9 @@ template struct alignas(64) HistoryEntry { }; enum class CheckType { NO_CHECK, DIRECT_CHECK, DISCOVERY_CHECK }; + +enum FENParsingMode { MODE_XFEN, MODE_SMK, MODE_AUTO }; + enum class MoveGenType : uint16_t { NONE = 0, @@ -396,16 +399,16 @@ template (mv.from_sq()) == PAWN; } - [[nodiscard]] std::string fen() const; + [[nodiscard]] std::string fen(bool xfen = true) const; [[nodiscard]] inline uint8_t halfmoveClock() const { return current_state.halfMoveClock; } [[nodiscard]] inline uint16_t fullmoveNumber() const { return current_state.fullMoveNumber; } [[nodiscard]] inline uint8_t rule50_count() const { return current_state.halfMoveClock; } @@ -428,9 +431,13 @@ template