-
Notifications
You must be signed in to change notification settings - Fork 57
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
Hotfix for unsoundness issue with MoveGen (55) #60
base: master
Are you sure you want to change the base?
Hotfix for unsoundness issue with MoveGen (55) #60
Conversation
This one is a bit tricky for me. This evil code-path is only possible if there are more pieces on the chess board than can exist on a chess board (or more than 2 e.p. captures possible at a time). Perhaps an assert on the number of pieces is cleaner? IE, don't use this if there are more than 16 pieces on the board. Perhaps the assertion should be done in As far as I can tell, if that evil path is hit, something has already gone horribly wrong, and a Alternatively, there may be a cheap way to handle any number of pieces that I haven't thought of yet. |
I choose not to panic because I wanted to do a minimal change and panicking also increases the function size.
I also think that a final fix requires a few design choices on how to handle "custom" boards. I suggest merging this change for now because this is a potential security risk for instance if you have a server that allows loading custom boards. |
I believe the following would resolve the unsoundness as well, but again I'd need to check more deeply: If all the following is done in
if self.color_combined(Color::White) > 16 || self.color_combined(Color::Black) > 16 { // Edit: Changed to > 16, lol
return false;
}
This upholds the same safety guarantees (as far as I can tell), but reduces the merge request down to 6-7 lines. It also completely removes the performance impact where it matters most. |
So your plan is to make it an safety relevant invariant that an object of type What are the invariants that you plan to guarantee? I would only choose a subset of the extended "sane" to keep upholding it easier an allow invalid
Maybe we could factor out Note: if self.combined() & opp == EMPTY {
None
} else {
let result: u8 = self.pieces.iter().enumerate().skip(1)
.map(|(idx, bb)| (idx as u8) * ((opp & bb != EMPTY) as u8)).sum();
Some(match result {
0 => Piece::Pawn,
1 => Piece::Knight,
2 => Piece::Bishop,
3 => Piece::Rook,
4 => Piece::Queen,
5 => Piece::King,
_ => unreachable!()
})
} Or this completely branchless version let result_plus_one: u8 = self.pieces.iter().enumerate()
.map(|(idx, bb)| (idx as u8 + 1) * ((opp & bb != EMPTY) as u8)).sum();
Some(match result_plus_one.wrapping_sub(1) {
0 => Piece::Pawn,
1 => Piece::Knight,
2 => Piece::Bishop,
3 => Piece::Rook,
4 => Piece::Queen,
5 => Piece::King,
_ => return None
}) |
|
I am currently hacking together a solution that works via a destinct type which represents the "physical" board and handles to pieces and squares to avoid duplicate checks.
Definitely but this is IMO a completely different category. If one has a safety relevant invariant there should go more effort into proving it.
Thanks. I expect that the benchmark result depends heavily on the work load, the CPU architecture and the compiler (version). |
Yeah, benchmarks are very tricky. You gotta be very careful in order to get numbers. That said, I think I could probably get some numbers on that tonight. |
See #55
I solved this by ignoring moves that do not fit in the list. The performance impact of this solution should be minimal because the branch predictor should be able to guess right and it should only introduce an additional conditional jump instruction. I did not measure anything though.
A clean fix would either require a
SmallVec
(probably best with a specialized happy path that does the same as above) or aExpectedBoard
orValidBoard
like type that upholds certain invariants.