use super::MAX_HAYSTACK_LEN;
use crate::smith_waterman::{
SmithWaterman,
backend::{Backend, BytesVec, MaskVec, ScoreVec},
greedy::match_greedy,
};
impl<B: Backend> SmithWaterman<B> {
#[inline(always)]
pub(crate) fn score_haystack_unicode(&mut self, haystack: &[u8]) -> u16 {
if haystack.len() > MAX_HAYSTACK_LEN {
return match_greedy(
self.needle.as_bytes(),
haystack,
&self.scoring,
self.case_sensitive,
)
.map(|(score, _)| score)
.unwrap_or(0);
}
if self.needle_unicode.is_empty() {
return 0;
}
let scoring = &self.scoring;
let haystack_chunks = haystack.len().div_ceil(B::LANES) + 1;
self.haystack_chunks = haystack_chunks;
let score_matrix = &mut self.score_matrix;
let match_masks = &mut self.match_masks;
let unicode_pending_gap_open_masks = &mut self.unicode_pending_gap_open_masks;
unsafe {
let gap_extend_penalty = B::Score::splat(scoring.gap_extend_penalty);
let gap_open_penalty =
B::Score::splat(scoring.gap_open_penalty - scoring.gap_extend_penalty);
let match_score = B::Score::splat(scoring.match_score + scoring.mismatch_penalty);
let mismatch_penalty = B::Score::splat(scoring.mismatch_penalty);
let matching_case_bonus = B::Score::splat(scoring.matching_case_bonus);
let capitalization_bonus = B::Score::splat(scoring.capitalization_bonus);
let delimiter_bonus = B::Score::splat(scoring.delimiter_bonus);
let final_row_idx = self.needle_unicode.len();
let mut max_scores = B::Score::zero();
for pending_gap_open_mask in unicode_pending_gap_open_masks
.iter_mut()
.take(final_row_idx + 1)
{
*pending_gap_open_mask = B::Score::zero();
}
let mut prefix_bonus_masked = B::Score::first_lane(scoring.prefix_bonus);
let mut prev_chunk_char_is_delimiter_mask = B::Mask::zero();
let mut prev_chunk_is_lower_mask = B::Mask::zero();
let mut prev_chunk_continuation_gap_extend_penalty = B::Score::zero();
let mut prev_chunk_scalar_start_mask = B::Score::zero();
for col_idx in 1..haystack_chunks {
let chunk_start = (col_idx - 1) * B::LANES;
let haystack_byte_chunks = [
B::Bytes::load_partial(haystack.as_ptr(), chunk_start + 3, haystack.len()),
B::Bytes::load_partial(haystack.as_ptr(), chunk_start + 2, haystack.len()),
B::Bytes::load_partial(haystack.as_ptr(), chunk_start + 1, haystack.len()),
B::Bytes::load_partial(haystack.as_ptr(), chunk_start, haystack.len()),
];
let haystack_chunk = haystack_byte_chunks[3];
let (continuation_mask, scalar_start_mask) =
unicode_scalar_masks::<B>(haystack_chunk, haystack.len(), chunk_start);
let scalar_start_score_mask = B::widen_mask(scalar_start_mask);
let continuation_gap_extend_penalty =
B::widen_mask(continuation_mask).and(gap_extend_penalty);
let is_upper_mask = haystack_chunk
.lt(B::Bytes::splat(b'Z' + 1))
.and(haystack_chunk.gt(B::Bytes::splat(b'A' - 1)));
let is_lower_mask = haystack_chunk
.lt(B::Bytes::splat(b'z' + 1))
.and(haystack_chunk.gt(B::Bytes::splat(b'a' - 1)));
let is_letter_mask = is_upper_mask.or(is_lower_mask);
let capitalization_mask = B::widen_mask(
is_upper_mask.and(is_lower_mask.shift_right_padded_1(prev_chunk_is_lower_mask)),
);
let capitalization_bonus_masked = capitalization_mask.and(capitalization_bonus);
prev_chunk_is_lower_mask = is_lower_mask;
let is_digit_mask = haystack_chunk
.gt(B::Bytes::splat(b'0' - 1))
.and(haystack_chunk.lt(B::Bytes::splat(b'9' + 1)));
let char_is_delimiter_mask = is_letter_mask
.or(is_digit_mask)
.or(haystack_chunk.gt(B::Bytes::splat(127)))
.not();
let prev_char_is_delimiter_mask =
char_is_delimiter_mask.shift_right_padded_1(prev_chunk_char_is_delimiter_mask);
let delimiter_mask =
B::widen_mask(prev_char_is_delimiter_mask.and(char_is_delimiter_mask.not()));
let delimiter_bonus_masked = delimiter_mask.and(delimiter_bonus);
prev_chunk_char_is_delimiter_mask = char_is_delimiter_mask;
let match_and_masked_bonuses = delimiter_bonus_masked
.add(capitalization_bonus_masked)
.add(prefix_bonus_masked)
.add(match_score);
prefix_bonus_masked = B::Score::zero();
let mut up_gap_mask = B::Score::zero();
let mut prev_row_scores = B::Score::zero();
let mut row_scores = B::Score::zero();
for (row_idx, needle_char) in self
.needle_unicode
.iter()
.enumerate()
.map(|(i, c)| (i + 1, c))
{
let exact_case_match_mask = unicode_char_match_mask::<B>(
haystack_byte_chunks,
scalar_start_mask,
needle_char.len,
needle_char.chars,
);
let flipped_case_match_mask = unicode_char_match_mask::<B>(
haystack_byte_chunks,
scalar_start_mask,
needle_char.len,
needle_char.flipped_chars,
);
let match_mask =
B::widen_mask(exact_case_match_mask.or(flipped_case_match_mask));
let exact_case_match_mask = B::widen_mask(exact_case_match_mask);
let diag_scores = {
let diag = prev_row_scores
.shift_right_padded::<1>(score_matrix.get(row_idx - 1, col_idx - 1));
let diag = diag.add(match_mask.and(match_and_masked_bonuses));
let diag = diag.subs(mismatch_penalty);
let diag = diag.add(exact_case_match_mask.and(matching_case_bonus));
diag.and(scalar_start_score_mask)
};
let up_scores = {
let after_extend = prev_row_scores.subs(gap_extend_penalty);
let up = after_extend.subs(up_gap_mask.and(gap_open_penalty));
up.and(scalar_start_score_mask)
};
let (next_row_scores, pending_gap_open_mask) =
B::propagate_horizontal_unicode_gaps(
diag_scores.max(up_scores),
score_matrix.get(row_idx, col_idx - 1),
match_mask,
*unicode_pending_gap_open_masks.get_unchecked(row_idx),
continuation_gap_extend_penalty,
prev_chunk_continuation_gap_extend_penalty,
scalar_start_score_mask,
prev_chunk_scalar_start_mask,
gap_open_penalty,
gap_extend_penalty,
);
score_matrix.set(row_idx, col_idx, next_row_scores);
match_masks.set(row_idx, col_idx, match_mask);
*unicode_pending_gap_open_masks.get_unchecked_mut(row_idx) =
pending_gap_open_mask;
prev_row_scores = next_row_scores;
row_scores = next_row_scores;
up_gap_mask = match_mask;
}
max_scores = max_scores.max(row_scores);
prev_chunk_continuation_gap_extend_penalty = continuation_gap_extend_penalty;
prev_chunk_scalar_start_mask = scalar_start_score_mask;
}
max_scores.horizontal_max()
}
}
}
#[inline(always)]
unsafe fn unicode_char_match_mask<B: Backend>(
haystack_chunks: [B::Bytes; 4],
scalar_start_mask: B::Mask,
char_len: usize,
chars: [u8; 4],
) -> B::Mask {
unsafe {
let mut mask = haystack_chunks[4 - char_len]
.eq(B::Bytes::splat(chars[char_len - 1]))
.and(scalar_start_mask);
if char_len > 1 && !mask.is_zero() {
for byte_idx in 0..(char_len - 1) {
let haystack_bytes = haystack_chunks[3 - byte_idx];
mask = mask.and(haystack_bytes.eq(B::Bytes::splat(chars[byte_idx])));
}
}
mask
}
}
#[inline(always)]
unsafe fn unicode_scalar_masks<B: Backend>(
current_bytes: B::Bytes,
haystack_len: usize,
start: usize,
) -> (B::Mask, B::Mask) {
unsafe {
let valid_mask = valid_haystack_lanes::<B>(haystack_len, start);
let continuation_mask = current_bytes
.gt(B::Bytes::splat(0x7f))
.and(current_bytes.lt(B::Bytes::splat(0xc0)))
.and(valid_mask);
let scalar_start_mask = continuation_mask.not().and(valid_mask);
(continuation_mask, scalar_start_mask)
}
}
#[inline(always)]
unsafe fn valid_haystack_lanes<B: Backend>(haystack_len: usize, start: usize) -> B::Mask {
unsafe {
let valid_lanes = haystack_len.saturating_sub(start).min(B::LANES);
let mut bytes = [0u8; 64];
for byte in bytes.iter_mut().take(valid_lanes) {
*byte = u8::MAX;
}
let valid_bytes = B::Bytes::load_partial(bytes.as_ptr(), 0, B::LANES);
valid_bytes.gt(B::Bytes::splat(0))
}
}