diff --git a/hexe_core/src/lib.rs b/hexe_core/src/lib.rs index cb6cf0f..9c14d41 100644 --- a/hexe_core/src/lib.rs +++ b/hexe_core/src/lib.rs @@ -95,6 +95,8 @@ pub mod mv; pub mod piece; pub mod square; +pub mod simd; + // Modules shared with hexe that aren't meant for public use #[doc(hidden)] pub mod _shared { diff --git a/hexe_core/src/magic/mod.rs b/hexe_core/src/magic/mod.rs index ad5f1ad..e5da90f 100644 --- a/hexe_core/src/magic/mod.rs +++ b/hexe_core/src/magic/mod.rs @@ -7,6 +7,60 @@ pub use self::tables::*; const BISHOP_SHIFT: u8 = 64 - 09; const ROOK_SHIFT: u8 = 64 - 12; +#[cfg(feature = "simd")] +pub mod simd { + macro_rules! sliding { + ($l:ident, $n:expr, $s:ident, $($tmp:ident),+) => { + #[allow(non_snake_case)] + pub mod $l { + use super::super::{Magic, tables, Table, ROOK_SHIFT, BISHOP_SHIFT}; + use simd::{Level, $l}; + use core::simd::$s; + + pub type Square = <$l as Level>::Square; + + #[inline] + fn extract(table: &Table, [$($tmp),+]: Square) -> [&Magic; $n] { + use misc::Extract; + [$($tmp.extract(table)),+] + } + + #[inline] + fn attacks([$($tmp),+]: [&Magic; $n], occupied: $s, shift: u8) -> $s { + use core::mem; + + let mask = $s::new($($tmp.mask),+); + let num = $s::new($($tmp.num),+); + let idx = $s::new($($tmp.idx as _),+) + + (((occupied & mask) * num) >> shift); + + let [$($tmp),+] = unsafe { + mem::transmute::<_, [u64; $n]>(idx) + }; + + unsafe { $s::new( + $(*tables::ATTACKS.get_unchecked($tmp as usize)),+ + ) } + } + + #[inline] + pub fn rook_attacks(sq: Square, occupied: $s) -> $s { + attacks(extract(&tables::MAGICS.rook, sq), occupied, ROOK_SHIFT) + } + + #[inline] + pub fn bishop_attacks(sq: Square, occupied: $s) -> $s { + attacks(extract(&tables::MAGICS.bishop, sq), occupied, BISHOP_SHIFT) + } + } + }; + } + + sliding! { L2, 2, u64x2, a, b } + sliding! { L4, 4, u64x4, a, b, c, d } + sliding! { L8, 8, u64x8, a, b, c, d, e, f, g, h } +} + type Table = [Magic; 64]; // Fixed shift magic diff --git a/hexe_core/src/simd.rs b/hexe_core/src/simd.rs new file mode 100644 index 0000000..4c0d6da --- /dev/null +++ b/hexe_core/src/simd.rs @@ -0,0 +1,150 @@ +//! [SIMD](https://en.wikipedia.org/wiki/Single_Instruction_Multiple_Data) +//! parallelism. + +use core::ops::BitOr; + +#[cfg(feature = "simd")] +use core::simd::{u64x2, u64x4, u64x8}; + +use board::BitBoard; +use sealed::Sealed; +use square::Square; + +/// The minimum level (1). +pub type LMin = L1; + +/// The maximum level (8). +#[cfg(feature = "simd")] +pub type LMax = L8; + +/// The maximum level (1). +#[cfg(not(feature = "simd"))] +pub type LMax = L1; + +/// The level of parallelism to use in operations. +pub trait Level: Sealed { + /// The `BitBoard` type. + type BitBoard: Copy + BitOr; + + /// The `Square` type. + type Square: Copy; + + /// An integral value for the level used. This is always a power of two. + const LEVEL: usize; + + /// Returns the bishop attacks for each square and each occupied board. + fn bishop_attacks(sq: Self::Square, occupied: Self::BitBoard) -> Self::BitBoard; + + /// Returns the rook attacks for each square and each occupied board. + fn rook_attacks(sq: Self::Square, occupied: Self::BitBoard) -> Self::BitBoard; + + /// Returns the queen attacks for each square and each occupied board. + #[inline] + fn queen_attacks(sq: Self::Square, occupied: Self::BitBoard) -> Self::BitBoard { + Self::bishop_attacks(sq, occupied) | Self::rook_attacks(sq, occupied) + } +} + +/// Only one of each type will be used. No parallelism is used. +#[derive(Copy, Clone, Debug)] +pub struct L1; + +impl Sealed for L1 {} + +impl Level for L1 { + type BitBoard = BitBoard; + type Square = Square; + + const LEVEL: usize = 1; + + #[inline] + fn bishop_attacks(sq: Square, occupied: BitBoard) -> BitBoard { + ::magic::bishop_attacks(sq, occupied) + } + + #[inline] + fn rook_attacks(sq: Square, occupied: BitBoard) -> BitBoard { + ::magic::rook_attacks(sq, occupied) + } +} + +#[cfg(feature = "simd")] +macro_rules! levels { + ($($d:expr, $l:ident, $n:expr, $bb:ty, $($tmp:ident),+;)+) => { + $( + #[doc = $d] + #[derive(Copy, Clone, Debug)] + pub struct $l; + + impl Sealed for $l {} + + impl Level for $l { + type BitBoard = $bb; + type Square = [Square; $n]; + + const LEVEL: usize = $n; + + #[inline] + fn bishop_attacks(sq: Self::Square, occupied: Self::BitBoard) -> Self::BitBoard { + ::magic::simd::$l::bishop_attacks(sq, occupied) + } + + #[inline] + fn rook_attacks(sq: Self::Square, occupied: Self::BitBoard) -> Self::BitBoard { + ::magic::simd::$l::rook_attacks(sq, occupied) + } + } + )+ + + #[cfg(test)] + mod tests { + use super::*; + use core::mem; + + #[test] + fn attacks() { + static SQ_FNS: [fn(Square, BitBoard) -> BitBoard; 2] = [ + Square::bishop_attacks, + Square::rook_attacks, + ]; + + use rand::{Rng, thread_rng}; + use square::Square; + + let mut rng = thread_rng(); + + $(for _ in 0..(20_000 / $l::LEVEL) { + type Array = [T; $l::LEVEL]; + + let squares = rng.gen::>(); + let board = rng.gen::>(); + let occupied: $bb = unsafe { mem::transmute(board) }; + + static FNS: [fn(Array, $bb) -> $bb; 2] = [ + $l::bishop_attacks, + $l::rook_attacks, + ]; + + for (v, s) in FNS.iter().zip(SQ_FNS.iter()) { + + // Get moves for the input vectors + let val = v(squares, occupied); + let val: Array = unsafe { mem::transmute(val) }; + + for (i, &x) in val.iter().enumerate() { + let y = s(squares[i], board[i].into()).0; + assert_eq!(x, y); + } + } + })+ + } + } + } +} + +#[cfg(feature = "simd")] +levels! { + "Two of each type will be used.", L2, 2, u64x2, a, b; + "Four of each type will be used.", L4, 4, u64x4, a, b, c, d; + "Eight of each type will be used.", L8, 8, u64x8, a, b, c, d, e, f, g, h; +}