Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow choosing between compile-time or runtime generation of large attack tables. #79

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ alloc = []
std = ["alloc", "btoi/std", "nohash-hasher?/std"]
variant = []
nohash-hasher = ["dep:nohash-hasher"]
runtime-lut = ["alloc", "dep:lazy_static"]

[[bench]]
name = "benches"
Expand All @@ -30,6 +31,7 @@ bitflags = "2.0.0"
btoi = { version = "0.4", default-features = false }
arrayvec = { version = "0.7", default-features = false }
nohash-hasher = { version = "0.2", default-features = false, optional = true }
lazy_static = { version = "1.5.0", optional = true }

[dev-dependencies]
csv = "1.3"
Expand Down
196 changes: 133 additions & 63 deletions src/bootstrap.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
// Initialize static lookup tables at compile time.
//! Contains static lookup tables, which by default are initialized at compile
//! time.

#[cfg(feature = "runtime-lut")]
use alloc::boxed::Box;

use crate::magics;

Expand Down Expand Up @@ -50,81 +54,147 @@ pub static KING_ATTACKS: [u64; 64] = init_stepping_attacks(&KING_DELTAS);
pub static WHITE_PAWN_ATTACKS: [u64; 64] = init_stepping_attacks(&WHITE_PAWN_DELTAS);
pub static BLACK_PAWN_ATTACKS: [u64; 64] = init_stepping_attacks(&BLACK_PAWN_DELTAS);

