1#![cfg_attr(not(target_arch = "x86_64"), allow(dead_code))]
15
16use crate::magic::{self, BISHOP_DIRS, BISHOP_MASKS, ROOK_DIRS, ROOK_MASKS};
17use crate::types::*;
18use std::sync::OnceLock;
19
20pub(crate) fn has_bmi2() -> bool {
24 #[cfg(target_arch = "x86_64")]
25 {
26 std::arch::is_x86_feature_detected!("bmi2")
27 }
28 #[cfg(not(target_arch = "x86_64"))]
29 {
30 false
31 }
32}
33
34fn pext_soft(val: u64, mask: u64) -> u64 {
39 let mut result = 0u64;
40 let mut bit = 0u64;
41 let mut m = mask;
42 while m != 0 {
43 let lsb = m & m.wrapping_neg();
45 if val & lsb != 0 {
46 result |= 1 << bit;
47 }
48 bit += 1;
49 m ^= lsb;
51 }
52 result
53}
54
55struct PextLayout {
60 popcounts: [u32; 64],
61 offsets: [usize; 64],
62 total: usize,
63}
64
65const fn compute_pext_layout(masks: &[Bitboard; 64]) -> PextLayout {
71 let mut popcounts = [0u32; 64];
72 let mut offsets = [0usize; 64];
73 let mut total = 0usize;
74 let mut i = 0;
75 while i < 64 {
76 let pc = masks[i].0.count_ones();
77 popcounts[i] = pc;
78 offsets[i] = total;
79 total += 1usize << pc;
80 i += 1;
81 }
82 PextLayout {
83 popcounts,
84 offsets,
85 total,
86 }
87}
88
89const ROOK_LAYOUT: PextLayout = compute_pext_layout(&ROOK_MASKS);
90const BISHOP_LAYOUT: PextLayout = compute_pext_layout(&BISHOP_MASKS);
91
92fn build_pext_table(
94 directions: &[(i8, i8)],
95 masks: &[Bitboard; 64],
96 offsets: &[usize; 64],
97 total_size: usize,
98) -> Box<[Bitboard]> {
99 let mut table = vec![Bitboard::EMPTY; total_size].into_boxed_slice();
100
101 for sq in 0..64 {
102 let mask = masks[sq].0;
103 let offset = offsets[sq];
104 let sq_enum = Square::from_u8(sq as u8);
105
106 let mut subset = 0u64;
108 loop {
109 let attacks = magic::sliding_attack(directions, sq_enum, Bitboard(subset));
110 let idx = offset + pext_soft(subset, mask) as usize;
111 table[idx] = attacks;
112
113 subset = subset.wrapping_sub(mask) & mask;
115 if subset == 0 {
116 break;
117 }
118 }
119 }
120
121 table
122}
123
124static ROOK_PEXT_TABLE: OnceLock<&[Bitboard]> = OnceLock::new();
125static BISHOP_PEXT_TABLE: OnceLock<&[Bitboard]> = OnceLock::new();
126
127pub(crate) fn init() {
129 _ = ROOK_PEXT_TABLE.set(Box::leak(build_pext_table(
130 &ROOK_DIRS,
131 &ROOK_MASKS,
132 &ROOK_LAYOUT.offsets,
133 ROOK_LAYOUT.total,
134 )));
135 _ = BISHOP_PEXT_TABLE.set(Box::leak(build_pext_table(
136 &BISHOP_DIRS,
137 &BISHOP_MASKS,
138 &BISHOP_LAYOUT.offsets,
139 BISHOP_LAYOUT.total,
140 )));
141}
142
143#[cfg(target_arch = "x86_64")]
144#[target_feature(enable = "bmi2")]
145unsafe fn bishop_attacks_pext_impl(sq: Square, occupied: Bitboard) -> Bitboard {
146 let table = BISHOP_PEXT_TABLE
147 .get()
148 .expect("PEXT tables not initialized — call attacks::init()");
149 let sq_idx = sq as usize;
150 let mask = BISHOP_MASKS[sq_idx];
151 let occ = occupied & mask;
152 let idx = core::arch::x86_64::_pext_u64(occ.0, mask.0) as usize;
153 table[BISHOP_LAYOUT.offsets[sq_idx] + idx]
154}
155
156#[cfg(target_arch = "x86_64")]
157#[target_feature(enable = "bmi2")]
158unsafe fn rook_attacks_pext_impl(sq: Square, occupied: Bitboard) -> Bitboard {
159 let table = ROOK_PEXT_TABLE
160 .get()
161 .expect("PEXT tables not initialized — call attacks::init()");
162 let sq_idx = sq as usize;
163 let mask = ROOK_MASKS[sq_idx];
164 let occ = occupied & mask;
165 let idx = core::arch::x86_64::_pext_u64(occ.0, mask.0) as usize;
166 table[ROOK_LAYOUT.offsets[sq_idx] + idx]
167}
168
169#[cfg(not(target_arch = "x86_64"))]
171unsafe fn bishop_attacks_pext_impl(_sq: Square, _occupied: Bitboard) -> Bitboard {
172 unreachable!("PEXT not available on non-x86_64")
173}
174
175#[cfg(not(target_arch = "x86_64"))]
176unsafe fn rook_attacks_pext_impl(_sq: Square, _occupied: Bitboard) -> Bitboard {
177 unreachable!("PEXT not available on non-x86_64")
178}
179
180#[inline(always)]
189pub(crate) fn bishop_attacks_pext(sq: Square, occupied: Bitboard) -> Bitboard {
190 unsafe { bishop_attacks_pext_impl(sq, occupied) }
193}
194
195#[inline(always)]
204pub(crate) fn rook_attacks_pext(sq: Square, occupied: Bitboard) -> Bitboard {
205 unsafe { rook_attacks_pext_impl(sq, occupied) }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_pext_soft_identity() {
216 assert_eq!(pext_soft(0xDEADBEEF, 0xFFFFFFFF), 0xDEADBEEF);
218 }
219
220 #[test]
221 fn test_pext_soft_compact() {
222 assert_eq!(pext_soft(0b10101, 0b10101), 0b111);
224 }
225
226 #[test]
227 fn test_pext_soft_sparse() {
228 assert_eq!(pext_soft(0xFF, 0x0101010101010101), 1);
231 }
232
233 #[test]
234 fn test_pext_soft_zero() {
235 assert_eq!(pext_soft(0, 0xFFFFFFFF), 0);
236 assert_eq!(pext_soft(0xFFFFFFFF, 0), 0);
237 }
238
239 #[test]
242 fn test_pext_vs_loop_bishop() {
243 super::init();
245 let table = BISHOP_PEXT_TABLE
246 .get()
247 .expect("PEXT tables not initialized");
248 for sq_idx in 0..64 {
249 let sq = Square::from_u8(sq_idx as u8);
250 let mask = BISHOP_MASKS[sq_idx].0;
251 let size = 1usize << BISHOP_LAYOUT.popcounts[sq_idx];
252 let mut count = 0;
253 let mut subset = 0u64;
254 loop {
255 let occ = Bitboard(subset);
256 let pext_idx = BISHOP_LAYOUT.offsets[sq_idx] + pext_soft(subset, mask) as usize;
258 let pext_atk = table[pext_idx];
259 let loop_atk = magic::sliding_attack(&BISHOP_DIRS, sq, occ);
260 assert_eq!(
261 pext_atk, loop_atk,
262 "Bishop PEXT mismatch at sq={:?}, occ=0x{:x}",
263 sq, subset
264 );
265 count += 1;
266 subset = subset.wrapping_sub(mask) & mask;
267 if subset == 0 {
268 break;
269 }
270 }
271 assert_eq!(count, size, "Bishop PEXT count mismatch at sq={:?}", sq);
272 }
273 }
274
275 #[test]
276 fn test_pext_vs_loop_rook() {
277 super::init();
278 let table = ROOK_PEXT_TABLE.get().expect("PEXT tables not initialized");
279 for sq_idx in 0..64 {
280 let sq = Square::from_u8(sq_idx as u8);
281 let mask = ROOK_MASKS[sq_idx].0;
282 let size = 1usize << ROOK_LAYOUT.popcounts[sq_idx];
283 let mut count = 0;
284 let mut subset = 0u64;
285 loop {
286 let occ = Bitboard(subset);
287 let pext_idx = ROOK_LAYOUT.offsets[sq_idx] + pext_soft(subset, mask) as usize;
288 let pext_atk = table[pext_idx];
289 let loop_atk = magic::sliding_attack(&ROOK_DIRS, sq, occ);
290 assert_eq!(
291 pext_atk, loop_atk,
292 "Rook PEXT mismatch at sq={:?}, occ=0x{:x}",
293 sq, subset
294 );
295 count += 1;
296 subset = subset.wrapping_sub(mask) & mask;
297 if subset == 0 {
298 break;
299 }
300 }
301 assert_eq!(count, size, "Rook PEXT count mismatch at sq={:?}", sq);
302 }
303 }
304
305 #[test]
308 fn test_pext_hardware_vs_loop() {
309 if !has_bmi2() {
310 return;
311 }
312 crate::attacks::init();
314
315 for sq_idx in [0, 7, 9, 27, 36, 56, 63] {
317 let sq = Square::from_u8(sq_idx as u8);
318 for occ_val in [0u64, 0xFF, 0xFFFF, 0xDEADBEEF, 0xFFFFFFFFFFFFFFFF] {
319 let occ = Bitboard(occ_val);
320 let pext_atk = bishop_attacks_pext(sq, occ);
321 let loop_atk = magic::sliding_attack(&BISHOP_DIRS, sq, occ);
322 assert_eq!(
323 pext_atk, loop_atk,
324 "Bishop HW PEXT mismatch at sq={:?}, occ=0x{:x}",
325 sq, occ_val
326 );
327
328 let pext_atk = rook_attacks_pext(sq, occ);
329 let loop_atk = magic::sliding_attack(&ROOK_DIRS, sq, occ);
330 assert_eq!(
331 pext_atk, loop_atk,
332 "Rook HW PEXT mismatch at sq={:?}, occ=0x{:x}",
333 sq, occ_val
334 );
335 }
336 }
337 }
338}