diff --git a/Cargo.toml b/Cargo.toml index c8766b9f..3d7a8015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,9 +52,9 @@ opt-level = 's' [profile.release] codegen-units = 1 -lto = true panic = "abort" -strip = "symbols" +lto = true +strip = true [profile.dev] opt-level = 3 diff --git a/bin/main.rs b/bin/main.rs index 0ed790d1..16b9b6ce 100644 --- a/bin/main.rs +++ b/bin/main.rs @@ -1,36 +1,26 @@ -use futures::executor::{block_on, block_on_stream}; -use futures::{channel::mpsc, prelude::*}; +use futures::executor::block_on; +use futures::{channel::mpsc::channel as bounded, prelude::*, sink::unfold}; use lib::uci::Uci; -use std::io::{prelude::*, stdin, stdout, LineWriter}; -use std::thread; +use std::io::{prelude::*, stdin, stdout}; +use std::{future::ready, thread}; fn main() { - let (mut tx, input) = mpsc::channel(32); - let (output, rx) = mpsc::channel(32); + let (mut tx, rx) = bounded(0); thread::spawn(move || { for item in stdin().lock().lines() { match item { Err(error) => return eprint!("{error}"), Ok(line) => { - if let Err(error) = block_on(tx.send(line)) { - if error.is_disconnected() { - break; - } + if block_on(tx.send(line)).is_err() { + break; } } } } }); - thread::spawn(move || { - let mut stdout = LineWriter::new(stdout().lock()); - for line in block_on_stream(rx) { - if let Err(error) = writeln!(stdout, "{line}") { - return eprint!("{error}"); - } - } - }); - - block_on(Uci::new(input, output).run()).ok(); + let mut stdout = stdout().lock(); + let output = unfold((), |_, line: String| ready(writeln!(stdout, "{line}"))); + block_on(Uci::new(rx, output).run()).unwrap(); } diff --git a/lib/chess/bitboard.rs b/lib/chess/bitboard.rs index 2b38af61..f9225158 100644 --- a/lib/chess/bitboard.rs +++ b/lib/chess/bitboard.rs @@ -26,7 +26,7 @@ use std::{cell::SyncUnsafeCell, mem::MaybeUninit}; #[repr(transparent)] pub struct Bitboard(u64); -impl fmt::Debug for Bitboard { +impl Debug for Bitboard { #[coverage(off)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_char('\n')?; @@ -44,31 +44,31 @@ impl fmt::Debug for Bitboard { impl Bitboard { /// An empty board. - #[inline(always)] + #[inline] pub fn empty() -> Self { Bitboard(0) } /// A full board. - #[inline(always)] + #[inline] pub fn full() -> Self { Bitboard(0xFFFFFFFFFFFFFFFF) } /// Border squares. - #[inline(always)] + #[inline] pub fn border() -> Self { Bitboard(0xFF818181818181FF) } /// Light squares. - #[inline(always)] + #[inline] pub fn light() -> Self { Bitboard(0x55AA55AA55AA55AA) } /// Dark squares. - #[inline(always)] + #[inline] pub fn dark() -> Self { Bitboard(0xAA55AA55AA55AA55) } @@ -86,7 +86,7 @@ impl Bitboard { /// vec![Square::F1, Square::E2, Square::D4, Square::C6] /// ); /// ``` - #[inline(always)] + #[inline] pub fn fill(sq: Square, steps: &[(i8, i8)], occupied: Bitboard) -> Self { let mut bitboard = sq.bitboard(); @@ -117,7 +117,7 @@ impl Bitboard { /// vec![Square::E1, Square::D2, Square::C3, Square::B4, Square::A5] /// ); /// ``` - #[inline(always)] + #[inline] pub fn line(whence: Square, whither: Square) -> Self { static LINES: SyncUnsafeCell<[[Bitboard; 64]; 64]> = unsafe { MaybeUninit::zeroed().assume_init() }; @@ -161,7 +161,7 @@ impl Bitboard { /// vec![Square::D2, Square::C3] /// ); /// ``` - #[inline(always)] + #[inline] pub fn segment(whence: Square, whither: Square) -> Self { static SEGMENTS: SyncUnsafeCell<[[Bitboard; 64]; 64]> = unsafe { MaybeUninit::zeroed().assume_init() }; @@ -190,61 +190,61 @@ impl Bitboard { } /// The number of [`Square`]s in the set. - #[inline(always)] + #[inline] pub fn len(&self) -> usize { self.0.count_ones() as _ } /// Whether the board is empty. - #[inline(always)] + #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } /// Whether this [`Square`] is in the set. - #[inline(always)] + #[inline] pub fn contains(&self, sq: Square) -> bool { !sq.bitboard().intersection(*self).is_empty() } /// Adds a [`Square`] to this bitboard. - #[inline(always)] + #[inline] pub fn with(&self, sq: Square) -> Self { sq.bitboard().union(*self) } /// Removes a [`Square`]s from this bitboard. - #[inline(always)] + #[inline] pub fn without(&self, sq: Square) -> Self { sq.bitboard().inverse().intersection(*self) } /// The set of [`Square`]s not in this bitboard. - #[inline(always)] + #[inline] pub fn inverse(&self) -> Self { Bitboard(!self.0) } /// The set of [`Square`]s in both bitboards. - #[inline(always)] + #[inline] pub fn intersection(&self, bb: Bitboard) -> Self { Bitboard(self.0 & bb.0) } /// The set of [`Square`]s in either bitboard. - #[inline(always)] + #[inline] pub fn union(&self, bb: Bitboard) -> Self { Bitboard(self.0 | bb.0) } /// An iterator over the [`Square`]s in this bitboard. - #[inline(always)] + #[inline] pub fn iter(&self) -> Squares { Squares::new(*self) } /// An iterator over the subsets of this bitboard. - #[inline(always)] + #[inline] pub fn subsets(&self) -> Subsets { Subsets::new(*self) } @@ -252,28 +252,28 @@ impl Bitboard { impl Perspective for Bitboard { /// Flips all squares in the set. - #[inline(always)] + #[inline] fn flip(&self) -> Self { Self(self.0.swap_bytes()) } } impl From for Bitboard { - #[inline(always)] + #[inline] fn from(f: File) -> Self { f.bitboard() } } impl From for Bitboard { - #[inline(always)] + #[inline] fn from(r: Rank) -> Self { r.bitboard() } } impl From for Bitboard { - #[inline(always)] + #[inline] fn from(sq: Square) -> Self { sq.bitboard() } @@ -283,7 +283,7 @@ impl IntoIterator for Bitboard { type Item = Square; type IntoIter = Squares; - #[inline(always)] + #[inline] fn into_iter(self) -> Self::IntoIter { Squares::new(self) } @@ -296,7 +296,7 @@ pub struct Squares(Bitboard); impl Iterator for Squares { type Item = Square; - #[inline(always)] + #[inline] fn next(&mut self) -> Option { if self.0.is_empty() { None @@ -307,14 +307,14 @@ impl Iterator for Squares { } } - #[inline(always)] + #[inline] fn size_hint(&self) -> (usize, Option) { (self.len(), Some(self.len())) } } impl ExactSizeIterator for Squares { - #[inline(always)] + #[inline] fn len(&self) -> usize { self.0.len() } @@ -325,7 +325,7 @@ impl ExactSizeIterator for Squares { pub struct Subsets(u64, Option); impl Subsets { - #[inline(always)] + #[inline] pub fn new(bb: Bitboard) -> Self { Self(bb.0, Some(0)) } @@ -334,7 +334,7 @@ impl Subsets { impl Iterator for Subsets { type Item = Bitboard; - #[inline(always)] + #[inline] fn next(&mut self) -> Option { let bits = self.1?; self.1 = match bits.wrapping_sub(self.0) & self.0 { diff --git a/lib/chess/board.rs b/lib/chess/board.rs index ab7d1324..234d8c19 100644 --- a/lib/chess/board.rs +++ b/lib/chess/board.rs @@ -1,8 +1,9 @@ -use crate::{chess::*, util::Integer}; -use arrayvec::ArrayString; +use crate::chess::*; +use crate::util::{Assume, Integer}; use derive_more::{Debug, Display, Error}; use std::fmt::{self, Write}; -use std::{ops::Index, str::FromStr}; +use std::str::{self, FromStr}; +use std::{io::Write as _, ops::Index}; /// The chess board. #[derive(Debug, Clone, Eq, PartialEq, Hash)] @@ -32,7 +33,7 @@ pub struct Board { } impl Default for Board { - #[inline(always)] + #[inline] fn default() -> Self { Self { roles: [ @@ -59,50 +60,50 @@ impl Default for Board { impl Board { /// [`Square`]s occupied by a [`Color`]. - #[inline(always)] + #[inline] pub fn by_color(&self, c: Color) -> Bitboard { self.colors[c as usize] } /// [`Square`]s occupied by a [`Role`]. - #[inline(always)] + #[inline] pub fn by_role(&self, r: Role) -> Bitboard { self.roles[r as usize] } /// [`Square`]s occupied by a [`Piece`]. - #[inline(always)] + #[inline] pub fn by_piece(&self, p: Piece) -> Bitboard { self.by_color(p.color()) & self.by_role(p.role()) } /// [`Square`] occupied by a the king of a [`Color`]. - #[inline(always)] + #[inline] pub fn king(&self, side: Color) -> Option { let piece = Piece::new(Role::King, side); self.by_piece(piece).into_iter().next() } /// The [`Color`] of the piece on the given [`Square`], if any. - #[inline(always)] + #[inline] pub fn color_on(&self, sq: Square) -> Option { Color::iter().find(|&c| self.by_color(c).contains(sq)) } /// The [`Role`] of the piece on the given [`Square`], if any. - #[inline(always)] + #[inline] pub fn role_on(&self, sq: Square) -> Option { Role::iter().find(|&r| self.by_role(r).contains(sq)) } /// The [`Piece`] on the given [`Square`], if any. - #[inline(always)] + #[inline] pub fn piece_on(&self, sq: Square) -> Option { Option::zip(self.role_on(sq), self.color_on(sq)).map(|(r, c)| Piece::new(r, c)) } /// An iterator over all pieces on the board. - #[inline(always)] + #[inline] pub fn iter(&self) -> impl Iterator + '_ { Piece::iter().flat_map(|p| self.by_piece(p).into_iter().map(move |sq| (p, sq))) } @@ -110,7 +111,7 @@ impl Board { /// Computes the [zobrist hash]. /// /// [zobrist hash]: https://www.chessprogramming.org/Zobrist_Hashing - #[inline(always)] + #[inline] pub fn zobrist(&self) -> Zobrist { let mut zobrist = ZobristNumbers::castling(self.castles); @@ -130,7 +131,7 @@ impl Board { } /// Toggles a piece on a square. - #[inline(always)] + #[inline] pub fn toggle(&mut self, p: Piece, sq: Square) { debug_assert!(self[sq].is_none_or(|q| p == q)); self.colors[p.color() as usize] ^= sq.bitboard(); @@ -142,7 +143,7 @@ impl Board { impl Index for Board { type Output = Option; - #[inline(always)] + #[inline] fn index(&self, sq: Square) -> &Self::Output { match self.piece_on(sq) { Some(Piece::WhitePawn) => &Some(Piece::WhitePawn), @@ -166,23 +167,28 @@ impl Display for Board { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut skip = 0; for sq in Square::iter().map(|sq| sq.flip()) { - let mut buffer = ArrayString::<2>::new(); + let mut buffer = [b'\0'; 2]; - match self[sq] { - None => skip += 1, - Some(p) => write!(buffer, "{}", p)?, + if sq.file() == File::H { + buffer[0] = if sq.rank() == Rank::First { b' ' } else { b'/' }; } - if sq.file() == File::H { - buffer.push(if sq.rank() == Rank::First { ' ' } else { '/' }); + match self[sq] { + None => skip += 1, + Some(p) => { + buffer[1] = buffer[0]; + write!(&mut buffer[..1], "{p}").assume() + } } - if !buffer.is_empty() && skip > 0 { - write!(f, "{}", skip)?; + if skip > 0 && buffer != [b'\0'; 2] { + write!(f, "{skip}")?; skip = 0; } - f.write_str(&buffer)?; + for b in buffer.into_iter().take_while(|&b| b != b'\0') { + f.write_char(b.into())?; + } } match self.turn { @@ -197,7 +203,7 @@ impl Display for Board { } if let Some(ep) = self.en_passant { - write!(f, "{} ", ep)?; + write!(f, "{ep} ")?; } else { f.write_str("- ")?; } @@ -230,6 +236,7 @@ pub enum ParseFenError { impl FromStr for Board { type Err = ParseFenError; + #[inline] fn from_str(s: &str) -> Result { let fields: Vec<_> = s.split(' ').collect(); let [board, turn, castles, en_passant, halfmoves, fullmoves] = &fields[..] else { diff --git a/lib/chess/castles.rs b/lib/chess/castles.rs index 59de5846..26323b43 100644 --- a/lib/chess/castles.rs +++ b/lib/chess/castles.rs @@ -25,37 +25,37 @@ pub struct Castles(Bits); impl Castles { /// No castling rights. - #[inline(always)] + #[inline] pub fn none() -> Self { Castles(Bits::new(0b0000)) } /// All castling rights. - #[inline(always)] + #[inline] pub fn all() -> Self { Castles(Bits::new(0b1111)) } /// A unique number the represents this castling rights configuration. - #[inline(always)] + #[inline] pub fn index(&self) -> u8 { self.0.get() } /// Whether the given side has kingside castling rights. - #[inline(always)] + #[inline] pub fn has_short(&self, side: Color) -> bool { *self & Castles::from(Square::H1.perspective(side)) != Castles::none() } /// Whether the given side has queenside castling rights. - #[inline(always)] + #[inline] pub fn has_long(&self, side: Color) -> bool { *self & Castles::from(Square::A1.perspective(side)) != Castles::none() } /// The kingside castling square, if side has the rights. - #[inline(always)] + #[inline] pub fn short(&self, side: Color) -> Option { if self.has_short(side) { Some(Square::G1.perspective(side)) @@ -65,7 +65,7 @@ impl Castles { } /// The queenside castling square, if side has the rights. - #[inline(always)] + #[inline] pub fn long(&self, side: Color) -> Option { if self.has_long(side) { Some(Square::C1.perspective(side)) @@ -76,14 +76,14 @@ impl Castles { } impl Default for Castles { - #[inline(always)] + #[inline] fn default() -> Self { Castles::all() } } impl From for Castles { - #[inline(always)] + #[inline] fn from(sq: Square) -> Self { static CASTLES: SyncUnsafeCell<[Castles; 64]> = unsafe { MaybeUninit::zeroed().assume_init() }; @@ -107,15 +107,15 @@ impl From for Castles { } } -impl fmt::Display for Castles { +impl Display for Castles { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for side in Color::iter() { if self.has_short(side) { - fmt::Display::fmt(&Piece::new(Role::King, side), f)?; + Display::fmt(&Piece::new(Role::King, side), f)?; } if self.has_long(side) { - fmt::Display::fmt(&Piece::new(Role::Queen, side), f)?; + Display::fmt(&Piece::new(Role::Queen, side), f)?; } } @@ -131,6 +131,7 @@ pub struct ParseCastlesError; impl FromStr for Castles { type Err = ParseCastlesError; + #[inline] fn from_str(s: &str) -> Result { let mut castles = Castles::none(); diff --git a/lib/chess/color.rs b/lib/chess/color.rs index 15709f3d..4eb96898 100644 --- a/lib/chess/color.rs +++ b/lib/chess/color.rs @@ -20,7 +20,7 @@ unsafe impl Integer for Color { } impl Perspective for Color { - #[inline(always)] + #[inline] fn flip(&self) -> Self { self.not() } @@ -29,7 +29,7 @@ impl Perspective for Color { impl Not for Color { type Output = Self; - #[inline(always)] + #[inline] fn not(self) -> Self { match self { Color::White => Color::Black, diff --git a/lib/chess/file.rs b/lib/chess/file.rs index 34546ef5..778b9b20 100644 --- a/lib/chess/file.rs +++ b/lib/chess/file.rs @@ -28,7 +28,7 @@ pub enum File { impl File { /// Returns a [`Bitboard`] that only contains this file. - #[inline(always)] + #[inline] pub fn bitboard(self) -> Bitboard { Bitboard::new(0x0101010101010101 << self.get()) } @@ -42,7 +42,7 @@ unsafe impl Integer for File { impl Mirror for File { /// Horizontally mirrors this file. - #[inline(always)] + #[inline] fn mirror(&self) -> Self { Self::new(self.get() ^ Self::MAX) } @@ -51,7 +51,7 @@ impl Mirror for File { impl Sub for File { type Output = i8; - #[inline(always)] + #[inline] fn sub(self, rhs: Self) -> Self::Output { self.get() - rhs.get() } @@ -69,6 +69,7 @@ pub struct ParseFileError; impl FromStr for File { type Err = ParseFileError; + #[inline] fn from_str(s: &str) -> Result { match s { "a" => Ok(File::A), diff --git a/lib/chess/magic.rs b/lib/chess/magic.rs index f7410a9b..436ddbf3 100644 --- a/lib/chess/magic.rs +++ b/lib/chess/magic.rs @@ -4,14 +4,17 @@ use crate::chess::{Bitboard, Square}; pub struct Magic(Bitboard, u64, usize); impl Magic { + #[inline] pub fn mask(&self) -> Bitboard { self.0 } + #[inline] pub fn factor(&self) -> u64 { self.1 } + #[inline] pub fn offset(&self) -> usize { self.2 } diff --git a/lib/chess/move.rs b/lib/chess/move.rs index 115823e5..443450ac 100644 --- a/lib/chess/move.rs +++ b/lib/chess/move.rs @@ -1,6 +1,6 @@ use crate::chess::{Bitboard, Perspective, Piece, Rank, Role, Square, Squares}; use crate::util::{Assume, Binary, Bits, Integer}; -use std::fmt::{self, Write}; +use std::fmt::{self, Debug, Display, Write}; use std::{num::NonZeroU16, ops::RangeBounds}; /// A chess move. @@ -9,13 +9,13 @@ use std::{num::NonZeroU16, ops::RangeBounds}; pub struct Move(NonZeroU16); impl Move { - #[inline(always)] + #[inline] fn bits>(&self, r: R) -> Bits { self.encode().slice(r) } /// Constructs a castling move. - #[inline(always)] + #[inline] pub fn castling(whence: Square, whither: Square) -> Self { let mut bits = Bits::::default(); bits.push(whence.encode()); @@ -25,7 +25,7 @@ impl Move { } /// Constructs an en passant move. - #[inline(always)] + #[inline] pub fn en_passant(whence: Square, whither: Square) -> Self { let mut bits = Bits::::default(); bits.push(whence.encode()); @@ -35,7 +35,7 @@ impl Move { } /// Constructs a regular move. - #[inline(always)] + #[inline] pub fn regular(whence: Square, whither: Square, promotion: Option) -> Self { let mut bits = Bits::::default(); bits.push(whence.encode()); @@ -53,7 +53,7 @@ impl Move { } /// Constructs a capture move. - #[inline(always)] + #[inline] pub fn capture(whence: Square, whither: Square, promotion: Option) -> Self { let mut m = Self::regular(whence, whither, promotion); m.0 |= 0b100; @@ -61,33 +61,33 @@ impl Move { } /// The source [`Square`]. - #[inline(always)] + #[inline] pub fn whence(&self) -> Square { Square::decode(self.bits(10..).pop()) } /// Updates the source [`Square`]. - #[inline(always)] + #[inline] pub fn set_whence(&mut self, whence: Square) { let bits = self.0.get() & 0b0000001111111111 | ((whence as u16) << 10); self.0 = ::new(bits); } /// The destination [`Square`]. - #[inline(always)] + #[inline] pub fn whither(&self) -> Square { Square::decode(self.bits(4..).pop()) } /// This move with a different destination [`Square`]. - #[inline(always)] + #[inline] pub fn set_whither(&mut self, whither: Square) { let bits = (self.0.get() & 0b1111110000001111) | ((whither as u16) << 4); self.0 = ::new(bits); } /// The promotion specifier. - #[inline(always)] + #[inline] pub fn promotion(&self) -> Option { if self.is_promotion() { Some(Role::new(self.bits(..2).cast::() + 1)) @@ -97,7 +97,7 @@ impl Move { } /// This move with a different promotion specifier. - #[inline(always)] + #[inline] pub fn set_promotion(&mut self, promotion: Role) { debug_assert!(self.is_promotion()); let bits = (self.0.get() & 0b1111111111111100) | (promotion as u16 - 1); @@ -105,40 +105,40 @@ impl Move { } /// Whether this is a castling move. - #[inline(always)] + #[inline] pub fn is_castling(&self) -> bool { self.bits(..4) == Bits::new(0b0001) } /// Whether this is an en passant capture move. - #[inline(always)] + #[inline] pub fn is_en_passant(&self) -> bool { self.bits(..4) == Bits::new(0b0110) } /// Whether this is a capture move. - #[inline(always)] + #[inline] pub fn is_capture(&self) -> bool { self.bits(2..=2) != Bits::new(0) } /// Whether this is a promotion move. - #[inline(always)] + #[inline] pub fn is_promotion(&self) -> bool { self.bits(3..=3) != Bits::new(0) } /// Whether this move is neither a capture nor a promotion. - #[inline(always)] + #[inline] pub fn is_quiet(&self) -> bool { self.bits(2..=3) == Bits::new(0) } } -impl fmt::Debug for Move { +impl Debug for Move { #[coverage(off)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self, f)?; + Display::fmt(&self, f)?; if self.is_en_passant() { f.write_char('^')?; @@ -152,13 +152,13 @@ impl fmt::Debug for Move { } } -impl fmt::Display for Move { +impl Display for Move { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.whence(), f)?; - fmt::Display::fmt(&self.whither(), f)?; + Display::fmt(&self.whence(), f)?; + Display::fmt(&self.whither(), f)?; if let Some(r) = self.promotion() { - fmt::Display::fmt(&r, f)?; + Display::fmt(&r, f)?; } Ok(()) @@ -168,12 +168,12 @@ impl fmt::Display for Move { impl Binary for Move { type Bits = Bits; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { self.0.convert().assume() } - #[inline(always)] + #[inline] fn decode(bits: Self::Bits) -> Self { Move(bits.convert().assume()) } @@ -190,21 +190,21 @@ pub struct MoveSet { impl MoveSet { /// A set of castling moves. - #[inline(always)] + #[inline] pub fn castling(whence: Square, whither: Bitboard) -> Self { let base = Move::castling(whence, whence.flip()); MoveSet { base, whither } } /// A set of en passant moves. - #[inline(always)] + #[inline] pub fn en_passant(whence: Square, whither: Bitboard) -> Self { let base = Move::en_passant(whence, whence.flip()); MoveSet { base, whither } } /// A set of regular moves. - #[inline(always)] + #[inline] pub fn regular(piece: Piece, whence: Square, whither: Bitboard) -> Self { use {Piece::*, Rank::*, Role::*}; let base = match (piece, whence.rank()) { @@ -217,7 +217,7 @@ impl MoveSet { } /// A set of capture moves. - #[inline(always)] + #[inline] pub fn capture(piece: Piece, whence: Square, whither: Bitboard) -> Self { use {Piece::*, Rank::*, Role::*}; let base = match (piece, whence.rank()) { @@ -230,49 +230,49 @@ impl MoveSet { } /// The source [`Square`]. - #[inline(always)] + #[inline] pub fn whence(&self) -> Square { self.base.whence() } /// The destination [`Square`]s. - #[inline(always)] + #[inline] pub fn whither(&self) -> Bitboard { self.whither } /// Whether the moves in this set are castling moves. - #[inline(always)] + #[inline] pub fn is_castling(&self) -> bool { self.base.is_castling() } /// Whether the moves in this set are en passant captures. - #[inline(always)] + #[inline] pub fn is_en_passant(&self) -> bool { self.base.is_en_passant() } /// Whether the moves in this set are captures. - #[inline(always)] + #[inline] pub fn is_capture(&self) -> bool { self.base.is_capture() } /// Whether the moves in this set are promotions. - #[inline(always)] + #[inline] pub fn is_promotion(&self) -> bool { self.base.is_promotion() } /// Whether the moves in this set are neither captures nor promotions. - #[inline(always)] + #[inline] pub fn is_quiet(&self) -> bool { self.base.is_quiet() } /// An iterator over the [`Move`]s in this bitboard. - #[inline(always)] + #[inline] pub fn iter(&self) -> Moves { Moves::new(*self) } @@ -282,7 +282,7 @@ impl IntoIterator for MoveSet { type Item = Move; type IntoIter = Moves; - #[inline(always)] + #[inline] fn into_iter(self) -> Self::IntoIter { Moves::new(self) } @@ -296,7 +296,7 @@ pub struct Moves { } impl Moves { - #[inline(always)] + #[inline] fn new(set: MoveSet) -> Self { Moves { base: set.base, @@ -308,7 +308,7 @@ impl Moves { impl Iterator for Moves { type Item = Move; - #[inline(always)] + #[inline] fn next(&mut self) -> Option { if let Some(r @ (Role::Queen | Role::Rook | Role::Bishop)) = self.base.promotion() { self.base.set_promotion(Role::new(r.get() - 1)); @@ -323,14 +323,14 @@ impl Iterator for Moves { Some(self.base) } - #[inline(always)] + #[inline] fn size_hint(&self) -> (usize, Option) { (self.len(), Some(self.len())) } } impl ExactSizeIterator for Moves { - #[inline(always)] + #[inline] fn len(&self) -> usize { match self.base.promotion() { None => self.whither.len(), diff --git a/lib/chess/outcome.rs b/lib/chess/outcome.rs index e7a50f90..98e385ed 100644 --- a/lib/chess/outcome.rs +++ b/lib/chess/outcome.rs @@ -25,16 +25,19 @@ impl Outcome { /// Whether the outcome is a [draw] and neither side has won. /// /// [draw]: https://www.chessprogramming.org/Draw + #[inline] pub fn is_draw(&self) -> bool { !self.is_decisive() } /// Whether the outcome is a decisive and one of the sides has won. + #[inline] pub fn is_decisive(&self) -> bool { matches!(self, Outcome::Checkmate(_)) } /// The winning side, if the outcome is [decisive](`Self::is_decisive`). + #[inline] pub fn winner(&self) -> Option { match *self { Outcome::Checkmate(c) => Some(c), diff --git a/lib/chess/perspective.rs b/lib/chess/perspective.rs index 432e99c5..a2e907c2 100644 --- a/lib/chess/perspective.rs +++ b/lib/chess/perspective.rs @@ -6,7 +6,7 @@ pub trait Perspective: Copy { fn flip(&self) -> Self; /// Sets the perspective to the side of the given [`Color`]. - #[inline(always)] + #[inline] fn perspective(&self, side: Color) -> Self { match side { Color::White => *self, diff --git a/lib/chess/piece.rs b/lib/chess/piece.rs index f6307de8..86bcd268 100644 --- a/lib/chess/piece.rs +++ b/lib/chess/piece.rs @@ -35,7 +35,7 @@ pub enum Piece { } impl Piece { - #[inline(always)] + #[inline] fn bitboard(idx: usize) -> Bitboard { static BITBOARDS: SyncUnsafeCell<[Bitboard; 88772]> = unsafe { MaybeUninit::zeroed().assume_init() }; @@ -105,31 +105,31 @@ impl Piece { } /// Constructs [`Piece`] from a pair of [`Color`] and [`Role`]. - #[inline(always)] + #[inline] pub fn new(r: Role, c: Color) -> Self { ::new(c.get() | r.get() << 1) } /// This piece's [`Role`]. - #[inline(always)] + #[inline] pub fn role(&self) -> Role { Role::new(self.get() >> 1) } /// This piece's [`Color`]. - #[inline(always)] + #[inline] pub fn color(&self) -> Color { Color::new(self.get() & 0b1) } /// This piece's possible target squares from a given square. - #[inline(always)] + #[inline] pub fn targets(&self, whence: Square) -> Bitboard { self.attacks(whence, Bitboard::empty()) } /// This piece's possible attacks from a given square. - #[inline(always)] + #[inline] pub fn attacks(&self, whence: Square, blockers: Bitboard) -> Bitboard { match self.role() { Role::Pawn => { @@ -169,7 +169,7 @@ impl Piece { } /// This piece's possible moves from a given square. - #[inline(always)] + #[inline] pub fn moves(&self, whence: Square, ours: Bitboard, theirs: Bitboard) -> Bitboard { let blockers = ours | theirs; @@ -195,7 +195,7 @@ unsafe impl Integer for Piece { impl Perspective for Piece { /// Mirrors this piece's [`Color`]. - #[inline(always)] + #[inline] fn flip(&self) -> Self { ::new(self.get() ^ Piece::BlackPawn.get()) } @@ -223,6 +223,7 @@ pub struct ParsePieceError; impl FromStr for Piece { type Err = ParsePieceError; + #[inline] fn from_str(s: &str) -> Result { match s { "P" => Ok(Piece::WhitePawn), diff --git a/lib/chess/position.rs b/lib/chess/position.rs index 4832ddb6..7aa2c423 100644 --- a/lib/chess/position.rs +++ b/lib/chess/position.rs @@ -11,7 +11,7 @@ use proptest::{prelude::*, sample::*}; struct Evasions; impl Evasions { - #[inline(always)] + #[inline] fn generate( pos: &Position, buffer: &mut ArrayVec, @@ -77,7 +77,7 @@ impl Evasions { struct Moves; impl Moves { - #[inline(always)] + #[inline] fn generate( pos: &Position, buffer: &mut ArrayVec, @@ -170,7 +170,7 @@ pub struct Position { } impl Default for Position { - #[inline(always)] + #[inline] fn default() -> Self { let board = Board::default(); @@ -185,14 +185,14 @@ impl Default for Position { } impl Hash for Position { - #[inline(always)] + #[inline] fn hash(&self, state: &mut H) { self.board.hash(state); } } impl PartialEq for Position { - #[inline(always)] + #[inline] fn eq(&self, other: &Self) -> bool { self.board.eq(&other.board) } @@ -225,7 +225,7 @@ impl Arbitrary for Position { impl Position { /// The side to move. - #[inline(always)] + #[inline] pub fn turn(&self) -> Color { self.board.turn } @@ -233,7 +233,7 @@ impl Position { /// The number of halfmoves since the last capture or pawn advance. /// /// It resets to 0 whenever a piece is captured or a pawn is moved. - #[inline(always)] + #[inline] pub fn halfmoves(&self) -> u8 { self.board.halfmoves } @@ -241,55 +241,55 @@ impl Position { /// The current move number since the start of the game. /// /// It starts at 1, and is incremented after every move by black. - #[inline(always)] + #[inline] pub fn fullmoves(&self) -> NonZeroU32 { self.board.fullmoves.convert().assume() } /// The en passant square. - #[inline(always)] + #[inline] pub fn en_passant(&self) -> Option { self.board.en_passant } /// The castle rights. - #[inline(always)] + #[inline] pub fn castles(&self) -> Castles { self.board.castles } /// [`Square`]s occupied. - #[inline(always)] + #[inline] pub fn occupied(&self) -> Bitboard { self.material(Color::White) ^ self.material(Color::Black) } /// [`Square`]s occupied by pieces of a [`Color`]. - #[inline(always)] + #[inline] pub fn material(&self, side: Color) -> Bitboard { self.board.by_color(side) } /// [`Square`]s occupied by pawns of a [`Color`]. - #[inline(always)] + #[inline] pub fn pawns(&self, side: Color) -> Bitboard { self.board.by_piece(Piece::new(Role::Pawn, side)) } /// [`Square`]s occupied by pieces other than pawns of a [`Color`]. - #[inline(always)] + #[inline] pub fn pieces(&self, side: Color) -> Bitboard { self.material(side) ^ self.pawns(side) } /// [`Square`] occupied by a the king of a [`Color`]. - #[inline(always)] + #[inline] pub fn king(&self, side: Color) -> Square { self.board.king(side).assume() } /// An iterator over all pieces on the board. - #[inline(always)] + #[inline] pub fn iter(&self) -> impl Iterator + '_ { self.board.iter() } @@ -297,25 +297,25 @@ impl Position { /// This position's [zobrist hash]. /// /// [zobrist hash]: https://www.chessprogramming.org/Zobrist_Hashing - #[inline(always)] + #[inline] pub fn zobrist(&self) -> Zobrist { self.zobrist } /// [`Square`]s occupied by pieces giving check. - #[inline(always)] + #[inline] pub fn checkers(&self) -> Bitboard { self.checkers } /// [`Square`]s occupied by pieces pinned. - #[inline(always)] + #[inline] pub fn pinned(&self) -> Bitboard { self.pinned } /// How many other times this position has repeated. - #[inline(always)] + #[inline] pub fn repetitions(&self) -> usize { match NonZeroU32::new(self.zobrist().cast()) { None => 0, @@ -327,7 +327,7 @@ impl Position { } /// Whether a [`Square`] is threatened by a piece of a [`Color`]. - #[inline(always)] + #[inline] pub fn is_threatened(&self, sq: Square, side: Color, occupied: Bitboard) -> bool { for role in Role::iter() { let piece = Piece::new(role, side); @@ -346,7 +346,7 @@ impl Position { /// Whether this position is a [check]. /// /// [check]: https://www.chessprogramming.org/Check - #[inline(always)] + #[inline] pub fn is_check(&self) -> bool { !self.checkers().is_empty() } @@ -354,7 +354,7 @@ impl Position { /// Whether this position is a [checkmate]. /// /// [checkmate]: https://www.chessprogramming.org/Checkmate - #[inline(always)] + #[inline] pub fn is_checkmate(&self) -> bool { self.is_check() && Evasions::generate(self, &mut ArrayVec::<_, 0>::new()).is_ok() } @@ -362,7 +362,7 @@ impl Position { /// Whether this position is a [stalemate]. /// /// [stalemate]: https://www.chessprogramming.org/Stalemate - #[inline(always)] + #[inline] pub fn is_stalemate(&self) -> bool { !self.is_check() && Moves::generate(self, &mut ArrayVec::<_, 0>::new()).is_ok() } @@ -370,7 +370,7 @@ impl Position { /// Whether the game is a draw by [Threefold repetition]. /// /// [Threefold repetition]: https://en.wikipedia.org/wiki/Threefold_repetition - #[inline(always)] + #[inline] pub fn is_draw_by_threefold_repetition(&self) -> bool { self.repetitions() > 1 } @@ -378,7 +378,7 @@ impl Position { /// Whether the game is a draw by the [50-move rule]. /// /// [50-move rule]: https://en.wikipedia.org/wiki/Fifty-move_rule - #[inline(always)] + #[inline] pub fn is_draw_by_50_move_rule(&self) -> bool { self.halfmoves() >= 100 } @@ -386,7 +386,7 @@ impl Position { /// Whether this position has [insufficient material]. /// /// [insufficient material]: https://www.chessprogramming.org/Material#InsufficientMaterial - #[inline(always)] + #[inline] pub fn is_material_insufficient(&self) -> bool { use Role::*; match self.occupied().len() { @@ -402,7 +402,7 @@ impl Position { } /// The [`Outcome`] of the game in case this position is final. - #[inline(always)] + #[inline] pub fn outcome(&self) -> Option { if self.is_checkmate() { Some(Outcome::Checkmate(!self.turn())) @@ -420,7 +420,7 @@ impl Position { } /// An iterator over the legal moves that can be played in this position. - #[inline(always)] + #[inline] pub fn moves(&self) -> impl Iterator { let mut moves = ArrayVec::<_, 35>::new(); @@ -434,7 +434,7 @@ impl Position { } /// Finds the least valued captor of the piece on a square. - #[inline(always)] + #[inline] pub fn exchange(&self, sq: Square) -> Option { use Role::*; @@ -473,7 +473,7 @@ impl Position { } /// Play a [`Move`]. - #[inline(always)] + #[inline] pub fn play(&mut self, m: Move) -> (Role, Option<(Role, Square)>) { debug_assert!(self.moves().flatten().any(|n| m == n)); @@ -579,7 +579,7 @@ impl Position { /// Play a [null-move]. /// /// [null-move]: https://www.chessprogramming.org/Null_Move - #[inline(always)] + #[inline] pub fn pass(&mut self) { debug_assert!(!self.is_check()); @@ -617,9 +617,8 @@ impl Position { } impl Display for Position { - #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.board, f) + Display::fmt(&self.board, f) } } @@ -635,6 +634,7 @@ pub enum ParsePositionError { impl FromStr for Position { type Err = ParsePositionError; + #[inline] fn from_str(s: &str) -> Result { use {ParsePositionError::*, Role::*}; diff --git a/lib/chess/rank.rs b/lib/chess/rank.rs index 3f101e43..a10c6224 100644 --- a/lib/chess/rank.rs +++ b/lib/chess/rank.rs @@ -28,7 +28,7 @@ pub enum Rank { impl Rank { /// Returns a [`Bitboard`] that only contains this rank. - #[inline(always)] + #[inline] pub fn bitboard(self) -> Bitboard { Bitboard::new(0x000000000000FF << (self.get() * 8)) } @@ -42,7 +42,7 @@ unsafe impl Integer for Rank { impl Perspective for Rank { /// This rank from the opponent's perspective. - #[inline(always)] + #[inline] fn flip(&self) -> Self { Self::new(self.get() ^ Self::MAX) } @@ -51,7 +51,7 @@ impl Perspective for Rank { impl Sub for Rank { type Output = i8; - #[inline(always)] + #[inline] fn sub(self, rhs: Self) -> Self::Output { self.get() - rhs.get() } @@ -69,6 +69,7 @@ pub struct ParseRankError; impl FromStr for Rank { type Err = ParseRankError; + #[inline] fn from_str(s: &str) -> Result { match s { "1" => Ok(Rank::First), diff --git a/lib/chess/role.rs b/lib/chess/role.rs index 23cc429e..ca0532cf 100644 --- a/lib/chess/role.rs +++ b/lib/chess/role.rs @@ -43,6 +43,7 @@ pub struct ParseRoleError; impl FromStr for Role { type Err = ParseRoleError; + #[inline] fn from_str(s: &str) -> Result { match s { "p" => Ok(Role::Pawn), diff --git a/lib/chess/square.rs b/lib/chess/square.rs index 8e6e9284..138b1b89 100644 --- a/lib/chess/square.rs +++ b/lib/chess/square.rs @@ -22,25 +22,25 @@ pub enum Square { impl Square { /// Constructs [`Square`] from a pair of [`File`] and [`Rank`]. - #[inline(always)] + #[inline] pub fn new(f: File, r: Rank) -> Self { ::new(f.get() | r.get() << 3) } /// This square's [`File`]. - #[inline(always)] + #[inline] pub fn file(&self) -> File { File::new(self.get() & 0b111) } /// This square's [`Rank`]. - #[inline(always)] + #[inline] pub fn rank(&self) -> Rank { Rank::new(self.get() >> 3) } /// Returns a [`Bitboard`] that only contains this square. - #[inline(always)] + #[inline] pub fn bitboard(self) -> Bitboard { Bitboard::new(1 << self.get()) } @@ -54,7 +54,7 @@ unsafe impl Integer for Square { impl Mirror for Square { /// Horizontally mirrors this square. - #[inline(always)] + #[inline] fn mirror(&self) -> Self { ::new(self.get() ^ Square::H1.get()) } @@ -62,7 +62,7 @@ impl Mirror for Square { impl Perspective for Square { /// Flips this square's [`Rank`]. - #[inline(always)] + #[inline] fn flip(&self) -> Self { ::new(self.get() ^ Square::A8.get()) } @@ -71,12 +71,12 @@ impl Perspective for Square { impl Binary for Square { type Bits = Bits; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { self.convert().assume() } - #[inline(always)] + #[inline] fn decode(bits: Self::Bits) -> Self { bits.convert().assume() } @@ -85,7 +85,7 @@ impl Binary for Square { impl Sub for Square { type Output = i8; - #[inline(always)] + #[inline] fn sub(self, rhs: Self) -> Self::Output { self.get() - rhs.get() } @@ -94,7 +94,7 @@ impl Sub for Square { impl Sub for Square { type Output = Self; - #[inline(always)] + #[inline] fn sub(self, rhs: i8) -> Self::Output { ::new(self.get() - rhs) } @@ -103,30 +103,30 @@ impl Sub for Square { impl Add for Square { type Output = Self; - #[inline(always)] + #[inline] fn add(self, rhs: i8) -> Self::Output { ::new(self.get() + rhs) } } impl SubAssign for Square { - #[inline(always)] + #[inline] fn sub_assign(&mut self, rhs: i8) { *self = *self - rhs } } impl AddAssign for Square { - #[inline(always)] + #[inline] fn add_assign(&mut self, rhs: i8) { *self = *self + rhs } } -impl fmt::Display for Square { +impl Display for Square { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.file(), f)?; - fmt::Display::fmt(&self.rank(), f)?; + Display::fmt(&self.file(), f)?; + Display::fmt(&self.rank(), f)?; Ok(()) } } @@ -143,8 +143,9 @@ pub enum ParseSquareError { impl FromStr for Square { type Err = ParseSquareError; + #[inline] fn from_str(s: &str) -> Result { - let i = s.char_indices().nth(1).map_or_else(|| s.len(), |(i, _)| i); + let i = s.ceil_char_boundary(1); Ok(Square::new(s[..i].parse()?, s[i..].parse()?)) } } diff --git a/lib/chess/zobrist.rs b/lib/chess/zobrist.rs index 6329c6b0..6ad630e6 100644 --- a/lib/chess/zobrist.rs +++ b/lib/chess/zobrist.rs @@ -33,25 +33,25 @@ unsafe fn init() { } impl ZobristNumbers { - #[inline(always)] + #[inline] pub fn psq(color: Color, role: Role, sq: Square) -> Zobrist { let psq = unsafe { &ZOBRIST.get().as_ref_unchecked().pieces }; Zobrist::new(psq[color as usize][role as usize][sq as usize]) } - #[inline(always)] + #[inline] pub fn castling(castles: Castles) -> Zobrist { let castling = unsafe { &ZOBRIST.get().as_ref_unchecked().castles }; Zobrist::new(*castling.get(castles.index() as usize).assume()) } - #[inline(always)] + #[inline] pub fn en_passant(file: File) -> Zobrist { let en_passant = unsafe { &ZOBRIST.get().as_ref_unchecked().en_passant }; Zobrist::new(en_passant[file as usize]) } - #[inline(always)] + #[inline] pub fn turn() -> Zobrist { Zobrist::new(unsafe { ZOBRIST.get().as_ref_unchecked().turn }) } diff --git a/lib/lib.rs b/lib/lib.rs index 99cf12e2..d2ab2698 100644 --- a/lib/lib.rs +++ b/lib/lib.rs @@ -2,6 +2,7 @@ #![feature( array_chunks, coverage_attribute, + round_char_boundary, new_zeroed_alloc, optimize_attribute, ptr_as_ref_unchecked, diff --git a/lib/nnue.rs b/lib/nnue.rs index 7825f75c..911fbb4d 100644 --- a/lib/nnue.rs +++ b/lib/nnue.rs @@ -46,7 +46,6 @@ unsafe fn init() { } impl Nnue { - #[inline(always)] fn load(&mut self, mut reader: T) -> io::Result<()> { reader.read_i16_into::(&mut *self.ft.bias)?; reader.read_i16_into::(unsafe { @@ -75,17 +74,17 @@ impl Nnue { Ok(()) } - #[inline(always)] + #[inline] fn psqt() -> &'static Transformer { unsafe { &NNUE.get().as_ref_unchecked().psqt } } - #[inline(always)] + #[inline] fn ft() -> &'static Transformer { unsafe { &NNUE.get().as_ref_unchecked().ft } } - #[inline(always)] + #[inline] fn hidden(phase: usize) -> &'static Hidden<{ Positional::LEN }> { unsafe { NNUE.get().as_ref_unchecked().hidden.get(phase).assume() } } diff --git a/lib/nnue/accumulator.rs b/lib/nnue/accumulator.rs index b517e2c3..3bc234b4 100644 --- a/lib/nnue/accumulator.rs +++ b/lib/nnue/accumulator.rs @@ -24,31 +24,31 @@ pub trait Accumulator: Default { impl Accumulator for (T, U) { const LEN: usize = T::LEN + U::LEN; - #[inline(always)] + #[inline] fn refresh(&mut self, side: Color) { self.0.refresh(side); self.1.refresh(side); } - #[inline(always)] + #[inline] fn add(&mut self, side: Color, feature: Feature) { self.0.add(side, feature); self.1.add(side, feature); } - #[inline(always)] + #[inline] fn remove(&mut self, side: Color, feature: Feature) { self.0.remove(side, feature); self.1.remove(side, feature); } - #[inline(always)] + #[inline] fn replace(&mut self, side: Color, remove: Feature, add: Feature) { self.0.replace(side, remove, add); self.1.replace(side, remove, add); } - #[inline(always)] + #[inline] fn evaluate(&self, turn: Color, phase: usize) -> i32 { self.0.evaluate(turn, phase) + self.1.evaluate(turn, phase) } diff --git a/lib/nnue/evaluator.rs b/lib/nnue/evaluator.rs index 5745bf9d..ab575c4a 100644 --- a/lib/nnue/evaluator.rs +++ b/lib/nnue/evaluator.rs @@ -36,6 +36,7 @@ impl Default for Evaluator { impl Evaluator { /// Constructs the evaluator from a [`Position`]. + #[inline] pub fn new(pos: Position) -> Self { let mut acc = T::default(); for side in Color::iter() { @@ -49,6 +50,7 @@ impl Evaluator { } /// The [`Position`]'s evaluation. + #[inline] pub fn evaluate(&self) -> Value { let phase = (self.occupied().len() - 1) / 4; self.acc.evaluate(self.turn(), phase).saturate() @@ -57,6 +59,7 @@ impl Evaluator { /// The Static Exchange Evaluation ([SEE]) algorithm. /// /// [SEE]: https://www.chessprogramming.org/Static_Exchange_Evaluation + #[inline] pub fn see(&mut self, sq: Square, bounds: Range) -> Value { let (mut alpha, mut beta) = (bounds.start, bounds.end); @@ -90,11 +93,13 @@ impl Evaluator { /// Play a [null-move]. /// /// [null-move]: https://www.chessprogramming.org/Null_Move + #[inline] pub fn pass(&mut self) { self.pos.pass(); } /// Play a [`Move`]. + #[inline] pub fn play(&mut self, m: Move) { let turn = self.turn(); let (role, capture) = self.pos.play(m); @@ -144,6 +149,7 @@ impl Evaluator { impl Evaluator { /// The [`Position`]'s material evaluator. + #[inline] pub fn material(&self) -> Evaluator { Evaluator { pos: self.pos.clone(), @@ -152,6 +158,7 @@ impl Evaluator { } /// The [`Position`]'s positional evaluator. + #[inline] pub fn positional(&self) -> Evaluator { Evaluator { pos: self.pos.clone(), diff --git a/lib/nnue/feature.rs b/lib/nnue/feature.rs index 125c8898..701bfc9c 100644 --- a/lib/nnue/feature.rs +++ b/lib/nnue/feature.rs @@ -30,7 +30,7 @@ impl Feature { ]; /// Constructs feature from some perspective. - #[inline(always)] + #[inline] pub fn new(side: Color, ksq: Square, piece: Piece, sq: Square) -> Self { let psq = 64 * piece.perspective(side) as u16 + if ksq.file() <= File::D { diff --git a/lib/nnue/hidden.rs b/lib/nnue/hidden.rs index 925f8066..405aa041 100644 --- a/lib/nnue/hidden.rs +++ b/lib/nnue/hidden.rs @@ -11,8 +11,8 @@ pub struct Hidden { } impl Hidden { + #[inline] #[doc(hidden)] - #[inline(always)] #[cfg(target_feature = "avx2")] pub unsafe fn avx2(&self, us: &[i16; N], them: &[i16; N]) -> i32 { const { assert!(N % 128 == 0) }; @@ -69,8 +69,8 @@ impl Hidden { _mm_extract_epi32(r, 0) } + #[inline] #[doc(hidden)] - #[inline(always)] #[cfg(target_feature = "ssse3")] pub unsafe fn sse(&self, us: &[i16; N], them: &[i16; N]) -> i32 { const { assert!(N % 64 == 0) }; @@ -123,8 +123,8 @@ impl Hidden { _mm_cvtsi128_si32(s) } + #[inline] #[doc(hidden)] - #[inline(always)] pub fn scalar(&self, us: &[i16; N], them: &[i16; N]) -> i32 { let mut y = self.bias; for (w, i) in self.weight.iter().zip([us, them]) { @@ -139,7 +139,7 @@ impl Hidden { impl Hidden { /// Transforms the accumulator. - #[inline(always)] + #[inline] pub fn forward(&self, us: &[i16; N], them: &[i16; N]) -> i32 { #[cfg(target_feature = "avx2")] unsafe { diff --git a/lib/nnue/material.rs b/lib/nnue/material.rs index f2c6c8c8..23bc2786 100644 --- a/lib/nnue/material.rs +++ b/lib/nnue/material.rs @@ -13,7 +13,7 @@ pub struct Material( ); impl Default for Material { - #[inline(always)] + #[inline] fn default() -> Self { Material(AlignTo64([Nnue::psqt().fresh(); 2])) } @@ -22,27 +22,27 @@ impl Default for Material { impl Accumulator for Material { const LEN: usize = 8; - #[inline(always)] + #[inline] fn refresh(&mut self, side: Color) { self.0[side as usize] = Nnue::psqt().fresh(); } - #[inline(always)] + #[inline] fn add(&mut self, side: Color, feature: Feature) { Nnue::psqt().add(feature, &mut self.0[side as usize]); } - #[inline(always)] + #[inline] fn remove(&mut self, side: Color, feature: Feature) { Nnue::psqt().remove(feature, &mut self.0[side as usize]); } - #[inline(always)] + #[inline] fn replace(&mut self, side: Color, remove: Feature, add: Feature) { Nnue::psqt().replace(remove, add, &mut self.0[side as usize]); } - #[inline(always)] + #[inline] fn evaluate(&self, turn: Color, phase: usize) -> i32 { let us = self.0[turn as usize]; let them = self.0[turn.flip() as usize]; diff --git a/lib/nnue/positional.rs b/lib/nnue/positional.rs index 869ff9fa..a417a284 100644 --- a/lib/nnue/positional.rs +++ b/lib/nnue/positional.rs @@ -13,7 +13,7 @@ pub struct Positional( ); impl Default for Positional { - #[inline(always)] + #[inline] fn default() -> Self { Positional(AlignTo64([Nnue::ft().fresh(); 2])) } @@ -22,27 +22,27 @@ impl Default for Positional { impl Accumulator for Positional { const LEN: usize = 768; - #[inline(always)] + #[inline] fn refresh(&mut self, side: Color) { self.0[side as usize] = Nnue::ft().fresh(); } - #[inline(always)] + #[inline] fn add(&mut self, side: Color, feature: Feature) { Nnue::ft().add(feature, &mut self.0[side as usize]); } - #[inline(always)] + #[inline] fn remove(&mut self, side: Color, feature: Feature) { Nnue::ft().remove(feature, &mut self.0[side as usize]); } - #[inline(always)] + #[inline] fn replace(&mut self, side: Color, remove: Feature, add: Feature) { Nnue::ft().replace(remove, add, &mut self.0[side as usize]); } - #[inline(always)] + #[inline] fn evaluate(&self, turn: Color, phase: usize) -> i32 { Nnue::hidden(phase).forward(&self.0[turn as usize], &self.0[turn.flip() as usize]) / 40 } diff --git a/lib/nnue/transformer.rs b/lib/nnue/transformer.rs index 48ccef00..0d0cbda8 100644 --- a/lib/nnue/transformer.rs +++ b/lib/nnue/transformer.rs @@ -46,13 +46,13 @@ where T: Copy + Add + AddAssign + Sub + SubAssign, { /// A fresh accumulator. - #[inline(always)] + #[inline] pub fn fresh(&self) -> [T; N] { *self.bias } /// Updates the accumulator by adding features. - #[inline(always)] + #[inline] pub fn add(&self, feature: Feature, accumulator: &mut [T; N]) { let a = self.weight.get(feature.cast::()).assume().iter(); for (y, a) in accumulator.iter_mut().zip(a) { @@ -61,7 +61,7 @@ where } /// Updates the accumulator by removing features. - #[inline(always)] + #[inline] pub fn remove(&self, feature: Feature, accumulator: &mut [T; N]) { let a = self.weight.get(feature.cast::()).assume().iter(); for (y, a) in accumulator.iter_mut().zip(a) { @@ -70,7 +70,7 @@ where } /// Updates the accumulator by replacing features. - #[inline(always)] + #[inline] pub fn replace(&self, remove: Feature, add: Feature, accumulator: &mut [T; N]) { let a = self.weight.get(add.cast::()).assume().iter(); let b = self.weight.get(remove.cast::()).assume().iter(); diff --git a/lib/nnue/value.rs b/lib/nnue/value.rs index e63e558b..fc4736ac 100644 --- a/lib/nnue/value.rs +++ b/lib/nnue/value.rs @@ -16,7 +16,7 @@ unsafe impl Integer for ValueRepr { pub type Value = Saturating; impl Perspective for Value { - #[inline(always)] + #[inline] fn flip(&self) -> Self { -*self } diff --git a/lib/search/control.rs b/lib/search/control.rs index 812a1473..8c67bcb2 100644 --- a/lib/search/control.rs +++ b/lib/search/control.rs @@ -16,7 +16,7 @@ pub enum Control<'a> { impl Control<'_> { /// A reference to the timer. - #[inline(always)] + #[inline] pub fn timer(&self) -> &Timer { match self { Control::Unlimited => &const { Timer::infinite() }, @@ -25,7 +25,7 @@ impl Control<'_> { } /// Whether the search should be interrupted. - #[inline(always)] + #[inline] pub fn interrupted(&self) -> Result<(), Interrupted> { if let Control::Limited(nodes, timer, trigger) = self { nodes.count().ok_or(Interrupted)?; diff --git a/lib/search/depth.rs b/lib/search/depth.rs index ca4771c3..db72e379 100644 --- a/lib/search/depth.rs +++ b/lib/search/depth.rs @@ -23,12 +23,12 @@ pub type Depth = Saturating; impl Binary for Depth { type Bits = Bits; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { self.convert().assume() } - #[inline(always)] + #[inline] fn decode(bits: Self::Bits) -> Self { bits.convert().assume() } diff --git a/lib/search/driver.rs b/lib/search/driver.rs index 6914e527..61ce574b 100644 --- a/lib/search/driver.rs +++ b/lib/search/driver.rs @@ -20,6 +20,7 @@ pub enum Driver { impl Driver { /// Constructs a parallel search driver with the given [`ThreadCount`]. + #[inline] pub fn new(threads: ThreadCount) -> Self { match threads.get() { 1 => Self::Sequential, @@ -30,7 +31,7 @@ impl Driver { /// Drive the search, possibly across multiple threads in parallel. /// /// The order in which elements are processed and on which thread is unspecified. - #[inline(always)] + #[inline] pub fn drive(&self, mut best: Pv, moves: &[M], f: F) -> Result where M: Sync, @@ -75,7 +76,7 @@ struct IndexedPv(#[deref] Pv, u32); impl Binary for IndexedPv { type Bits = Bits; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { let mut bits = Bits::default(); bits.push(self.score().encode()); @@ -84,7 +85,7 @@ impl Binary for IndexedPv { bits } - #[inline(always)] + #[inline] fn decode(mut bits: Self::Bits) -> Self { let best = Binary::decode(bits.pop()); let idx = bits.pop::().get(); diff --git a/lib/search/engine.rs b/lib/search/engine.rs index c51cb852..01c44713 100644 --- a/lib/search/engine.rs +++ b/lib/search/engine.rs @@ -122,7 +122,7 @@ impl Engine { ply: Ply, ctrl: &Control, ) -> Result { - self.pvs::(pos, Score::lower()..Score::upper(), depth, ply, ctrl) + self.pvs(pos, Score::lower()..Score::upper(), depth, ply, ctrl) } /// A [zero-window] alpha-beta search. @@ -136,14 +136,14 @@ impl Engine { ply: Ply, ctrl: &Control, ) -> Result { - self.pvs::(pos, beta - 1..beta, depth, ply, ctrl) + self.pvs(pos, beta - 1..beta, depth, ply, ctrl) } /// An implementation of the [PVS] variation of [alpha-beta pruning] algorithm. /// /// [PVS]: https://www.chessprogramming.org/Principal_Variation_Search /// [alpha-beta pruning]: https://www.chessprogramming.org/Alpha-Beta - fn pvs( + fn pvs( &self, pos: &Evaluator, bounds: Range, @@ -178,8 +178,9 @@ impl Engine { _ => depth, }; + let is_pv = alpha + 1 < beta; if let Some(t) = transposition { - if !PV && t.depth() >= depth - ply { + if !is_pv && t.depth() >= depth - ply { let (lower, upper) = t.bounds().into_inner(); if lower >= upper || upper <= alpha || lower >= beta { return Ok(Pv::new(t.score().normalize(ply), Some(t.best()))); @@ -202,7 +203,7 @@ impl Engine { if alpha >= beta || ply >= Ply::MAX { return Ok(Pv::new(score, None)); - } else if !PV && !pos.is_check() { + } else if !is_pv && !pos.is_check() { if let Some(d) = self.nmp(pos, score, beta, depth, ply) { let mut next = pos.clone(); next.pass(); @@ -241,7 +242,7 @@ impl Engine { Some((m, _)) => { let mut next = pos.clone(); next.play(m); - m >> -self.pvs::(&next, -beta..-alpha, depth, ply + 1, ctrl)? + m >> -self.pvs(&next, -beta..-alpha, depth, ply + 1, ctrl)? } }; @@ -271,7 +272,7 @@ impl Engine { let pv = match -self.nw(&next, -alpha, depth, ply + 1, ctrl)? { pv if pv <= alpha || pv >= beta => m >> pv, - _ => m >> -self.pvs::(&next, -beta..-alpha, depth, ply + 1, ctrl)?, + _ => m >> -self.pvs(&next, -beta..-alpha, depth, ply + 1, ctrl)?, }; Ok(pv) @@ -321,8 +322,7 @@ impl Engine { break 'id; } - let bounds = lower..upper; - let Ok(partial) = self.pvs::(pos, bounds, depth, Ply::new(0), &ctrl) else { + let Ok(partial) = self.pvs(pos, lower..upper, depth, Ply::new(0), &ctrl) else { break 'id; }; @@ -502,7 +502,7 @@ mod tests { d: Depth, p: Ply, ) { - e.pvs::(&pos, b.end..b.start, d, p, &Control::Unlimited)?; + e.pvs(&pos, b.end..b.start, d, p, &Control::Unlimited)?; } #[proptest] @@ -515,7 +515,7 @@ mod tests { ) { let interrupter = Trigger::armed(); let ctrl = Control::Limited(Counter::new(0), Timer::infinite(), &interrupter); - assert_eq!(e.pvs::(&pos, b, d, p, &ctrl), Err(Interrupted)); + assert_eq!(e.pvs(&pos, b, d, p, &ctrl), Err(Interrupted)); } #[proptest] @@ -533,7 +533,7 @@ mod tests { &interrupter, ); std::thread::sleep(Duration::from_millis(1)); - assert_eq!(e.pvs::(&pos, b, d, p, &ctrl), Err(Interrupted)); + assert_eq!(e.pvs(&pos, b, d, p, &ctrl), Err(Interrupted)); } #[proptest] @@ -546,7 +546,7 @@ mod tests { ) { let interrupter = Trigger::disarmed(); let ctrl = Control::Limited(Counter::new(u64::MAX), Timer::infinite(), &interrupter); - assert_eq!(e.pvs::(&pos, b, d, p, &ctrl), Err(Interrupted)); + assert_eq!(e.pvs(&pos, b, d, p, &ctrl), Err(Interrupted)); } #[proptest] @@ -557,7 +557,7 @@ mod tests { d: Depth, ) { assert_eq!( - e.pvs::(&pos, b, d, Ply::upper(), &Control::Unlimited), + e.pvs(&pos, b, d, Ply::upper(), &Control::Unlimited), Ok(Pv::new(pos.evaluate().saturate(), None)) ); } @@ -571,7 +571,7 @@ mod tests { p: Ply, ) { assert_eq!( - e.pvs::(&pos, b, d, p, &Control::Unlimited), + e.pvs(&pos, b, d, p, &Control::Unlimited), Ok(Pv::new(Score::new(0), None)) ); } @@ -585,7 +585,7 @@ mod tests { p: Ply, ) { assert_eq!( - e.pvs::(&pos, b, d, p, &Control::Unlimited), + e.pvs(&pos, b, d, p, &Control::Unlimited), Ok(Pv::new(Score::lower().normalize(p), None)) ); } diff --git a/lib/search/killers.rs b/lib/search/killers.rs index a26f806e..a6d086da 100644 --- a/lib/search/killers.rs +++ b/lib/search/killers.rs @@ -9,7 +9,7 @@ use crate::{search::Ply, util::Integer}; pub struct Killers([[[Option; 2]; 2]; P]); impl Default for Killers

{ - #[inline(always)] + #[inline] fn default() -> Self { Self::new() } @@ -17,13 +17,13 @@ impl Default for Killers

{ impl Killers

{ /// Constructs an empty set of killer moves. - #[inline(always)] + #[inline] pub const fn new() -> Self { Killers([[[None; 2]; 2]; P]) } /// Adds a killer move to the set at a given ply for a given side to move. - #[inline(always)] + #[inline] pub fn insert(&mut self, ply: Ply, side: Color, m: Move) { if let Some(ks) = self.0.get_mut(ply.cast::()) { let [first, last] = &mut ks[side as usize]; @@ -35,7 +35,7 @@ impl Killers

{ } /// Checks whether move is a known killer at a given ply for a given side to move. - #[inline(always)] + #[inline] pub fn contains(&self, ply: Ply, side: Color, m: Move) -> bool { self.0 .get(ply.cast::()) diff --git a/lib/search/limits.rs b/lib/search/limits.rs index a20012a4..02254abc 100644 --- a/lib/search/limits.rs +++ b/lib/search/limits.rs @@ -25,6 +25,7 @@ pub enum Limits { impl Limits { /// Maximum depth or [`Depth::MAX`]. + #[inline] pub fn depth(&self) -> Depth { match self { Limits::Depth(d) => *d, @@ -33,6 +34,7 @@ impl Limits { } /// Maximum number of nodes [`u64::MAX`]. + #[inline] pub fn nodes(&self) -> u64 { match self { Limits::Nodes(n) => *n, @@ -41,6 +43,7 @@ impl Limits { } /// Maximum time or [`Duration::MAX`]. + #[inline] pub fn time(&self) -> Duration { match self { Limits::Time(t) => *t, @@ -50,6 +53,7 @@ impl Limits { } /// Time left on the clock or [`Duration::MAX`]. + #[inline] pub fn clock(&self) -> Duration { match self { Limits::Clock(t, _) => *t, @@ -58,6 +62,7 @@ impl Limits { } /// Time increment or [`Duration::ZERO`]. + #[inline] pub fn increment(&self) -> Duration { match self { Limits::Clock(_, i) => *i, diff --git a/lib/search/pv.rs b/lib/search/pv.rs index 7ff7964a..468e1051 100644 --- a/lib/search/pv.rs +++ b/lib/search/pv.rs @@ -14,33 +14,33 @@ pub struct Pv { impl Pv { /// Constructs a pv. - #[inline(always)] + #[inline] pub fn new(score: Score, best: Option) -> Self { Pv { score, best } } /// The score from the point of view of the side to move. - #[inline(always)] + #[inline] pub fn score(&self) -> Score { self.score } /// An iterator over [`Move`]s in this principal variation. - #[inline(always)] + #[inline] pub fn best(&self) -> Option { self.best } } impl Ord for Pv { - #[inline(always)] + #[inline] fn cmp(&self, other: &Self) -> Ordering { self.score.cmp(&other.score) } } impl PartialOrd for Pv { - #[inline(always)] + #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } @@ -50,7 +50,7 @@ impl PartialEq for Pv where Score: PartialEq, { - #[inline(always)] + #[inline] fn eq(&self, other: &T) -> bool { self.score.eq(other) } @@ -60,7 +60,7 @@ impl PartialOrd for Pv where Score: PartialOrd, { - #[inline(always)] + #[inline] fn partial_cmp(&self, other: &T) -> Option { self.score.partial_cmp(other) } @@ -69,7 +69,7 @@ where impl Neg for Pv { type Output = Self; - #[inline(always)] + #[inline] fn neg(mut self) -> Self::Output { self.score = -self.score; self @@ -79,7 +79,7 @@ impl Neg for Pv { impl Shr for Move { type Output = Pv; - #[inline(always)] + #[inline] fn shr(self, mut pv: Pv) -> Self::Output { pv.best = Some(self); pv diff --git a/lib/search/score.rs b/lib/search/score.rs index acf77934..463e02ae 100644 --- a/lib/search/score.rs +++ b/lib/search/score.rs @@ -1,6 +1,6 @@ use crate::util::{Binary, Bits, Integer, Saturating}; use crate::{chess::Perspective, search::Ply}; -use std::fmt; +use std::fmt::{self, Display}; #[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[cfg_attr(test, derive(test_strategy::Arbitrary))] @@ -20,6 +20,7 @@ impl Score { /// Returns number of plies to mate, if one is in the horizon. /// /// Negative number of plies means the opponent is mating. + #[inline] pub fn mate(&self) -> Option { if *self <= Score::lower() - Ply::MIN { Some((Score::lower() - *self).saturate()) @@ -31,6 +32,7 @@ impl Score { } /// Normalizes mate scores relative to `ply`. + #[inline] pub fn normalize(&self, ply: Ply) -> Self { if *self <= Score::lower() - Ply::MIN { (*self + ply).min(Score::lower() - Ply::MIN) @@ -43,7 +45,7 @@ impl Score { } impl Perspective for Score { - #[inline(always)] + #[inline] fn flip(&self) -> Self { -*self } @@ -52,18 +54,18 @@ impl Perspective for Score { impl Binary for Score { type Bits = Bits; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { Bits::new((self.get() - Self::lower().get()).cast()) } - #[inline(always)] + #[inline] fn decode(bits: Self::Bits) -> Self { Self::lower() + bits.cast::() } } -impl fmt::Display for Score { +impl Display for Score { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.mate() { Some(p) if p > 0 => write!(f, "{:+}#{}", self.get(), (p.cast::() + 1) / 2), diff --git a/lib/search/transposition.rs b/lib/search/transposition.rs index a8c7dba6..abc63d02 100644 --- a/lib/search/transposition.rs +++ b/lib/search/transposition.rs @@ -30,12 +30,12 @@ unsafe impl Integer for TranspositionKind { impl Binary for TranspositionKind { type Bits = Bits; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { self.convert().assume() } - #[inline(always)] + #[inline] fn decode(bits: Self::Bits) -> Self { bits.convert().assume() } @@ -52,6 +52,7 @@ pub struct Transposition { } impl Transposition { + #[inline] fn new(kind: TranspositionKind, depth: Depth, score: Score, best: Move) -> Self { Transposition { kind, @@ -62,21 +63,25 @@ impl Transposition { } /// Constructs a [`Transposition`] given a lower bound for the score, the depth searched, and best [`Move`]. + #[inline] pub fn lower(depth: Depth, score: Score, best: Move) -> Self { Transposition::new(TranspositionKind::Lower, depth, score, best) } /// Constructs a [`Transposition`] given an upper bound for the score, the depth searched, and best [`Move`]. + #[inline] pub fn upper(depth: Depth, score: Score, best: Move) -> Self { Transposition::new(TranspositionKind::Upper, depth, score, best) } /// Constructs a [`Transposition`] given the exact score, the depth searched, and best [`Move`]. + #[inline] pub fn exact(depth: Depth, score: Score, best: Move) -> Self { Transposition::new(TranspositionKind::Exact, depth, score, best) } /// Bounds for the exact score. + #[inline] pub fn bounds(&self) -> RangeInclusive { match self.kind { TranspositionKind::Lower => self.score..=Score::upper(), @@ -86,16 +91,19 @@ impl Transposition { } /// Depth searched. + #[inline] pub fn depth(&self) -> Depth { self.depth } /// Partial score. + #[inline] pub fn score(&self) -> Score { self.score } /// Best [`Move`] at this depth. + #[inline] pub fn best(&self) -> Move { self.best } @@ -104,7 +112,7 @@ impl Transposition { impl Binary for Transposition { type Bits = Bits; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { let mut bits = Bits::default(); bits.push(self.depth.encode()); @@ -114,7 +122,7 @@ impl Binary for Transposition { bits } - #[inline(always)] + #[inline] fn decode(mut bits: Self::Bits) -> Self { Transposition { best: Binary::decode(bits.pop()), @@ -134,7 +142,7 @@ struct SignedTransposition(Signature, ::Bits); impl Binary for SignedTransposition { type Bits = Bits; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { let mut bits = Bits::default(); bits.push(self.1); @@ -142,7 +150,7 @@ impl Binary for SignedTransposition { bits } - #[inline(always)] + #[inline] fn decode(mut bits: Self::Bits) -> Self { SignedTransposition(bits.pop(), bits.pop()) } @@ -174,6 +182,7 @@ impl TranspositionTable { const WIDTH: usize = size_of::< as Binary>::Bits>(); /// Constructs a transposition table of at most `size` many bytes. + #[inline] pub fn new(size: HashSize) -> Self { let capacity = (1 + size.get() / 2).next_power_of_two() / Self::WIDTH; @@ -183,17 +192,19 @@ impl TranspositionTable { } /// The actual size of this table in bytes. + #[inline] pub fn size(&self) -> HashSize { HashSize::new(self.capacity() * Self::WIDTH) } /// The actual size of this table in number of entries. + #[inline] pub fn capacity(&self) -> usize { self.cache.len() } /// Instructs the CPU to load the slot associated with `key` onto the cache. - #[inline(always)] + #[inline] pub fn prefetch(&self, key: Zobrist) { if self.capacity() > 0 { #[cfg(target_arch = "x86_64")] @@ -205,7 +216,7 @@ impl TranspositionTable { } /// Loads the [`Transposition`] from the slot associated with `key`. - #[inline(always)] + #[inline] pub fn get(&self, key: Zobrist) -> Option { if self.capacity() == 0 { return None; @@ -222,7 +233,7 @@ impl TranspositionTable { /// Stores a [`Transposition`] in the slot associated with `key`. /// /// In the slot if not empty, the [`Transposition`] with greater depth is chosen. - #[inline(always)] + #[inline] pub fn set(&self, key: Zobrist, tpos: Transposition) { if self.capacity() > 0 { let sig = self.sign(key); @@ -232,7 +243,7 @@ impl TranspositionTable { } /// Returns the [`Signature`] associated with `key`. - #[inline(always)] + #[inline] pub fn sign(&self, key: Zobrist) -> Signature { key.slice(self.capacity().trailing_zeros()..).pop() } @@ -241,7 +252,7 @@ impl TranspositionTable { impl Index for [AtomicU64] { type Output = AtomicU64; - #[inline(always)] + #[inline] fn index(&self, key: Zobrist) -> &Self::Output { let idx: usize = key.slice(..self.len().trailing_zeros()).cast(); self.get(idx).assume() diff --git a/lib/uci.rs b/lib/uci.rs index 07fba8e1..5e3d8570 100644 --- a/lib/uci.rs +++ b/lib/uci.rs @@ -2,11 +2,11 @@ use crate::chess::{Color, Move, Perspective}; use crate::nnue::Evaluator; use crate::search::{Engine, HashSize, Limits, Options, Score, ThreadCount}; use crate::util::{Assume, Integer, Trigger}; -use arrayvec::ArrayString; use derive_more::{Deref, Display}; -use futures::{channel::oneshot, future::FusedFuture, prelude::*, select_biased as select}; +use futures::channel::oneshot::channel as oneshot; +use futures::{future::FusedFuture, prelude::*, select_biased as select}; use std::time::{Duration, Instant}; -use std::{fmt::Debug, mem::transmute, thread}; +use std::{fmt::Debug, io::Write, mem::transmute, str, thread}; #[cfg(test)] use proptest::prelude::*; @@ -17,13 +17,14 @@ use proptest::prelude::*; /// /// Must be awaited on through completion strictly before any /// of the variables `f` may capture is dropped. +#[inline] #[must_use] unsafe fn unblock<'a, F, R>(f: F) -> impl FusedFuture + 'a where F: FnOnce() -> R + Send + 'a, R: Send + 'a, { - let (tx, rx) = oneshot::channel(); + let (tx, rx) = oneshot(); thread::spawn(transmute::< Box, Box, @@ -31,23 +32,33 @@ where rx.map(Assume::assume) } -#[derive(Debug, Display, Default, Clone, Eq, PartialEq, Hash, Deref)] +#[derive(Debug, Display, Default, Clone, Eq, PartialEq, Hash)] #[cfg_attr(test, derive(test_strategy::Arbitrary))] -struct UciMove( - #[deref(forward)] - #[cfg_attr(test, map(|m: Move| ArrayString::from(&m.to_string()).unwrap()))] - ArrayString<5>, -); +#[display("{}", *self)] +struct UciMove([u8; 5]); -impl From for UciMove { - fn from(m: Move) -> Self { - Self(ArrayString::from(&m.to_string()).assume()) +impl Deref for UciMove { + type Target = str; + + #[inline] + fn deref(&self) -> &Self::Target { + let len = if self.0[4] == b'\0' { 4 } else { 5 }; + unsafe { str::from_utf8_unchecked(&self.0[..len]) } } } impl PartialEq<&str> for UciMove { + #[inline] fn eq(&self, other: &&str) -> bool { - self.0.eq(*other) + **self == **other + } +} + +impl From for UciMove { + fn from(m: Move) -> Self { + let mut buffer = [b'\0'; 5]; + write!(&mut buffer[..], "{m}").assume(); + Self(buffer) } } @@ -93,18 +104,25 @@ impl + Unpin, O: Sink + Unpin> Uci { Ok(()) } - fn play(&mut self, uci: &str) { - let mut moves = self.position.moves().flatten(); - let Some(m) = moves.find(|&m| UciMove::from(m) == uci) else { - return if !(0..=5).contains(&uci.len()) || !uci.is_ascii() { - eprintln!("invalid move `{uci}`") - } else { - eprintln!("illegal move `{uci}` in position `{}`", self.position) - }; - }; + fn play(&mut self, s: &str) { + if (4..=5).contains(&s.len()) && s.is_ascii() { + if let Ok(whence) = s[..2].parse() { + for ms in self.position.moves() { + if ms.whence() == whence { + for m in ms { + let uci = UciMove::from(m); + if uci == s { + self.position.play(m); + self.moves.push(uci); + return; + } + } + } + } + } + } - self.position.play(m); - self.moves.push(UciMove::from(m)); + eprintln!("illegal move `{s}` in position `{}`", self.position) } async fn go(&mut self, limits: &Limits) -> Result<(), O::Error> { @@ -113,21 +131,18 @@ impl + Unpin, O: Sink + Unpin> Uci { let mut search = unsafe { unblock(|| self.engine.search(&self.position, limits, &interrupter)) }; - let stop = async { - loop { - match self.input.next().await.as_deref().map(str::trim) { - None => break false, - Some("stop") => break interrupter.disarm(), - Some(cmd) => eprintln!("ignored unsupported command `{cmd}` during search"), - }; + let pv = loop { + select! { + pv = search => break pv, + line = self.input.next().fuse() => { + match line.as_deref().map(str::trim) { + None | Some("stop") => { interrupter.disarm(); }, + Some(cmd) => eprintln!("ignored unsupported command `{cmd}` during search"), + } + } } }; - let pv = select! { - pv = search => pv, - _ = stop.fuse() => search.await - }; - let best = pv.best().expect("the engine failed to find a move"); let info = match pv.score().mate() { Some(p) if p > 0 => format!("info score mate {} pv {best}", (p + 1).get() / 2), diff --git a/lib/util/assume.rs b/lib/util/assume.rs index c1eb46c9..277bc9a2 100644 --- a/lib/util/assume.rs +++ b/lib/util/assume.rs @@ -10,10 +10,10 @@ pub trait Assume { impl Assume for Option { type Assumed = T; + #[inline] #[track_caller] - #[inline(always)] fn assume(self) -> Self::Assumed { - // Definitely not safe, but we'll assume unit tests will catch everything. + // Definitely not safe, but we'll be careful. unsafe { self.unwrap_unchecked() } } } @@ -21,10 +21,10 @@ impl Assume for Option { impl Assume for Result { type Assumed = T; + #[inline] #[track_caller] - #[inline(always)] fn assume(self) -> Self::Assumed { - // Definitely not safe, but we'll assume unit tests will catch everything. + // Definitely not safe, but we'll be careful. unsafe { self.unwrap_unchecked() } } } diff --git a/lib/util/binary.rs b/lib/util/binary.rs index 18a459dd..2637956d 100644 --- a/lib/util/binary.rs +++ b/lib/util/binary.rs @@ -16,12 +16,12 @@ pub trait Binary: Sized { impl Binary for Bits { type Bits = Self; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { *self } - #[inline(always)] + #[inline] fn decode(bits: Self::Bits) -> Self { bits } @@ -34,7 +34,7 @@ where { type Bits = T::Bits; - #[inline(always)] + #[inline] fn encode(&self) -> Self::Bits { match self { None => T::Bits::default(), @@ -46,7 +46,7 @@ where } } - #[inline(always)] + #[inline] fn decode(bits: Self::Bits) -> Self { if bits == T::Bits::default() { None diff --git a/lib/util/bits.rs b/lib/util/bits.rs index ef607fb3..53c14c7a 100644 --- a/lib/util/bits.rs +++ b/lib/util/bits.rs @@ -42,7 +42,7 @@ unsafe impl Integer for Bits { impl Bits { /// Returns a slice of bits. - #[inline(always)] + #[inline] pub fn slice>(&self, r: R) -> Self { let a = match r.start_bound() { Bound::Included(&i) => i, @@ -60,13 +60,13 @@ impl Bits { } /// Shifts bits into the collection. - #[inline(always)] + #[inline] pub fn push(&mut self, bits: Bits) { *self = Bits::new((self.get() << N.cast()) & T::ones(W) ^ bits.cast()); } /// Shifts bits out of the collection. - #[inline(always)] + #[inline] pub fn pop(&mut self) -> Bits { let bits = Bits::new(self.cast::() & U::ones(N)); *self = Bits::new(self.get() >> N.cast()); @@ -75,7 +75,7 @@ impl Bits { } impl Default for Bits { - #[inline(always)] + #[inline] fn default() -> Self { Bits::new(T::zero()) } @@ -84,7 +84,7 @@ impl Default for Bits { impl Not for Bits { type Output = Self; - #[inline(always)] + #[inline] fn not(self) -> Self::Output { self ^ Bits::new(T::ones(W)) } diff --git a/lib/util/counter.rs b/lib/util/counter.rs index 71144d0b..9a8d2615 100644 --- a/lib/util/counter.rs +++ b/lib/util/counter.rs @@ -8,7 +8,7 @@ pub struct Counter { impl Counter { /// Constructs a counter with the given limit. - #[inline(always)] + #[inline] pub fn new(limit: u64) -> Self { Counter { remaining: AtomicU64::new(limit), @@ -16,7 +16,7 @@ impl Counter { } /// Increments the counter and returns the number of counts remaining if any. - #[inline(always)] + #[inline] pub fn count(&self) -> Option { self.remaining .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |c| c.checked_sub(1)) diff --git a/lib/util/integer.rs b/lib/util/integer.rs index 3abf7b2c..4ff0e236 100644 --- a/lib/util/integer.rs +++ b/lib/util/integer.rs @@ -1,4 +1,4 @@ -use std::num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}; +use std::num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}; use std::{mem::transmute_copy, ops::*}; /// Trait for types that can be represented by a contiguous range of primitive integers. @@ -17,26 +17,26 @@ pub unsafe trait Integer: Copy { const MAX: Self::Repr; /// The minimum value. - #[inline(always)] + #[inline] fn lower() -> Self { Self::new(Self::MIN) } /// The maximum value. - #[inline(always)] + #[inline] fn upper() -> Self { Self::new(Self::MAX) } /// Casts from [`Integer::Repr`]. - #[inline(always)] + #[inline] fn new(i: Self::Repr) -> Self { debug_assert!((Self::MIN..=Self::MAX).contains(&i)); unsafe { transmute_copy(&i) } } /// Casts to [`Integer::Repr`]. - #[inline(always)] + #[inline] fn get(self) -> Self::Repr { unsafe { transmute_copy(&self) } } @@ -44,19 +44,19 @@ pub unsafe trait Integer: Copy { /// Casts to a [`Primitive`]. /// /// This is equivalent to the operator `as`. - #[inline(always)] + #[inline] fn cast(self) -> I { self.get().cast() } /// Converts to another [`Integer`] if possible without data loss. - #[inline(always)] + #[inline] fn convert(self) -> Option { self.get().convert() } /// Converts to another [`Integer`] with saturation. - #[inline(always)] + #[inline] fn saturate(self) -> I { let min = I::MIN.convert().unwrap_or(Self::MIN); let max = I::MAX.convert().unwrap_or(Self::MAX); @@ -64,7 +64,7 @@ pub unsafe trait Integer: Copy { } /// An iterator over all values in the range [`Integer::MIN`]..=[`Integer::MAX`]. - #[inline(always)] + #[inline] fn iter() -> impl ExactSizeIterator + DoubleEndedIterator where RangeInclusive: ExactSizeIterator + DoubleEndedIterator, @@ -104,7 +104,7 @@ pub trait Primitive: const BITS: u32; /// The constant `0`. - #[inline(always)] + #[inline] fn zero() -> Self { Self::ones(0) } @@ -133,6 +133,7 @@ impl_integer_for_non_zero!(NonZeroU8, u8); impl_integer_for_non_zero!(NonZeroU16, u16); impl_integer_for_non_zero!(NonZeroU32, u32); impl_integer_for_non_zero!(NonZeroU64, u64); +impl_integer_for_non_zero!(NonZeroU128, u128); impl_integer_for_non_zero!(NonZeroUsize, usize); macro_rules! impl_primitive_for { @@ -142,7 +143,7 @@ macro_rules! impl_primitive_for { impl Primitive for $i { const BITS: u32 = <$i>::BITS; - #[inline(always)] + #[inline] fn ones(n: u32) -> Self { match n { 0 => 0, @@ -157,7 +158,7 @@ macro_rules! impl_primitive_for { const MIN: Self::Repr = <$i>::MIN; const MAX: Self::Repr = <$i>::MAX; - #[inline(always)] + #[inline] fn cast(self) -> I { if I::BITS <= Self::BITS { unsafe { transmute_copy(&self) } @@ -172,7 +173,7 @@ macro_rules! impl_primitive_for { } } - #[inline(always)] + #[inline] fn convert(self) -> Option { let i = self.cast(); diff --git a/lib/util/saturating.rs b/lib/util/saturating.rs index 9102439b..159e4a5c 100644 --- a/lib/util/saturating.rs +++ b/lib/util/saturating.rs @@ -1,6 +1,6 @@ -use crate::util::{Integer, Signed}; +use crate::util::Integer; use std::ops::{Add, Div, Mul, Neg, Sub}; -use std::{cmp::Ordering, mem::size_of}; +use std::{cmp::Ordering, mem::size_of, num::Saturating as S}; /// A saturating bounded integer. #[derive(Debug, Default, Copy, Clone, Hash)] @@ -16,157 +16,115 @@ unsafe impl Integer for Saturating { impl Eq for Saturating where Self: PartialEq {} -impl PartialEq for Saturating -where - T: Integer, - U: Integer, - I: Signed, - J: Signed, -{ - #[inline(always)] +impl PartialEq for Saturating { + #[inline] fn eq(&self, other: &U) -> bool { - if size_of::() <= size_of::() { - J::eq(&self.cast(), &other.cast()) + if size_of::() > size_of::() { + T::Repr::eq(&self.get(), &other.cast()) } else { - I::eq(&self.cast(), &other.cast()) + U::Repr::eq(&self.cast(), &other.get()) } } } -impl Ord for Saturating -where - T: Integer, - I: Signed + Ord, -{ - #[inline(always)] +impl Ord for Saturating { + #[inline] fn cmp(&self, other: &Self) -> Ordering { self.get().cmp(&other.get()) } } -impl PartialOrd for Saturating -where - T: Integer, - U: Integer, - I: Signed, - J: Signed, -{ - #[inline(always)] +impl PartialOrd for Saturating { + #[inline] fn partial_cmp(&self, other: &U) -> Option { - if size_of::() <= size_of::() { - J::partial_cmp(&self.cast(), &other.cast()) + if size_of::() > size_of::() { + T::Repr::partial_cmp(&self.get(), &other.cast()) } else { - I::partial_cmp(&self.cast(), &other.cast()) + U::Repr::partial_cmp(&self.cast(), &other.get()) } } } -impl Neg for Saturating +impl Neg for Saturating where - T: Integer, - I: Widen, - J: Signed + Neg, + S: Neg>, { type Output = Self; - #[inline(always)] + #[inline] fn neg(self) -> Self::Output { - J::neg(self.cast()).saturate() + S(self.get()).neg().0.saturate() } } -impl Add for Saturating +impl Add for Saturating where - T: Integer, - U: Integer, - I: Widen, - J: Widen, + S: Add>, + S: Add>, { type Output = Self; - #[inline(always)] + #[inline] fn add(self, rhs: U) -> Self::Output { - if size_of::() <= size_of::() { - J::Wider::add(self.cast(), rhs.cast()).saturate() + if size_of::() > size_of::() { + S::add(S(self.get()), S(rhs.cast())).0.saturate() } else { - I::Wider::add(self.cast(), rhs.cast()).saturate() + S::add(S(self.cast()), S(rhs.get())).0.saturate() } } } -impl Sub for Saturating +impl Sub for Saturating where - T: Integer, - U: Integer, - I: Widen, - J: Widen, + S: Sub>, + S: Sub>, { type Output = Self; - #[inline(always)] + #[inline] fn sub(self, rhs: U) -> Self::Output { - if size_of::() <= size_of::() { - J::Wider::sub(self.cast(), rhs.cast()).saturate() + if size_of::() > size_of::() { + S::sub(S(self.get()), S(rhs.cast())).0.saturate() } else { - I::Wider::sub(self.cast(), rhs.cast()).saturate() + S::sub(S(self.cast()), S(rhs.get())).0.saturate() } } } -impl Mul for Saturating +impl Mul for Saturating where - T: Integer, - U: Integer, - I: Widen, - J: Widen, + S: Mul>, + S: Mul>, { type Output = Self; - #[inline(always)] + #[inline] fn mul(self, rhs: U) -> Self::Output { - if size_of::() <= size_of::() { - J::Wider::mul(self.cast(), rhs.cast()).saturate() + if size_of::() > size_of::() { + S::mul(S(self.get()), S(rhs.cast())).0.saturate() } else { - I::Wider::mul(self.cast(), rhs.cast()).saturate() + S::mul(S(self.cast()), S(rhs.get())).0.saturate() } } } -impl Div for Saturating +impl Div for Saturating where - T: Integer, - U: Integer, - I: Widen, - J: Widen, + S: Div>, + S: Div>, { type Output = Self; - #[inline(always)] + #[inline] fn div(self, rhs: U) -> Self::Output { - if size_of::() <= size_of::() { - J::Wider::div(self.cast(), rhs.cast()).saturate() + if size_of::() > size_of::() { + S::div(S(self.get()), S(rhs.cast())).0.saturate() } else { - I::Wider::div(self.cast(), rhs.cast()).saturate() + S::div(S(self.cast()), S(rhs.get())).0.saturate() } } } -trait Widen: Signed { - type Wider: Signed; -} - -impl Widen for i8 { - type Wider = i16; -} - -impl Widen for i16 { - type Wider = i32; -} - -impl Widen for i32 { - type Wider = i64; -} - #[cfg(test)] mod tests { use super::*; diff --git a/lib/util/timer.rs b/lib/util/timer.rs index e82bdf31..777169d5 100644 --- a/lib/util/timer.rs +++ b/lib/util/timer.rs @@ -8,13 +8,13 @@ pub struct Timer { impl Timer { /// Constructs a timer that elapses after the given duration. - #[inline(always)] + #[inline] pub const fn infinite() -> Self { Timer { deadline: None } } /// Constructs a timer that elapses after the given duration. - #[inline(always)] + #[inline] pub fn new(duration: Duration) -> Self { Timer { deadline: Instant::now().checked_add(duration), @@ -22,7 +22,7 @@ impl Timer { } /// Returns the time remaining if any. - #[inline(always)] + #[inline] pub fn remaining(&self) -> Option { match self.deadline { Some(deadline) => deadline.checked_duration_since(Instant::now()), diff --git a/lib/util/trigger.rs b/lib/util/trigger.rs index 4e4f44d1..a27f032e 100644 --- a/lib/util/trigger.rs +++ b/lib/util/trigger.rs @@ -6,13 +6,13 @@ pub struct Trigger(AtomicBool); impl Trigger { /// An armed trigger. - #[inline(always)] + #[inline] pub const fn armed() -> Self { Trigger(AtomicBool::new(true)) } /// A disarmed trigger. - #[inline(always)] + #[inline] pub const fn disarmed() -> Self { Trigger(AtomicBool::new(false)) } @@ -20,13 +20,13 @@ impl Trigger { /// Disarm the trigger. /// /// Returns `true` if the trigger was disarmed for the first time. - #[inline(always)] + #[inline] pub fn disarm(&self) -> bool { self.0.fetch_and(false, Ordering::Relaxed) } /// Whether the trigger is armed. - #[inline(always)] + #[inline] pub fn is_armed(&self) -> bool { self.0.load(Ordering::Relaxed) }