const fn init_rays() -> [[u64; 64]; 64] {
let mut table = [[0; 64]; 64];
let mut a = 0;
while a < 64 {
let mut b = 0;
while b < 64 {
table[a as usize][b as usize] = if a == b {
0
} else if a & 7 == b & 7 {
0x0101_0101_0101_0101 << (a & 7)
} else if a >> 3 == b >> 3 {
0xff << (8 * (a >> 3))
} else {
let diag = (a >> 3) - (a & 7);
let anti_diag = (a >> 3) + (a & 7) - 7;
if diag == (b >> 3) - (b & 7) {
if diag >= 0 {
0x8040_2010_0804_0201 << (8 * diag)
} else {
0x8040_2010_0804_0201 >> (8 * -diag)
}
} else if anti_diag == (b >> 3) + (b & 7) - 7 {
if anti_diag >= 0 {
0x0102_0408_1020_4080 << (8 * anti_diag)

// Credit for these macros goes to <https://stackoverflow.com/a/49480186>.
// They wrap a function definition and make it `const` when the `runtime-lut`
// feature is off.
#[cfg(not(feature = "runtime-lut"))]
macro_rules! maybe_const_fn {
($(#[$($meta:meta)*])* $vis:vis $ident:ident $($tokens:tt)*) => {
$(#[$($meta)*])* $vis const $ident $($tokens)*
};
}

#[cfg(feature = "runtime-lut")]
macro_rules! maybe_const_fn {
($($tokens:tt)*) => {
$($tokens)*
};
}

#[cfg(not(feature = "runtime-lut"))]
type RayTable = [[u64; 64]; 64];

#[cfg(feature = "runtime-lut")]
type RayTable = Box<[[u64; 64]; 64]>;

maybe_const_fn! {
fn init_rays() -> RayTable {
#[cfg(not(feature = "runtime-lut"))]
let mut table = [[0; 64]; 64];
#[cfg(feature = "runtime-lut")]
let mut table = Box::new([[0; 64]; 64]);
let mut a = 0;
while a < 64 {
let mut b = 0;
while b < 64 {
table[a as usize][b as usize] = if a == b {
0
} else if a & 7 == b & 7 {
0x0101_0101_0101_0101 << (a & 7)
} else if a >> 3 == b >> 3 {
0xff << (8 * (a >> 3))
} else {
let diag = (a >> 3) - (a & 7);
let anti_diag = (a >> 3) + (a & 7) - 7;
if diag == (b >> 3) - (b & 7) {
if diag >= 0 {
0x8040_2010_0804_0201 << (8 * diag)
} else {
0x8040_2010_0804_0201 >> (8 * -diag)
}
} else if anti_diag == (b >> 3) + (b & 7) - 7 {
if anti_diag >= 0 {
0x0102_0408_1020_4080 << (8 * anti_diag)
} else {
0x0102_0408_1020_4080 >> (8 * -anti_diag)
}
} else {
0x0102_0408_1020_4080 >> (8 * -anti_diag)
0
}
} else {
0
}
};
b += 1;
};
b += 1;
}
a += 1;
}
a += 1;
table
}
table
}

#[cfg(not(feature = "runtime-lut"))]
pub static RAYS: [[u64; 64]; 64] = init_rays();

const fn init_magics() -> [u64; 88772] {
let mut table = [0; 88772];
let mut square = 0;
while square < 64 {
let magic = &magics::BISHOP_MAGICS[square as usize];
let range = magic.mask;
let mut subset = 0;
loop {
let attack = sliding_attacks(square, subset, &BISHOP_DELTAS);
let idx = (magic.factor.wrapping_mul(subset) >> (64 - 9)) as usize + magic.offset;
assert!(table[idx] == 0 || table[idx] == attack);
table[idx] = attack;
subset = subset.wrapping_sub(range) & range;
if subset == 0 {
break;
#[cfg(feature = "runtime-lut")]
lazy_static::lazy_static! {
pub static ref RAYS: Box<[[u64; 64]; 64]> = init_rays();
}

#[cfg(not(feature = "runtime-lut"))]
type AttackTable = [u64; 88772];

#[cfg(feature = "runtime-lut")]
type AttackTable = Box<[u64; 88772]>;

maybe_const_fn! {
fn init_magics() -> AttackTable {
#[cfg(not(feature = "runtime-lut"))]
let mut table = [0; 88772];
#[cfg(feature = "runtime-lut")]
let mut table = Box::new([0; 88772]);

let mut square = 0;
while square < 64 {
let magic = &magics::BISHOP_MAGICS[square as usize];
let range = magic.mask;
let mut subset = 0;
loop {
let attack = sliding_attacks(square, subset, &BISHOP_DELTAS);
let idx = (magic.factor.wrapping_mul(subset) >> (64 - 9)) as usize + magic.offset;
assert!(table[idx] == 0 || table[idx] == attack);
table[idx] = attack;
subset = subset.wrapping_sub(range) & range;
if subset == 0 {
break;
}
}
}

let magic = &magics::ROOK_MAGICS[square as usize];
let range = magic.mask;
let mut subset = 0;
loop {
let attack = sliding_attacks(square, subset, &ROOK_DELTAS);
let idx = (magic.factor.wrapping_mul(subset) >> (64 - 12)) as usize + magic.offset;
assert!(table[idx] == 0 || table[idx] == attack);
table[idx] = attack;
subset = subset.wrapping_sub(range) & range;
if subset == 0 {
break;
let magic = &magics::ROOK_MAGICS[square as usize];
let range = magic.mask;
let mut subset = 0;
loop {
let attack = sliding_attacks(square, subset, &ROOK_DELTAS);
let idx = (magic.factor.wrapping_mul(subset) >> (64 - 12)) as usize + magic.offset;
assert!(table[idx] == 0 || table[idx] == attack);
table[idx] = attack;
subset = subset.wrapping_sub(range) & range;
if subset == 0 {
break;
}
}

square += 1;
}
table
}
}

square += 1;
#[cfg(not(feature = "runtime-lut"))]
pub static ATTACKS: AttackTable = init_magics();

#[cfg(feature = "runtime-lut")]
lazy_static::lazy_static! {
pub static ref ATTACKS: AttackTable = init_magics();
}

/// When shakmaty is compiled with the `runtime-lut` feature enabled, this
/// function can be called to explicitly initialize the internal attack tables.
/// Otherwise, the tables will be initialized on first use.
///
/// When `runtime-lut` is not enabled this function is a no-op.
pub fn init_tables() {
#[cfg(feature = "runtime-lut")] {
lazy_static::initialize(&RAYS);
lazy_static::initialize(&ATTACKS);
}
table
}

pub static ATTACKS: [u64; 88772] = init_magics();
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@
//! * `nohash-hasher`: Implements
//! [`nohash_hasher::IsEnabled`](https://docs.rs/nohash-hasher/0.2/nohash_hasher/trait.IsEnabled.html)
//! for sensible types.
//! * `runtime-lut`: Shakmaty uses internal tables to speed up its algorithms.
//! By default, these tables are generated at compile time and embedded within
//! the built binary. Some of these tables are quite large, so if you are
//! concerned about binary size, you can enable this feature to instead
//! have the tables generated at runtime. Tables will be initialised the
//! first time shakmaty needs them, or if you would like predictability
//! you can manually trigger initialization with the [`init_tables`]
//! function.

#![no_std]
#![doc(html_root_url = "https://docs.rs/shakmaty/0.27.1")]
Expand Down Expand Up @@ -100,6 +108,7 @@ pub mod variant;

pub use bitboard::Bitboard;
pub use board::Board;
pub use bootstrap::init_tables;
pub use castling_side::{ByCastlingSide, CastlingSide};
pub use color::{ByColor, Color, ParseColorError};
pub use movelist::MoveList;
Expand Down