Skip to content

Commit b974fbe

Browse files
authored
Set-wise knight/bishop/rook attack generation (#909)
Locally 1.03 ± 0.02 times faster. VSTC (AVX512) Elo | 1.64 +- 1.32 (95%) SPRT | 4.0+0.04s Threads=1 Hash=16MB LLR | 2.92 (-2.25, 2.89) [0.00, 3.00] Games | N: 69852 W: 18158 L: 17829 D: 33865 Penta | [355, 7623, 18657, 7920, 371] https://recklesschess.space/test/13592/ No functional change. Bench: 2900111
1 parent 7893298 commit b974fbe

5 files changed

Lines changed: 186 additions & 72 deletions

File tree

src/board.rs

Lines changed: 17 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use crate::{
22
lookup::{
33
attacks, between, bishop_attacks, cuckoo, cuckoo_a, cuckoo_b, h1, h2, king_attacks, knight_attacks,
4-
pawn_attacks, pawn_attacks_setwise, queen_attacks, ray_pass, rook_attacks,
4+
pawn_attacks, queen_attacks, ray_pass, rook_attacks,
55
},
6+
setwise::{bishop_attacks_setwise, knight_attacks_setwise, pawn_attacks_setwise, rook_attacks_setwise},
67
types::{
78
Bitboard, Castling, CastlingKind, Color, File, Move, PAWN_HOME_RANK, PROMO_RANK, Piece, PieceType, Square,
89
ZOBRIST,
@@ -412,33 +413,22 @@ impl Board {
412413
// This "hack" is used to speed up the implementation of `Board::is_legal`.
413414
let stm = self.side_to_move();
414415
let occupancies = self.occupancies() ^ self.colored_pieces(stm, PieceType::King);
415-
let mut threats = pawn_attacks_setwise(self.colored_pieces(!stm, PieceType::Pawn), !stm);
416-
self.state.piece_threats[PieceType::Pawn] = threats;
417-
418-
threats = Bitboard(0);
419-
for square in self.colored_pieces(!stm, PieceType::Knight) {
420-
threats |= knight_attacks(square);
421-
}
422-
self.state.piece_threats[PieceType::Knight] = threats;
423-
424-
threats = Bitboard(0);
425-
for square in self.colored_pieces(!stm, PieceType::Bishop) {
426-
threats |= bishop_attacks(square, occupancies);
427-
}
428-
self.state.piece_threats[PieceType::Bishop] = threats;
429-
430-
threats = Bitboard(0);
431-
for square in self.colored_pieces(!stm, PieceType::Rook) {
432-
threats |= rook_attacks(square, occupancies);
433-
}
434-
self.state.piece_threats[PieceType::Rook] = threats;
435-
436-
threats = Bitboard(0);
437-
for square in self.colored_pieces(!stm, PieceType::Queen) {
438-
threats |= queen_attacks(square, occupancies);
439-
}
440-
self.state.piece_threats[PieceType::Queen] = threats;
441416

417+
self.state.piece_threats[PieceType::Pawn] =
418+
pawn_attacks_setwise(self.colored_pieces(!stm, PieceType::Pawn), !stm);
419+
self.state.piece_threats[PieceType::Knight] =
420+
knight_attacks_setwise(self.colored_pieces(!stm, PieceType::Knight));
421+
self.state.piece_threats[PieceType::Bishop] =
422+
bishop_attacks_setwise(self.colored_pieces(!stm, PieceType::Bishop), occupancies);
423+
self.state.piece_threats[PieceType::Rook] =
424+
rook_attacks_setwise(self.colored_pieces(!stm, PieceType::Rook), occupancies);
425+
self.state.piece_threats[PieceType::Queen] = {
426+
let mut threats = Bitboard(0);
427+
for square in self.colored_pieces(!stm, PieceType::Queen) {
428+
threats |= queen_attacks(square, occupancies);
429+
}
430+
threats
431+
};
442432
self.state.piece_threats[PieceType::King] = king_attacks(self.king_square(!stm));
443433

444434
self.state.all_threats = self.piece_threats(PieceType::Pawn)

src/lookup.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::types::{Bitboard, Color, File, Piece, PieceType, Square, ZOBRIST};
1+
use crate::types::{Bitboard, Color, Piece, PieceType, Square, ZOBRIST};
22

33
include!(concat!(env!("OUT_DIR"), "/lookup.rs"));
44

@@ -144,18 +144,6 @@ pub fn attacks(piece: Piece, square: Square, occupancies: Bitboard) -> Bitboard
144144
}
145145
}
146146

147-
pub fn pawn_attacks_setwise(bb: Bitboard, color: Color) -> Bitboard {
148-
let (up_right, up_left) = match color {
149-
Color::White => (9, 7),
150-
Color::Black => (-7, -9),
151-
};
152-
153-
let right_attacks = (bb & !Bitboard::file(File::H)).shift(up_right);
154-
let left_attacks = (bb & !Bitboard::file(File::A)).shift(up_left);
155-
156-
right_attacks | left_attacks
157-
}
158-
159147
pub fn pawn_attacks(square: Square, color: Color) -> Bitboard {
160148
unsafe {
161149
match color {

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod nnue;
1313
mod numa;
1414
mod parameters;
1515
mod search;
16+
mod setwise;
1617
mod stack;
1718
mod thread;
1819
mod threadpool;

src/movepick.rs

Lines changed: 18 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::{
2-
lookup::{bishop_attacks, king_attacks, knight_attacks, pawn_attacks_setwise, rook_attacks},
2+
lookup::king_attacks,
33
search::NodeType,
4+
setwise::{bishop_attacks_setwise, knight_attacks_setwise, pawn_attacks_setwise, rook_attacks_setwise},
45
thread::ThreadData,
56
types::{ArrayVec, Bitboard, MAX_MOVES, Move, MoveEntry, MoveList, PieceType},
67
};
@@ -170,6 +171,7 @@ impl MovePicker {
170171
fn score_quiet(&mut self, td: &ThreadData, ply: isize) {
171172
let threats = td.board.all_threats();
172173
let side = td.board.side_to_move();
174+
let occupancies = td.board.occupancies();
173175

174176
let threatened = {
175177
let pawn_threats = td.board.piece_threats(PieceType::Pawn);
@@ -183,40 +185,24 @@ impl MovePicker {
183185

184186
// safe squares where we can attack an opponent piece
185187
let offense = {
186-
let mut n = Bitboard(0);
187-
let mut b = Bitboard(0);
188-
let mut q = Bitboard(0);
189-
let pawn_offense = pawn_attacks_setwise(td.board.colors(!side), !side) & !threats;
190-
191-
for square in td.board.colored_pieces(!side, PieceType::Bishop) & !threats {
192-
n |= knight_attacks(square);
193-
q |= rook_attacks(square, td.board.occupancies());
194-
}
195-
196-
for square in td.board.colored_pieces(!side, PieceType::Rook) {
197-
n |= knight_attacks(square);
198-
b |= bishop_attacks(square, td.board.occupancies());
199-
200-
if !threats.contains(square) {
201-
q |= bishop_attacks(square, td.board.occupancies());
202-
}
203-
}
204-
for square in td.board.colored_pieces(!side, PieceType::Queen) {
205-
n |= knight_attacks(square);
206-
}
207-
208-
[pawn_offense, n & !threats, b & !threats, Bitboard(0), q & !threats, Bitboard(0)]
188+
let knight_vulnerable = (td.board.colored_pieces(!side, PieceType::Bishop) & !threats)
189+
| td.board.colored_pieces(!side, PieceType::Rook)
190+
| td.board.colored_pieces(!side, PieceType::Queen);
191+
let bishop_vulnerable = td.board.colored_pieces(!side, PieceType::Rook);
192+
let queen_orth_vulnerable = td.board.colored_pieces(!side, PieceType::Bishop) & !threats;
193+
let queen_diag_vulnerable = td.board.colored_pieces(!side, PieceType::Rook) & !threats;
194+
195+
let p = pawn_attacks_setwise(td.board.colors(!side), !side);
196+
let n = knight_attacks_setwise(knight_vulnerable);
197+
let b = bishop_attacks_setwise(bishop_vulnerable, occupancies);
198+
let q = rook_attacks_setwise(queen_orth_vulnerable, occupancies)
199+
| bishop_attacks_setwise(queen_diag_vulnerable, occupancies);
200+
201+
[p & !threats, n & !threats, b & !threats, Bitboard(0), q & !threats, Bitboard(0)]
209202
};
210203

211204
// King ring diag attacks and ortho attacks
212-
let king_ring_ortho = {
213-
let mut king_ring_ortho = Bitboard(0);
214-
for square in king_attacks(td.board.king_square(!side)) {
215-
king_ring_ortho |= rook_attacks(square, td.board.occupancies());
216-
}
217-
king_ring_ortho &= !threats;
218-
king_ring_ortho
219-
};
205+
let king_ring_ortho = rook_attacks_setwise(king_attacks(td.board.king_square(!side)), occupancies) & !threats;
220206

221207
// don't move king wall pawns
222208
let wall_pawns = if Bitboard::HOME_ROWS[side].contains(td.board.king_square(side)) {

src/setwise.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use crate::types::{Bitboard, Color, File, Rank};
2+
3+
const A: Bitboard = Bitboard::file(File::A);
4+
const B: Bitboard = Bitboard::file(File::B);
5+
const G: Bitboard = Bitboard::file(File::G);
6+
const H: Bitboard = Bitboard::file(File::H);
7+
const R1: Bitboard = Bitboard::rank(Rank::R1);
8+
const R2: Bitboard = Bitboard::rank(Rank::R2);
9+
const R7: Bitboard = Bitboard::rank(Rank::R7);
10+
const R8: Bitboard = Bitboard::rank(Rank::R8);
11+
12+
pub fn pawn_attacks_setwise(bb: Bitboard, color: Color) -> Bitboard {
13+
let (up_right, up_left) = match color {
14+
Color::White => (9, 7),
15+
Color::Black => (-7, -9),
16+
};
17+
18+
let right_attacks = (bb & !Bitboard::file(File::H)).shift(up_right);
19+
let left_attacks = (bb & !Bitboard::file(File::A)).shift(up_left);
20+
21+
right_attacks | left_attacks
22+
}
23+
24+
#[cfg(not(target_feature = "avx2"))]
25+
#[inline]
26+
pub fn knight_attacks_setwise(bb: Bitboard) -> Bitboard {
27+
(bb & !(A | B | R8)).shift(6)
28+
| (bb & !(A | R7 | R8)).shift(15)
29+
| (bb & !(H | R7 | R8)).shift(17)
30+
| (bb & !(G | H | R8)).shift(10)
31+
| (bb & !(G | H | R1)).shift(-6)
32+
| (bb & !(H | R1 | R2)).shift(-15)
33+
| (bb & !(A | R1 | R2)).shift(-17)
34+
| (bb & !(A | B | R1)).shift(-10)
35+
}
36+
37+
#[cfg(target_feature = "avx2")]
38+
#[inline]
39+
pub fn knight_attacks_setwise(bb: Bitboard) -> Bitboard {
40+
use core::arch::x86_64::*;
41+
42+
unsafe {
43+
let mask_a = _mm256_set_epi64x(
44+
!(A | B | R8).0 as i64,
45+
!(A | R7 | R8).0 as i64,
46+
!(H | R7 | R8).0 as i64,
47+
!(G | H | R8).0 as i64,
48+
);
49+
let mask_b = _mm256_set_epi64x(
50+
!(G | H | R1).0 as i64,
51+
!(H | R1 | R2).0 as i64,
52+
!(A | R1 | R2).0 as i64,
53+
!(A | B | R1).0 as i64,
54+
);
55+
56+
let bb = _mm256_set1_epi64x(bb.0 as i64);
57+
let a = _mm256_and_si256(bb, mask_a);
58+
let b = _mm256_and_si256(bb, mask_b);
59+
let a = _mm256_sllv_epi64(a, _mm256_set_epi64x(6, 15, 17, 10));
60+
let b = _mm256_srlv_epi64(b, _mm256_set_epi64x(6, 15, 17, 10));
61+
fold_to_bitboard(_mm256_or_si256(a, b))
62+
}
63+
}
64+
65+
#[cfg(not(target_feature = "avx512f"))]
66+
#[inline]
67+
pub fn bishop_attacks_setwise(bb: Bitboard, occupancies: Bitboard) -> Bitboard {
68+
use crate::lookup::bishop_attacks;
69+
70+
let mut result = Bitboard(0);
71+
for square in bb {
72+
result |= bishop_attacks(square, occupancies);
73+
}
74+
result
75+
}
76+
77+
#[cfg(target_feature = "avx512f")]
78+
#[inline]
79+
pub fn bishop_attacks_setwise(bb: Bitboard, occupancies: Bitboard) -> Bitboard {
80+
use std::arch::x86_64::*;
81+
82+
unsafe {
83+
let attackers = _mm256_set1_epi64x(bb.0 as i64);
84+
let rotates1 = _mm256_set_epi64x(-9, -7, 7, 9);
85+
let rotates2 = _mm256_add_epi64(rotates1, rotates1);
86+
let rotates4 = _mm256_add_epi64(rotates2, rotates2);
87+
88+
let mask = _mm256_set_epi64x(!(R8 | H).0 as i64, !(R8 | A).0 as i64, !(R1 | H).0 as i64, !(R1 | A).0 as i64);
89+
90+
let generate = attackers;
91+
let propagate = _mm256_and_si256(_mm256_set1_epi64x(!occupancies.0 as i64), mask);
92+
let generate = _mm256_or_si256(generate, _mm256_and_si256(propagate, _mm256_rolv_epi64(generate, rotates1)));
93+
let propagate = _mm256_and_si256(propagate, _mm256_rolv_epi64(propagate, rotates1));
94+
let generate = _mm256_or_si256(generate, _mm256_and_si256(propagate, _mm256_rolv_epi64(generate, rotates2)));
95+
let propagate = _mm256_and_si256(propagate, _mm256_rolv_epi64(propagate, rotates2));
96+
let generate = _mm256_or_si256(generate, _mm256_and_si256(propagate, _mm256_rolv_epi64(generate, rotates4)));
97+
let attacks = _mm256_and_si256(_mm256_rolv_epi64(generate, rotates1), mask);
98+
99+
fold_to_bitboard(attacks)
100+
}
101+
}
102+
103+
#[cfg(not(target_feature = "avx512f"))]
104+
#[inline]
105+
pub fn rook_attacks_setwise(bb: Bitboard, occupancies: Bitboard) -> Bitboard {
106+
use crate::lookup::rook_attacks;
107+
108+
let mut result = Bitboard(0);
109+
for square in bb {
110+
result |= rook_attacks(square, occupancies);
111+
}
112+
result
113+
}
114+
115+
#[cfg(target_feature = "avx512f")]
116+
#[inline]
117+
pub fn rook_attacks_setwise(bb: Bitboard, occupancies: Bitboard) -> Bitboard {
118+
use std::arch::x86_64::*;
119+
120+
unsafe {
121+
let attackers = _mm256_set1_epi64x(bb.0 as i64);
122+
let rotates1 = _mm256_set_epi64x(-8, -1, 1, 8);
123+
let rotates2 = _mm256_add_epi64(rotates1, rotates1);
124+
let rotates4 = _mm256_add_epi64(rotates2, rotates2);
125+
126+
let mask = _mm256_set_epi64x(!R8.0 as i64, !H.0 as i64, !A.0 as i64, !R1.0 as i64);
127+
128+
let generate = attackers;
129+
let propagate = _mm256_and_si256(_mm256_set1_epi64x(!occupancies.0 as i64), mask);
130+
let generate = _mm256_or_si256(generate, _mm256_and_si256(propagate, _mm256_rolv_epi64(generate, rotates1)));
131+
let propagate = _mm256_and_si256(propagate, _mm256_rolv_epi64(propagate, rotates1));
132+
let generate = _mm256_or_si256(generate, _mm256_and_si256(propagate, _mm256_rolv_epi64(generate, rotates2)));
133+
let propagate = _mm256_and_si256(propagate, _mm256_rolv_epi64(propagate, rotates2));
134+
let generate = _mm256_or_si256(generate, _mm256_and_si256(propagate, _mm256_rolv_epi64(generate, rotates4)));
135+
let attacks = _mm256_and_si256(_mm256_rolv_epi64(generate, rotates1), mask);
136+
137+
fold_to_bitboard(attacks)
138+
}
139+
}
140+
141+
#[cfg(target_feature = "avx2")]
142+
#[inline]
143+
unsafe fn fold_to_bitboard(vector: core::arch::x86_64::__m256i) -> Bitboard {
144+
use core::arch::x86_64::*;
145+
146+
let vector = _mm_or_si128(_mm256_castsi256_si128(vector), _mm256_extracti128_si256::<1>(vector));
147+
let result = _mm_extract_epi64::<0>(vector) | _mm_extract_epi64::<1>(vector);
148+
Bitboard(result as u64)
149+
}

0 commit comments

Comments
 (0)