#![cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
use crate::magic::{self, BISHOP_DIRS, BISHOP_MASKS, ROOK_DIRS, ROOK_MASKS};
use crate::types::*;
use std::sync::OnceLock;
pub(crate) fn has_bmi2() -> bool {
#[cfg(target_arch = "x86_64")]
{
std::arch::is_x86_feature_detected!("bmi2")
}
#[cfg(not(target_arch = "x86_64"))]
{
false
}
}
fn pext_soft(val: u64, mask: u64) -> u64 {
let mut result = 0u64;
let mut bit = 0u64;
let mut m = mask;
while m != 0 {
let lsb = m & m.wrapping_neg();
if val & lsb != 0 {
result |= 1 << bit;
}
bit += 1;
m ^= lsb;
}
result
}
struct PextLayout {
popcounts: [u32; 64],
offsets: [usize; 64],
total: usize,
}
const fn compute_pext_layout(masks: &[Bitboard; 64]) -> PextLayout {
let mut popcounts = [0u32; 64];
let mut offsets = [0usize; 64];
let mut total = 0usize;
let mut i = 0;
while i < 64 {
let pc = masks[i].0.count_ones();
popcounts[i] = pc;
offsets[i] = total;
total += 1usize << pc;
i += 1;
}
PextLayout {
popcounts,
offsets,
total,
}
}
const ROOK_LAYOUT: PextLayout = compute_pext_layout(&ROOK_MASKS);
const BISHOP_LAYOUT: PextLayout = compute_pext_layout(&BISHOP_MASKS);
fn build_pext_table(
directions: &[(i8, i8)],
masks: &[Bitboard; 64],
offsets: &[usize; 64],
total_size: usize,
) -> Box<[Bitboard]> {
let mut table = vec![Bitboard::EMPTY; total_size].into_boxed_slice();
for sq in 0..64 {
let mask = masks[sq].0;
let offset = offsets[sq];
let sq_enum = Square::from_u8(sq as u8);
let mut subset = 0u64;
loop {
let attacks = magic::sliding_attack(directions, sq_enum, Bitboard(subset));
let idx = offset + pext_soft(subset, mask) as usize;
table[idx] = attacks;
subset = subset.wrapping_sub(mask) & mask;
if subset == 0 {
break;
}
}
}
table
}
static ROOK_PEXT_TABLE: OnceLock<&[Bitboard]> = OnceLock::new();
static BISHOP_PEXT_TABLE: OnceLock<&[Bitboard]> = OnceLock::new();
pub(crate) fn init() {
_ = ROOK_PEXT_TABLE.set(Box::leak(build_pext_table(
&ROOK_DIRS,
&ROOK_MASKS,
&ROOK_LAYOUT.offsets,
ROOK_LAYOUT.total,
)));
_ = BISHOP_PEXT_TABLE.set(Box::leak(build_pext_table(
&BISHOP_DIRS,
&BISHOP_MASKS,
&BISHOP_LAYOUT.offsets,
BISHOP_LAYOUT.total,
)));
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "bmi2")]
unsafe fn bishop_attacks_pext_impl(sq: Square, occupied: Bitboard) -> Bitboard {
let table = BISHOP_PEXT_TABLE
.get()
.expect("PEXT tables not initialized — call attacks::init()");
let sq_idx = sq as usize;
let mask = BISHOP_MASKS[sq_idx];
let occ = occupied & mask;
let idx = core::arch::x86_64::_pext_u64(occ.0, mask.0) as usize;
table[BISHOP_LAYOUT.offsets[sq_idx] + idx]
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "bmi2")]
unsafe fn rook_attacks_pext_impl(sq: Square, occupied: Bitboard) -> Bitboard {
let table = ROOK_PEXT_TABLE
.get()
.expect("PEXT tables not initialized — call attacks::init()");
let sq_idx = sq as usize;
let mask = ROOK_MASKS[sq_idx];
let occ = occupied & mask;
let idx = core::arch::x86_64::_pext_u64(occ.0, mask.0) as usize;
table[ROOK_LAYOUT.offsets[sq_idx] + idx]
}
#[cfg(not(target_arch = "x86_64"))]
unsafe fn bishop_attacks_pext_impl(_sq: Square, _occupied: Bitboard) -> Bitboard {
unreachable!("PEXT not available on non-x86_64")
}
#[cfg(not(target_arch = "x86_64"))]
unsafe fn rook_attacks_pext_impl(_sq: Square, _occupied: Bitboard) -> Bitboard {
unreachable!("PEXT not available on non-x86_64")
}
#[inline(always)]
pub(crate) fn bishop_attacks_pext(sq: Square, occupied: Bitboard) -> Bitboard {
unsafe { bishop_attacks_pext_impl(sq, occupied) }
}
#[inline(always)]
pub(crate) fn rook_attacks_pext(sq: Square, occupied: Bitboard) -> Bitboard {
unsafe { rook_attacks_pext_impl(sq, occupied) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pext_soft_identity() {
assert_eq!(pext_soft(0xDEADBEEF, 0xFFFFFFFF), 0xDEADBEEF);
}
#[test]
fn test_pext_soft_compact() {
assert_eq!(pext_soft(0b10101, 0b10101), 0b111);
}
#[test]
fn test_pext_soft_sparse() {
assert_eq!(pext_soft(0xFF, 0x0101010101010101), 1);
}
#[test]
fn test_pext_soft_zero() {
assert_eq!(pext_soft(0, 0xFFFFFFFF), 0);
assert_eq!(pext_soft(0xFFFFFFFF, 0), 0);
}
#[test]
fn test_pext_vs_loop_bishop() {
super::init();
let table = BISHOP_PEXT_TABLE
.get()
.expect("PEXT tables not initialized");
for sq_idx in 0..64 {
let sq = Square::from_u8(sq_idx as u8);
let mask = BISHOP_MASKS[sq_idx].0;
let size = 1usize << BISHOP_LAYOUT.popcounts[sq_idx];
let mut count = 0;
let mut subset = 0u64;
loop {
let occ = Bitboard(subset);
let pext_idx = BISHOP_LAYOUT.offsets[sq_idx] + pext_soft(subset, mask) as usize;
let pext_atk = table[pext_idx];
let loop_atk = magic::sliding_attack(&BISHOP_DIRS, sq, occ);
assert_eq!(
pext_atk, loop_atk,
"Bishop PEXT mismatch at sq={:?}, occ=0x{:x}",
sq, subset
);
count += 1;
subset = subset.wrapping_sub(mask) & mask;
if subset == 0 {
break;
}
}
assert_eq!(count, size, "Bishop PEXT count mismatch at sq={:?}", sq);
}
}
#[test]
fn test_pext_vs_loop_rook() {
super::init();
let table = ROOK_PEXT_TABLE.get().expect("PEXT tables not initialized");
for sq_idx in 0..64 {
let sq = Square::from_u8(sq_idx as u8);
let mask = ROOK_MASKS[sq_idx].0;
let size = 1usize << ROOK_LAYOUT.popcounts[sq_idx];
let mut count = 0;
let mut subset = 0u64;
loop {
let occ = Bitboard(subset);
let pext_idx = ROOK_LAYOUT.offsets[sq_idx] + pext_soft(subset, mask) as usize;
let pext_atk = table[pext_idx];
let loop_atk = magic::sliding_attack(&ROOK_DIRS, sq, occ);
assert_eq!(
pext_atk, loop_atk,
"Rook PEXT mismatch at sq={:?}, occ=0x{:x}",
sq, subset
);
count += 1;
subset = subset.wrapping_sub(mask) & mask;
if subset == 0 {
break;
}
}
assert_eq!(count, size, "Rook PEXT count mismatch at sq={:?}", sq);
}
}
#[test]
fn test_pext_hardware_vs_loop() {
if !has_bmi2() {
return;
}
crate::attacks::init();
for sq_idx in [0, 7, 9, 27, 36, 56, 63] {
let sq = Square::from_u8(sq_idx as u8);
for occ_val in [0u64, 0xFF, 0xFFFF, 0xDEADBEEF, 0xFFFFFFFFFFFFFFFF] {
let occ = Bitboard(occ_val);
let pext_atk = bishop_attacks_pext(sq, occ);
let loop_atk = magic::sliding_attack(&BISHOP_DIRS, sq, occ);
assert_eq!(
pext_atk, loop_atk,
"Bishop HW PEXT mismatch at sq={:?}, occ=0x{:x}",
sq, occ_val
);
let pext_atk = rook_attacks_pext(sq, occ);
let loop_atk = magic::sliding_attack(&ROOK_DIRS, sq, occ);
assert_eq!(
pext_atk, loop_atk,
"Rook HW PEXT mismatch at sq={:?}, occ=0x{:x}",
sq, occ_val
);
}
}
}
}