#ifndef STRINGZILLAS_SIMILARITIES_ICELAKE_HPP_
#define STRINGZILLAS_SIMILARITIES_ICELAKE_HPP_
#include "stringzillas/similarities/serial.hpp"
#include "stringzilla/find/icelake.h"
namespace ashvardanian {
namespace stringzillas {
#pragma region Ice Lake Implementation
#if SZ_USE_ICELAKE
#if defined(__clang__) && SZ_CLANG_HAS_EVEX512_
#pragma clang attribute push( \
__attribute__((target("avx,avx512f,avx512vl,avx512bw,avx512dq,avx512vbmi,bmi,bmi2,evex512"))), \
apply_to = function)
#elif defined(__clang__)
#pragma clang attribute push(__attribute__((target("avx,avx512f,avx512vl,avx512bw,avx512dq,avx512vbmi,bmi,bmi2"))), \
apply_to = function)
#elif defined(__GNUC__)
#pragma GCC push_options
#pragma GCC target("avx", "avx512f", "avx512vl", "avx512bw", "avx512dq", "avx512vbmi", "bmi", "bmi2")
#endif
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, u8_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, u8_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, u8_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_minimize_distance_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
SZ_INLINE void slice_aligned64chars( char const *first_reversed_slice, char const *second_slice, u8_t const *scores_pre_substitution, u8_t const *scores_pre_insertion, u8_t const *scores_pre_deletion, u8_t *scores_new, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_cost_vec) const noexcept {
__mmask64 match_mask;
u512_vec_t first_vec, second_vec;
u512_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u512_vec_t cost_of_substitution_vec;
u512_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
first_vec.zmm = _mm512_loadu_epi8(first_reversed_slice);
second_vec.zmm = _mm512_loadu_epi8(second_slice);
pre_substitution_vec.zmm = _mm512_loadu_epi8(scores_pre_substitution);
pre_insert_vec.zmm = _mm512_loadu_epi8(scores_pre_insertion);
pre_delete_vec.zmm = _mm512_loadu_epi8(scores_pre_deletion);
match_mask = _mm512_cmpeq_epi8_mask(first_vec.zmm, second_vec.zmm);
cost_of_substitution_vec.zmm = _mm512_mask_blend_epi8(match_mask, mismatch_cost_vec.zmm, match_cost_vec.zmm);
cost_if_substitution_vec.zmm = _mm512_add_epi8(pre_substitution_vec.zmm, cost_of_substitution_vec.zmm);
cost_if_gap_vec.zmm = _mm512_add_epi8(_mm512_min_epu8(pre_insert_vec.zmm, pre_delete_vec.zmm),
gap_cost_vec.zmm);
cell_score_vec.zmm = _mm512_min_epu8(cost_if_substitution_vec.zmm, cost_if_gap_vec.zmm);
_mm512_store_si512(scores_new, cell_score_vec.zmm);
}
SZ_INLINE void slice_upto64chars( char const *first_reversed_slice, char const *second_slice, size_t n, u8_t const *scores_pre_substitution, u8_t const *scores_pre_insertion, u8_t const *scores_pre_deletion, u8_t *scores_new, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_cost_vec) const noexcept {
__mmask64 load_mask, match_mask;
u512_vec_t first_vec, second_vec;
u512_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u512_vec_t cost_of_substitution_vec;
u512_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
load_mask = sz_u64_mask_until_(n);
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
pre_substitution_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, scores_pre_substitution);
pre_insert_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, scores_pre_insertion);
pre_delete_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, scores_pre_deletion);
match_mask = _mm512_cmpeq_epi8_mask(first_vec.zmm, second_vec.zmm);
cost_of_substitution_vec.zmm = _mm512_mask_blend_epi8(match_mask, mismatch_cost_vec.zmm, match_cost_vec.zmm);
cost_if_substitution_vec.zmm = _mm512_add_epi8(pre_substitution_vec.zmm, cost_of_substitution_vec.zmm);
cost_if_gap_vec.zmm = _mm512_add_epi8(_mm512_min_epu8(pre_insert_vec.zmm, pre_delete_vec.zmm),
gap_cost_vec.zmm);
cell_score_vec.zmm = _mm512_min_epu8(cost_if_substitution_vec.zmm, cost_if_gap_vec.zmm);
_mm512_mask_storeu_epi8(scores_new, load_mask, cell_score_vec.zmm);
}
template <typename executor_type_ = dummy_executor_t>
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, u8_t const *scores_pre_substitution, u8_t const *scores_pre_insertion, u8_t const *scores_pre_deletion, u8_t *scores_new, executor_type_ &&executor = {}) noexcept {
sz_unused_(executor);
u512_vec_t match_cost_vec, mismatch_cost_vec, gap_cost_vec;
match_cost_vec.zmm = _mm512_set1_epi8(this->substituter_.match);
mismatch_cost_vec.zmm = _mm512_set1_epi8(this->substituter_.mismatch);
gap_cost_vec.zmm = _mm512_set1_epi8(this->gap_costs_.open_or_extend);
if (length <= step_k) {
slice_upto64chars( first_reversed_slice, second_slice, length, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
this->last_score_ = scores_new[0];
return;
}
head_body_tail_t hbt = head_body_tail<step_k>(scores_new, length);
if (hbt.head)
slice_upto64chars( first_reversed_slice, second_slice, hbt.head, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
first_reversed_slice += hbt.head, second_slice += hbt.head, scores_pre_substitution += hbt.head,
scores_pre_insertion += hbt.head, scores_pre_deletion += hbt.head, scores_new += hbt.head;
for (size_t progress = 0; progress < hbt.body; progress += step_k, first_reversed_slice += step_k, second_slice += step_k, scores_pre_substitution += step_k,
scores_pre_insertion += step_k, scores_pre_deletion += step_k, scores_new += step_k)
slice_aligned64chars( first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
if (hbt.tail)
slice_upto64chars( first_reversed_slice, second_slice, hbt.tail, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<rune_t const *, rune_t const *, u8_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<rune_t const *, rune_t const *, u8_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<rune_t const *, rune_t const *, u8_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_minimize_distance_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 16;
SZ_INLINE void slice_aligned16chars( rune_t const *first_reversed_slice, rune_t const *second_slice, u8_t const *scores_pre_substitution, u8_t const *scores_pre_insertion, u8_t const *scores_pre_deletion, u8_t *scores_new, u128_vec_t match_cost_vec, u128_vec_t mismatch_cost_vec, u128_vec_t gap_cost_vec) const noexcept {
__mmask16 match_mask;
u512_vec_t first_vec, second_vec;
u128_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u128_vec_t cost_of_substitution_vec;
u128_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
first_vec.zmm = _mm512_loadu_epi32(first_reversed_slice);
second_vec.zmm = _mm512_loadu_epi32(second_slice);
pre_substitution_vec.xmm = _mm_lddqu_si128((__m128i const *)(scores_pre_substitution));
pre_insert_vec.xmm = _mm_lddqu_si128((__m128i const *)(scores_pre_insertion));
pre_delete_vec.xmm = _mm_lddqu_si128((__m128i const *)(scores_pre_deletion));
match_mask = _mm512_cmpeq_epi32_mask(first_vec.zmm, second_vec.zmm);
cost_of_substitution_vec.xmm = _mm_mask_blend_epi8(match_mask, mismatch_cost_vec.xmm, match_cost_vec.xmm);
cost_if_substitution_vec.xmm = _mm_add_epi8(pre_substitution_vec.xmm, cost_of_substitution_vec.xmm);
cost_if_gap_vec.xmm = _mm_add_epi8(_mm_min_epu8(pre_insert_vec.xmm, pre_delete_vec.xmm), gap_cost_vec.xmm);
cell_score_vec.xmm = _mm_min_epu8(cost_if_substitution_vec.xmm, cost_if_gap_vec.xmm);
_mm_store_si128((__m128i *)scores_new, cell_score_vec.xmm);
}
SZ_INLINE void slice_upto16chars( rune_t const *first_reversed_slice, rune_t const *second_slice, size_t n, u8_t const *scores_pre_substitution, u8_t const *scores_pre_insertion, u8_t const *scores_pre_deletion, u8_t *scores_new, u128_vec_t match_cost_vec, u128_vec_t mismatch_cost_vec, u128_vec_t gap_cost_vec) const noexcept {
__mmask16 load_mask, match_mask;
u512_vec_t first_vec, second_vec;
u128_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u128_vec_t cost_of_substitution_vec;
u128_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
load_mask = sz_u16_mask_until_(n);
first_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, second_slice);
pre_substitution_vec.xmm = _mm_maskz_loadu_epi8(load_mask, scores_pre_substitution);
pre_insert_vec.xmm = _mm_maskz_loadu_epi8(load_mask, scores_pre_insertion);
pre_delete_vec.xmm = _mm_maskz_loadu_epi8(load_mask, scores_pre_deletion);
match_mask = _mm512_cmpeq_epi32_mask(first_vec.zmm, second_vec.zmm);
cost_of_substitution_vec.xmm = _mm_mask_blend_epi8(match_mask, mismatch_cost_vec.xmm, match_cost_vec.xmm);
cost_if_substitution_vec.xmm = _mm_add_epi8(pre_substitution_vec.xmm, cost_of_substitution_vec.xmm);
cost_if_gap_vec.xmm = _mm_add_epi8(_mm_min_epu8(pre_insert_vec.xmm, pre_delete_vec.xmm), gap_cost_vec.xmm);
cell_score_vec.xmm = _mm_min_epu8(cost_if_substitution_vec.xmm, cost_if_gap_vec.xmm);
_mm_mask_storeu_epi8(scores_new, load_mask, cell_score_vec.xmm);
}
template <typename executor_type_ = dummy_executor_t>
void operator()( rune_t const *first_reversed_slice, rune_t const *second_slice, size_t const length, u8_t const *scores_pre_substitution, u8_t const *scores_pre_insertion, u8_t const *scores_pre_deletion, u8_t *scores_new, executor_type_ &&executor = {}) noexcept {
sz_unused_(executor);
u128_vec_t match_cost_vec, mismatch_cost_vec, gap_cost_vec;
match_cost_vec.xmm = _mm_set1_epi8(this->substituter_.match);
mismatch_cost_vec.xmm = _mm_set1_epi8(this->substituter_.mismatch);
gap_cost_vec.xmm = _mm_set1_epi8(this->gap_costs_.open_or_extend);
if (length <= step_k) {
slice_upto16chars( first_reversed_slice, second_slice, length, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
this->last_score_ = scores_new[0];
return;
}
head_body_tail_t hbt = head_body_tail<step_k>(scores_new, length);
if (hbt.head)
slice_upto16chars( first_reversed_slice, second_slice, hbt.head, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
first_reversed_slice += hbt.head, second_slice += hbt.head, scores_pre_substitution += hbt.head,
scores_pre_insertion += hbt.head, scores_pre_deletion += hbt.head, scores_new += hbt.head;
for (size_t progress = 0; progress < hbt.body; progress += step_k, first_reversed_slice += step_k, second_slice += step_k, scores_pre_substitution += step_k,
scores_pre_insertion += step_k, scores_pre_deletion += step_k, scores_new += step_k)
slice_aligned16chars( first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
if (hbt.tail)
slice_upto16chars( first_reversed_slice, second_slice, hbt.tail, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, u16_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, u16_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, u16_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_minimize_distance_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 32;
SZ_INLINE void slice_aligned32chars( char const *first_reversed_slice, char const *second_slice, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t *scores_new, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_cost_vec) const noexcept {
__mmask32 match_mask;
u256_vec_t first_vec, second_vec;
u512_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u512_vec_t cost_of_substitution_vec;
u512_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
first_vec.ymm = _mm256_loadu_epi8(first_reversed_slice);
second_vec.ymm = _mm256_loadu_epi8(second_slice);
pre_substitution_vec.zmm = _mm512_loadu_epi16(scores_pre_substitution);
pre_insert_vec.zmm = _mm512_loadu_epi16(scores_pre_insertion);
pre_delete_vec.zmm = _mm512_loadu_epi16(scores_pre_deletion);
match_mask = _mm256_cmpeq_epi8_mask(first_vec.ymm, second_vec.ymm);
cost_of_substitution_vec.zmm = _mm512_mask_blend_epi16(match_mask, mismatch_cost_vec.zmm, match_cost_vec.zmm);
cost_if_substitution_vec.zmm = _mm512_add_epi16(pre_substitution_vec.zmm, cost_of_substitution_vec.zmm);
cost_if_gap_vec.zmm = _mm512_add_epi16(_mm512_min_epu16(pre_insert_vec.zmm, pre_delete_vec.zmm),
gap_cost_vec.zmm);
cell_score_vec.zmm = _mm512_min_epu16(cost_if_substitution_vec.zmm, cost_if_gap_vec.zmm);
_mm512_store_si512(scores_new, cell_score_vec.zmm);
}
SZ_INLINE void slice_upto32chars( char const *first_reversed_slice, char const *second_slice, size_t n, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t *scores_new, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_cost_vec) const noexcept {
__mmask32 load_mask, match_mask;
u256_vec_t first_vec, second_vec;
u512_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u512_vec_t cost_of_substitution_vec;
u512_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
load_mask = sz_u32_mask_until_(n);
first_vec.ymm = _mm256_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.ymm = _mm256_maskz_loadu_epi8(load_mask, second_slice);
pre_substitution_vec.zmm = _mm512_maskz_loadu_epi16(load_mask, scores_pre_substitution);
pre_insert_vec.zmm = _mm512_maskz_loadu_epi16(load_mask, scores_pre_insertion);
pre_delete_vec.zmm = _mm512_maskz_loadu_epi16(load_mask, scores_pre_deletion);
match_mask = _mm256_cmpeq_epi8_mask(first_vec.ymm, second_vec.ymm);
cost_of_substitution_vec.zmm = _mm512_mask_blend_epi16(match_mask, mismatch_cost_vec.zmm, match_cost_vec.zmm);
cost_if_substitution_vec.zmm = _mm512_add_epi16(pre_substitution_vec.zmm, cost_of_substitution_vec.zmm);
cost_if_gap_vec.zmm = _mm512_add_epi16(_mm512_min_epu16(pre_insert_vec.zmm, pre_delete_vec.zmm),
gap_cost_vec.zmm);
cell_score_vec.zmm = _mm512_min_epu16(cost_if_substitution_vec.zmm, cost_if_gap_vec.zmm);
_mm512_mask_storeu_epi16(scores_new, load_mask, cell_score_vec.zmm);
}
SZ_NOINLINE void score_slice_trampoline_( char const *first_reversed_slice, char const *second_slice, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t *scores_new, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_cost_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_aligned32chars( first_reversed_slice + progress, second_slice + progress, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_new + progress, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t *scores_new, executor_type_ &&executor = {}) noexcept {
u512_vec_t match_cost_vec, mismatch_cost_vec, gap_cost_vec;
match_cost_vec.zmm = _mm512_set1_epi16(this->substituter_.match);
mismatch_cost_vec.zmm = _mm512_set1_epi16(this->substituter_.mismatch);
gap_cost_vec.zmm = _mm512_set1_epi16(this->gap_costs_.open_or_extend);
if (length <= step_k) {
slice_upto32chars( first_reversed_slice, second_slice, length, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
this->last_score_ = scores_new[0];
return;
}
head_body_tail_t hbt = head_body_tail<step_k>(scores_new, length);
if (hbt.head)
slice_upto32chars( first_reversed_slice, second_slice, hbt.head, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
first_reversed_slice += hbt.head, second_slice += hbt.head, scores_pre_substitution += hbt.head,
scores_pre_insertion += hbt.head, scores_pre_deletion += hbt.head, scores_new += hbt.head;
if (hbt.tail)
slice_upto32chars( first_reversed_slice + hbt.body, second_slice + hbt.body, hbt.tail, scores_pre_substitution + hbt.body, scores_pre_insertion + hbt.body, scores_pre_deletion + hbt.body, scores_new + hbt.body, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
size_t const body_pages = hbt.body / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion,
scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec,
from, to);
});
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<rune_t const *, rune_t const *, u16_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<rune_t const *, rune_t const *, u16_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<rune_t const *, rune_t const *, u16_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_minimize_distance_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 16;
SZ_INLINE void slice_aligned16chars( rune_t const *first_reversed_slice, rune_t const *second_slice, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t *scores_new, u256_vec_t match_cost_vec, u256_vec_t mismatch_cost_vec, u256_vec_t gap_cost_vec) const noexcept {
__mmask16 match_mask;
u512_vec_t first_vec, second_vec;
u256_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u256_vec_t cost_of_substitution_vec;
u256_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
first_vec.zmm = _mm512_loadu_epi32(first_reversed_slice);
second_vec.zmm = _mm512_loadu_epi32(second_slice);
pre_substitution_vec.ymm = _mm256_loadu_epi16(scores_pre_substitution);
pre_insert_vec.ymm = _mm256_loadu_epi16(scores_pre_insertion);
pre_delete_vec.ymm = _mm256_loadu_epi16(scores_pre_deletion);
match_mask = _mm512_cmpeq_epi32_mask(first_vec.zmm, second_vec.zmm);
cost_of_substitution_vec.ymm = _mm256_mask_blend_epi16(match_mask, mismatch_cost_vec.ymm, match_cost_vec.ymm);
cost_if_substitution_vec.ymm = _mm256_add_epi16(pre_substitution_vec.ymm, cost_of_substitution_vec.ymm);
cost_if_gap_vec.ymm = _mm256_add_epi16(_mm256_min_epu16(pre_insert_vec.ymm, pre_delete_vec.ymm),
gap_cost_vec.ymm);
cell_score_vec.ymm = _mm256_min_epu16(cost_if_substitution_vec.ymm, cost_if_gap_vec.ymm);
_mm256_store_si256((__m256i *)scores_new, cell_score_vec.ymm);
}
SZ_INLINE void slice_upto16chars( rune_t const *first_reversed_slice, rune_t const *second_slice, size_t n, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t *scores_new, u256_vec_t match_cost_vec, u256_vec_t mismatch_cost_vec, u256_vec_t gap_cost_vec) const noexcept {
__mmask16 load_mask, match_mask;
u512_vec_t first_vec, second_vec;
u256_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u256_vec_t cost_of_substitution_vec;
u256_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
load_mask = sz_u16_mask_until_(n);
first_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, second_slice);
pre_substitution_vec.ymm = _mm256_maskz_loadu_epi16(load_mask, scores_pre_substitution);
pre_insert_vec.ymm = _mm256_maskz_loadu_epi16(load_mask, scores_pre_insertion);
pre_delete_vec.ymm = _mm256_maskz_loadu_epi16(load_mask, scores_pre_deletion);
match_mask = _mm512_cmpeq_epi32_mask(first_vec.zmm, second_vec.zmm);
cost_of_substitution_vec.ymm = _mm256_mask_blend_epi16(match_mask, mismatch_cost_vec.ymm, match_cost_vec.ymm);
cost_if_substitution_vec.ymm = _mm256_add_epi16(pre_substitution_vec.ymm, cost_of_substitution_vec.ymm);
cost_if_gap_vec.ymm = _mm256_add_epi16(_mm256_min_epu16(pre_insert_vec.ymm, pre_delete_vec.ymm),
gap_cost_vec.ymm);
cell_score_vec.ymm = _mm256_min_epu16(cost_if_substitution_vec.ymm, cost_if_gap_vec.ymm);
_mm256_mask_storeu_epi16(scores_new, load_mask, cell_score_vec.ymm);
}
SZ_NOINLINE void score_slice_trampoline_( rune_t const *first_reversed_slice, rune_t const *second_slice, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t *scores_new, u256_vec_t match_cost_vec, u256_vec_t mismatch_cost_vec, u256_vec_t gap_cost_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_aligned16chars( first_reversed_slice + progress, second_slice + progress, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_new + progress, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( rune_t const *first_reversed_slice, rune_t const *second_slice, size_t const length, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t *scores_new, executor_type_ &&executor = {}) noexcept {
u256_vec_t match_cost_vec, mismatch_cost_vec, gap_cost_vec;
match_cost_vec.ymm = _mm256_set1_epi16(this->substituter_.match);
mismatch_cost_vec.ymm = _mm256_set1_epi16(this->substituter_.mismatch);
gap_cost_vec.ymm = _mm256_set1_epi16(this->gap_costs_.open_or_extend);
if (length <= step_k) {
slice_upto16chars( first_reversed_slice, second_slice, length, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
this->last_score_ = scores_new[0];
return;
}
head_body_tail_t hbt = head_body_tail<step_k>(scores_new, length);
if (hbt.head)
slice_upto16chars( first_reversed_slice, second_slice, hbt.head, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
first_reversed_slice += hbt.head, second_slice += hbt.head, scores_pre_substitution += hbt.head,
scores_pre_insertion += hbt.head, scores_pre_deletion += hbt.head, scores_new += hbt.head;
if (hbt.tail)
slice_upto16chars( first_reversed_slice + hbt.body, second_slice + hbt.body, hbt.tail, scores_pre_substitution + hbt.body, scores_pre_insertion + hbt.body, scores_pre_deletion + hbt.body, scores_new + hbt.body, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
size_t const body_pages = hbt.body / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion,
scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec,
from, to);
});
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, u32_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, u32_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, u32_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_minimize_distance_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 16;
SZ_INLINE void slice_aligned16chars( char const *first_reversed_slice, char const *second_slice, u32_t const *scores_pre_substitution, u32_t const *scores_pre_insertion, u32_t const *scores_pre_deletion, u32_t *scores_new, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_cost_vec) const noexcept {
__mmask16 match_mask;
u128_vec_t first_vec, second_vec;
u512_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u512_vec_t cost_of_substitution_vec;
u512_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
first_vec.xmm = _mm_lddqu_si128((__m128i const *)first_reversed_slice);
second_vec.xmm = _mm_lddqu_si128((__m128i const *)second_slice);
pre_substitution_vec.zmm = _mm512_loadu_epi32(scores_pre_substitution);
pre_insert_vec.zmm = _mm512_loadu_epi32(scores_pre_insertion);
pre_delete_vec.zmm = _mm512_loadu_epi32(scores_pre_deletion);
match_mask = _mm_cmpeq_epi8_mask(first_vec.xmm, second_vec.xmm);
cost_of_substitution_vec.zmm = _mm512_mask_blend_epi32(match_mask, mismatch_cost_vec.zmm, match_cost_vec.zmm);
cost_if_substitution_vec.zmm = _mm512_add_epi32(pre_substitution_vec.zmm, cost_of_substitution_vec.zmm);
cost_if_gap_vec.zmm = _mm512_add_epi32(_mm512_min_epu32(pre_insert_vec.zmm, pre_delete_vec.zmm),
gap_cost_vec.zmm);
cell_score_vec.zmm = _mm512_min_epu32(cost_if_substitution_vec.zmm, cost_if_gap_vec.zmm);
_mm512_store_si512((__m512i *)scores_new, cell_score_vec.zmm);
}
SZ_INLINE void slice_upto16chars( char const *first_reversed_slice, char const *second_slice, size_t n, u32_t const *scores_pre_substitution, u32_t const *scores_pre_insertion, u32_t const *scores_pre_deletion, u32_t *scores_new, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_cost_vec) const noexcept {
__mmask16 load_mask, match_mask;
u128_vec_t first_vec, second_vec;
u512_vec_t pre_substitution_vec, pre_insert_vec, pre_delete_vec;
u512_vec_t cost_of_substitution_vec;
u512_vec_t cost_if_substitution_vec, cost_if_gap_vec, cell_score_vec;
load_mask = sz_u16_mask_until_(n);
first_vec.xmm = _mm_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.xmm = _mm_maskz_loadu_epi8(load_mask, second_slice);
pre_substitution_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, scores_pre_substitution);
pre_insert_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, scores_pre_insertion);
pre_delete_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, scores_pre_deletion);
match_mask = _mm_cmpeq_epi8_mask(first_vec.xmm, second_vec.xmm);
cost_of_substitution_vec.zmm = _mm512_mask_blend_epi32(match_mask, mismatch_cost_vec.zmm, match_cost_vec.zmm);
cost_if_substitution_vec.zmm = _mm512_add_epi32(pre_substitution_vec.zmm, cost_of_substitution_vec.zmm);
cost_if_gap_vec.zmm = _mm512_add_epi32(_mm512_min_epu32(pre_insert_vec.zmm, pre_delete_vec.zmm),
gap_cost_vec.zmm);
cell_score_vec.zmm = _mm512_min_epu32(cost_if_substitution_vec.zmm, cost_if_gap_vec.zmm);
_mm512_mask_storeu_epi32(scores_new, load_mask, cell_score_vec.zmm);
}
SZ_NOINLINE void score_slice_trampoline_( char const *first_reversed_slice, char const *second_slice, u32_t const *scores_pre_substitution, u32_t const *scores_pre_insertion, u32_t const *scores_pre_deletion, u32_t *scores_new, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_cost_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_aligned16chars( first_reversed_slice + progress, second_slice + progress, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_new + progress, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, u32_t const *scores_pre_substitution, u32_t const *scores_pre_insertion, u32_t const *scores_pre_deletion, u32_t *scores_new, executor_type_ &&executor = {}) noexcept {
u512_vec_t match_cost_vec, mismatch_cost_vec, gap_cost_vec;
match_cost_vec.zmm = _mm512_set1_epi32(this->substituter_.match);
mismatch_cost_vec.zmm = _mm512_set1_epi32(this->substituter_.mismatch);
gap_cost_vec.zmm = _mm512_set1_epi32(this->gap_costs_.open_or_extend);
if (length <= step_k) {
slice_upto16chars( first_reversed_slice, second_slice, length, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
this->last_score_ = scores_new[0];
return;
}
head_body_tail_t hbt = head_body_tail<step_k>(scores_new, length);
if (hbt.head)
slice_upto16chars( first_reversed_slice, second_slice, hbt.head, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
first_reversed_slice += hbt.head, second_slice += hbt.head, scores_pre_substitution += hbt.head,
scores_pre_insertion += hbt.head, scores_pre_deletion += hbt.head, scores_new += hbt.head;
if (hbt.tail)
slice_upto16chars( first_reversed_slice + hbt.body, second_slice + hbt.body, hbt.tail, scores_pre_substitution + hbt.body, scores_pre_insertion + hbt.body, scores_pre_deletion + hbt.body, scores_new + hbt.body, match_cost_vec, mismatch_cost_vec, gap_cost_vec);
size_t const body_pages = hbt.body / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion,
scores_pre_deletion, scores_new, match_cost_vec, mismatch_cost_vec, gap_cost_vec,
from, to);
});
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, u8_t, uniform_substitution_costs_t, affine_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, u8_t, uniform_substitution_costs_t, affine_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, u8_t, uniform_substitution_costs_t, affine_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_minimize_distance_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
SZ_INLINE void slice_upto64chars( char const *first_reversed_slice, char const *second_slice, size_t n, u8_t const *scores_pre_substitution, u8_t const *scores_pre_insertion, u8_t const *scores_pre_deletion, u8_t const *scores_running_insertions, u8_t const *scores_running_deletions, u8_t *scores_new, u8_t *scores_new_insertions, u8_t *scores_new_deletions, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec) const noexcept {
__mmask64 load_mask, match_mask;
u512_vec_t first_vec, second_vec;
u512_vec_t pre_substitution_vec, pre_insert_open_vec, pre_delete_open_vec, pre_insert_expand_vec,
pre_delete_expand_vec;
u512_vec_t cost_of_substitution_vec;
u512_vec_t cost_if_substitution_vec, cost_if_insert, cost_if_delete, cell_score_vec;
load_mask = sz_u64_mask_until_(n);
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
pre_substitution_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, scores_pre_substitution);
pre_insert_open_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, scores_pre_insertion);
pre_delete_open_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, scores_pre_deletion);
pre_insert_expand_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, scores_running_insertions);
pre_delete_expand_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, scores_running_deletions);
match_mask = _mm512_cmpeq_epi8_mask(first_vec.zmm, second_vec.zmm);
cost_of_substitution_vec.zmm = _mm512_mask_blend_epi8(match_mask, mismatch_cost_vec.zmm, match_cost_vec.zmm);
cost_if_substitution_vec.zmm = _mm512_add_epi8(pre_substitution_vec.zmm, cost_of_substitution_vec.zmm);
cost_if_insert.zmm = _mm512_min_epu8(_mm512_add_epi8(pre_insert_expand_vec.zmm, gap_expand_vec.zmm),
_mm512_add_epi8(pre_insert_open_vec.zmm, gap_open_vec.zmm));
cost_if_delete.zmm = _mm512_min_epu8(_mm512_add_epi8(pre_delete_expand_vec.zmm, gap_expand_vec.zmm),
_mm512_add_epi8(pre_delete_open_vec.zmm, gap_open_vec.zmm));
cell_score_vec.zmm = _mm512_min_epu8(cost_if_substitution_vec.zmm,
_mm512_min_epu8(cost_if_insert.zmm, cost_if_delete.zmm));
_mm512_mask_storeu_epi8(scores_new, load_mask, cell_score_vec.zmm);
_mm512_mask_storeu_epi8(scores_new_insertions, load_mask, cost_if_insert.zmm);
_mm512_mask_storeu_epi8(scores_new_deletions, load_mask, cost_if_delete.zmm);
}
template <typename executor_type_ = dummy_executor_t>
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, u8_t const *scores_pre_substitution, u8_t const *scores_pre_insertion, u8_t const *scores_pre_deletion, u8_t const *scores_running_insertions, u8_t const *scores_running_deletions, u8_t *scores_new, u8_t *scores_new_insertions, u8_t *scores_new_deletions, executor_type_ &&executor = {}) noexcept {
sz_unused_(executor);
u512_vec_t match_cost_vec, mismatch_cost_vec, gap_open_vec, gap_expand_vec;
match_cost_vec.zmm = _mm512_set1_epi8(this->substituter_.match);
mismatch_cost_vec.zmm = _mm512_set1_epi8(this->substituter_.mismatch);
gap_open_vec.zmm = _mm512_set1_epi8(this->gap_costs_.open);
gap_expand_vec.zmm = _mm512_set1_epi8(this->gap_costs_.extend);
size_t progress = 0;
for (; progress + step_k <= length; progress += step_k)
slice_upto64chars( first_reversed_slice, second_slice, step_k, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, match_cost_vec, mismatch_cost_vec, gap_open_vec, gap_expand_vec);
size_t const tail = length - progress;
if (tail)
slice_upto64chars( first_reversed_slice + progress, second_slice + progress, tail, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, match_cost_vec, mismatch_cost_vec, gap_open_vec, gap_expand_vec);
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, u16_t, uniform_substitution_costs_t, affine_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, u16_t, uniform_substitution_costs_t, affine_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, u16_t, uniform_substitution_costs_t, affine_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_minimize_distance_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 32;
SZ_INLINE void slice_upto32chars( char const *first_reversed_slice, char const *second_slice, size_t n, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t const *scores_running_insertions, u16_t const *scores_running_deletions, u16_t *scores_new, u16_t *scores_new_insertions, u16_t *scores_new_deletions, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec) const noexcept {
__mmask32 load_mask, match_mask;
u256_vec_t first_vec, second_vec;
u512_vec_t pre_substitution_vec, pre_insert_open_vec, pre_delete_open_vec, pre_insert_expand_vec,
pre_delete_expand_vec;
u512_vec_t cost_of_substitution_vec;
u512_vec_t cost_if_substitution_vec, cost_if_insert, cost_if_delete, cell_score_vec;
load_mask = sz_u32_mask_until_(n);
first_vec.ymm = _mm256_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.ymm = _mm256_maskz_loadu_epi8(load_mask, second_slice);
pre_substitution_vec.zmm = _mm512_maskz_loadu_epi16(load_mask, scores_pre_substitution);
pre_insert_open_vec.zmm = _mm512_maskz_loadu_epi16(load_mask, scores_pre_insertion);
pre_delete_open_vec.zmm = _mm512_maskz_loadu_epi16(load_mask, scores_pre_deletion);
pre_insert_expand_vec.zmm = _mm512_maskz_loadu_epi16(load_mask, scores_running_insertions);
pre_delete_expand_vec.zmm = _mm512_maskz_loadu_epi16(load_mask, scores_running_deletions);
match_mask = _mm256_cmpeq_epi8_mask(first_vec.ymm, second_vec.ymm);
cost_of_substitution_vec.zmm = _mm512_mask_blend_epi16(match_mask, mismatch_cost_vec.zmm, match_cost_vec.zmm);
cost_if_substitution_vec.zmm = _mm512_add_epi16(pre_substitution_vec.zmm, cost_of_substitution_vec.zmm);
cost_if_insert.zmm = _mm512_min_epu16(_mm512_add_epi16(pre_insert_expand_vec.zmm, gap_expand_vec.zmm),
_mm512_add_epi16(pre_insert_open_vec.zmm, gap_open_vec.zmm));
cost_if_delete.zmm = _mm512_min_epu16(_mm512_add_epi16(pre_delete_expand_vec.zmm, gap_expand_vec.zmm),
_mm512_add_epi16(pre_delete_open_vec.zmm, gap_open_vec.zmm));
cell_score_vec.zmm = _mm512_min_epu16(cost_if_substitution_vec.zmm,
_mm512_min_epu16(cost_if_insert.zmm, cost_if_delete.zmm));
_mm512_mask_storeu_epi16(scores_new, load_mask, cell_score_vec.zmm);
_mm512_mask_storeu_epi16(scores_new_insertions, load_mask, cost_if_insert.zmm);
_mm512_mask_storeu_epi16(scores_new_deletions, load_mask, cost_if_delete.zmm);
}
SZ_NOINLINE void score_slice_trampoline_( char const *first_reversed_slice, char const *second_slice, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t const *scores_running_insertions, u16_t const *scores_running_deletions, u16_t *scores_new, u16_t *scores_new_insertions, u16_t *scores_new_deletions, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_upto32chars( first_reversed_slice + progress, second_slice + progress, step_k, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, match_cost_vec, mismatch_cost_vec, gap_open_vec, gap_expand_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, u16_t const *scores_pre_substitution, u16_t const *scores_pre_insertion, u16_t const *scores_pre_deletion, u16_t const *scores_running_insertions, u16_t const *scores_running_deletions, u16_t *scores_new, u16_t *scores_new_insertions, u16_t *scores_new_deletions, executor_type_ &&executor = {}) noexcept {
u512_vec_t match_cost_vec, mismatch_cost_vec, gap_open_vec, gap_expand_vec;
match_cost_vec.zmm = _mm512_set1_epi16(this->substituter_.match);
mismatch_cost_vec.zmm = _mm512_set1_epi16(this->substituter_.mismatch);
gap_open_vec.zmm = _mm512_set1_epi16(this->gap_costs_.open);
gap_expand_vec.zmm = _mm512_set1_epi16(this->gap_costs_.extend);
size_t const body_pages = length / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion,
scores_pre_deletion, scores_running_insertions, scores_running_deletions,
scores_new, scores_new_insertions, scores_new_deletions, match_cost_vec,
mismatch_cost_vec, gap_open_vec, gap_expand_vec, from, to);
});
size_t const progress = body_pages * step_k;
size_t const tail = length - progress;
if (tail)
slice_upto32chars( first_reversed_slice + progress, second_slice + progress, tail, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, match_cost_vec, mismatch_cost_vec, gap_open_vec, gap_expand_vec);
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, u32_t, uniform_substitution_costs_t, affine_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, u32_t, uniform_substitution_costs_t, affine_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, u32_t, uniform_substitution_costs_t, affine_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_minimize_distance_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 16;
SZ_INLINE void slice_upto16chars( char const *first_reversed_slice, char const *second_slice, size_t n, u32_t const *scores_pre_substitution, u32_t const *scores_pre_insertion, u32_t const *scores_pre_deletion, u32_t const *scores_running_insertions, u32_t const *scores_running_deletions, u32_t *scores_new, u32_t *scores_new_insertions, u32_t *scores_new_deletions, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec) const noexcept {
__mmask16 load_mask, match_mask;
u128_vec_t first_vec, second_vec;
u512_vec_t pre_substitution_vec, pre_insert_open_vec, pre_delete_open_vec, pre_insert_expand_vec,
pre_delete_expand_vec;
u512_vec_t cost_of_substitution_vec;
u512_vec_t cost_if_substitution_vec, cost_if_insert, cost_if_delete, cell_score_vec;
load_mask = sz_u16_mask_until_(n);
first_vec.xmm = _mm_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.xmm = _mm_maskz_loadu_epi8(load_mask, second_slice);
pre_substitution_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, scores_pre_substitution);
pre_insert_open_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, scores_pre_insertion);
pre_delete_open_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, scores_pre_deletion);
pre_insert_expand_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, scores_running_insertions);
pre_delete_expand_vec.zmm = _mm512_maskz_loadu_epi32(load_mask, scores_running_deletions);
match_mask = _mm_cmpeq_epi8_mask(first_vec.xmm, second_vec.xmm);
cost_of_substitution_vec.zmm = _mm512_mask_blend_epi32(match_mask, mismatch_cost_vec.zmm, match_cost_vec.zmm);
cost_if_substitution_vec.zmm = _mm512_add_epi32(pre_substitution_vec.zmm, cost_of_substitution_vec.zmm);
cost_if_insert.zmm = _mm512_min_epu32(_mm512_add_epi32(pre_insert_expand_vec.zmm, gap_expand_vec.zmm),
_mm512_add_epi32(pre_insert_open_vec.zmm, gap_open_vec.zmm));
cost_if_delete.zmm = _mm512_min_epu32(_mm512_add_epi32(pre_delete_expand_vec.zmm, gap_expand_vec.zmm),
_mm512_add_epi32(pre_delete_open_vec.zmm, gap_open_vec.zmm));
cell_score_vec.zmm = _mm512_min_epu32(cost_if_substitution_vec.zmm,
_mm512_min_epu32(cost_if_insert.zmm, cost_if_delete.zmm));
_mm512_mask_storeu_epi32(scores_new, load_mask, cell_score_vec.zmm);
_mm512_mask_storeu_epi32(scores_new_insertions, load_mask, cost_if_insert.zmm);
_mm512_mask_storeu_epi32(scores_new_deletions, load_mask, cost_if_delete.zmm);
}
SZ_NOINLINE void score_slice_trampoline_( char const *first_reversed_slice, char const *second_slice, u32_t const *scores_pre_substitution, u32_t const *scores_pre_insertion, u32_t const *scores_pre_deletion, u32_t const *scores_running_insertions, u32_t const *scores_running_deletions, u32_t *scores_new, u32_t *scores_new_insertions, u32_t *scores_new_deletions, u512_vec_t match_cost_vec, u512_vec_t mismatch_cost_vec, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_upto16chars( first_reversed_slice + progress, second_slice + progress, step_k, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, match_cost_vec, mismatch_cost_vec, gap_open_vec, gap_expand_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, u32_t const *scores_pre_substitution, u32_t const *scores_pre_insertion, u32_t const *scores_pre_deletion, u32_t const *scores_running_insertions, u32_t const *scores_running_deletions, u32_t *scores_new, u32_t *scores_new_insertions, u32_t *scores_new_deletions, executor_type_ &&executor = {}) noexcept {
u512_vec_t match_cost_vec, mismatch_cost_vec, gap_open_vec, gap_expand_vec;
match_cost_vec.zmm = _mm512_set1_epi32(this->substituter_.match);
mismatch_cost_vec.zmm = _mm512_set1_epi32(this->substituter_.mismatch);
gap_open_vec.zmm = _mm512_set1_epi32(this->gap_costs_.open);
gap_expand_vec.zmm = _mm512_set1_epi32(this->gap_costs_.extend);
size_t const body_pages = length / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_slice, second_slice, scores_pre_substitution, scores_pre_insertion,
scores_pre_deletion, scores_running_insertions, scores_running_deletions,
scores_new, scores_new_insertions, scores_new_deletions, match_cost_vec,
mismatch_cost_vec, gap_open_vec, gap_expand_vec, from, to);
});
size_t const progress = body_pages * step_k;
size_t const tail = length - progress;
if (tail)
slice_upto16chars( first_reversed_slice + progress, second_slice + progress, tail, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, match_cost_vec, mismatch_cost_vec, gap_open_vec, gap_expand_vec);
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct levenshtein_distance_myers<char, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using index_t = u32_t;
static constexpr index_t lanes_k = 8;
static constexpr size_t match_masks_bytes_k = sizeof(u64_t) * lanes_k * 256;
static constexpr int ternlog_xor_or_k = 0xBE; static constexpr int ternlog_or_nor_k = 0xF1;
levenshtein_distance_myers() noexcept {}
template <typename results_writer_>
status_t distances_8x64_(lane_pairs_view<char_t> const &pairs, results_writer_ &results,
scratch_space_t scratch_space) const noexcept {
size_t max_longer = 0;
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index)
max_longer = sz_max_of_two(max_longer, pairs.longers[lane_index].size());
if (scratch_space.size() < match_masks_bytes_k + max_longer * lanes_k) return status_t::bad_alloc_k;
u64_t *const match_masks = reinterpret_cast<u64_t *>(scratch_space.data()); u8_t *const transposed_text = reinterpret_cast<u8_t *>(scratch_space.data() + match_masks_bytes_k);
alignas(64) u64_t top_bits[lanes_k] = {0}, shorter_lengths[lanes_k] = {0}, longer_lengths[lanes_k] = {0};
for (size_t position = 0; position != max_longer * lanes_k; ++position) transposed_text[position] = 0;
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
index_t const shorter_length = (index_t)pairs.shorters[lane_index].size();
size_t const longer_length = pairs.longers[lane_index].size();
char_t const *const shorter = pairs.shorters[lane_index].data();
char_t const *const longer = pairs.longers[lane_index].data();
for (index_t position = 0; position != shorter_length; ++position)
match_masks[lane_index * 256 + (u8_t)shorter[position]] = 0;
for (size_t position = 0; position != longer_length; ++position)
match_masks[lane_index * 256 + (u8_t)longer[position]] = 0;
for (index_t position = 0; position != shorter_length; ++position)
match_masks[lane_index * 256 + (u8_t)shorter[position]] |= (u64_t)1 << position;
top_bits[lane_index] = (u64_t)1 << (shorter_length - 1);
shorter_lengths[lane_index] = shorter_length;
longer_lengths[lane_index] = longer_length;
for (size_t position = 0; position != longer_length; ++position)
transposed_text[position * lanes_k + lane_index] = (u8_t)longer[position];
}
__m512i const lane_offsets = _mm512_set_epi64(7 * 256, 6 * 256, 5 * 256, 4 * 256, 3 * 256, 2 * 256, 1 * 256, 0);
__m512i const one = _mm512_set1_epi64(1);
__m512i const top_mask = _mm512_load_si512(top_bits), longer_vec = _mm512_load_si512(longer_lengths);
__m512i const length_vec = _mm512_load_si512(shorter_lengths);
__m512i vertical_positive = _mm512_sub_epi64(_mm512_sllv_epi64(one, length_vec), one);
__m512i vertical_negative = _mm512_setzero_si512();
__m512i score = length_vec;
for (size_t position = 0; position != max_longer; ++position) {
__mmask8 const active = _mm512_cmpgt_epi64_mask(longer_vec, _mm512_set1_epi64((long long)position));
__m512i const symbols = _mm512_cvtepu8_epi64(
_mm_loadl_epi64((__m128i const *)(transposed_text + position * lanes_k)));
__m512i const equality = _mm512_i64gather_epi64(_mm512_add_epi64(lane_offsets, symbols), match_masks, 8);
__m512i const carry_in = _mm512_or_si512(equality, vertical_negative);
__m512i const sum = _mm512_add_epi64(_mm512_and_si512(equality, vertical_positive), vertical_positive);
__m512i const diagonal = _mm512_ternarylogic_epi64(sum, vertical_positive, equality, ternlog_xor_or_k);
__m512i horizontal_positive = _mm512_ternarylogic_epi64(vertical_negative, diagonal, vertical_positive,
ternlog_or_nor_k);
__m512i horizontal_negative = _mm512_and_si512(vertical_positive, diagonal);
score = _mm512_mask_add_epi64(score, active & _mm512_test_epi64_mask(horizontal_positive, top_mask), score,
one);
score = _mm512_mask_sub_epi64(score, active & _mm512_test_epi64_mask(horizontal_negative, top_mask), score,
one);
horizontal_positive = _mm512_or_si512(_mm512_slli_epi64(horizontal_positive, 1), one);
horizontal_negative = _mm512_slli_epi64(horizontal_negative, 1);
__m512i const next_positive = _mm512_ternarylogic_epi64(horizontal_negative, carry_in, horizontal_positive,
ternlog_or_nor_k);
__m512i const next_negative = _mm512_and_si512(horizontal_positive, carry_in);
vertical_positive = _mm512_mask_blend_epi64(active, vertical_positive, next_positive);
vertical_negative = _mm512_mask_blend_epi64(active, vertical_negative, next_negative);
}
alignas(64) u64_t final_scores[lanes_k];
_mm512_store_si512(final_scores, score);
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index)
results[pairs.positions[lane_index]] = (size_t)final_scores[lane_index];
return status_t::success_k;
}
template <typename results_writer_>
status_t distances_8x64_shared_query_(span<char_t const> query, span<span<char_t const> const> candidates,
results_writer_ &results, scratch_space_t scratch_space) const noexcept {
static constexpr size_t shared_match_masks_bytes_k = sizeof(u64_t) * 256;
size_t max_candidate_length = 0;
for (index_t lane_index = 0; lane_index != candidates.size(); ++lane_index)
max_candidate_length = sz_max_of_two(max_candidate_length, candidates[lane_index].size());
if (scratch_space.size() < shared_match_masks_bytes_k + max_candidate_length * lanes_k)
return status_t::bad_alloc_k;
u64_t *const match_masks = reinterpret_cast<u64_t *>(scratch_space.data()); u8_t *const transposed_text = reinterpret_cast<u8_t *>(scratch_space.data() + shared_match_masks_bytes_k);
alignas(64) u64_t top_bits[lanes_k] = {0}, shorter_lengths[lanes_k] = {0}, longer_lengths[lanes_k] = {0};
for (size_t position = 0; position != max_candidate_length * lanes_k; ++position) transposed_text[position] = 0;
index_t const query_length = (index_t)query.size();
char_t const *const query_data = query.data();
for (index_t position = 0; position != query_length; ++position) match_masks[(u8_t)query_data[position]] = 0;
for (index_t lane_index = 0; lane_index != candidates.size(); ++lane_index) {
size_t const candidate_length = candidates[lane_index].size();
char_t const *const candidate = candidates[lane_index].data();
for (size_t position = 0; position != candidate_length; ++position)
match_masks[(u8_t)candidate[position]] = 0;
}
for (index_t position = 0; position != query_length; ++position)
match_masks[(u8_t)query_data[position]] |= (u64_t)1 << position;
for (index_t lane_index = 0; lane_index != candidates.size(); ++lane_index) {
size_t const candidate_length = candidates[lane_index].size();
char_t const *const candidate = candidates[lane_index].data();
top_bits[lane_index] = (u64_t)1 << (query_length - 1);
shorter_lengths[lane_index] = query_length;
longer_lengths[lane_index] = candidate_length;
for (size_t position = 0; position != candidate_length; ++position)
transposed_text[position * lanes_k + lane_index] = (u8_t)candidate[position];
}
__m512i const lane_offsets = _mm512_setzero_si512(); __m512i const one = _mm512_set1_epi64(1);
__m512i const top_mask = _mm512_load_si512(top_bits), longer_vec = _mm512_load_si512(longer_lengths);
__m512i const length_vec = _mm512_load_si512(shorter_lengths);
__m512i vertical_positive = _mm512_sub_epi64(_mm512_sllv_epi64(one, length_vec), one);
__m512i vertical_negative = _mm512_setzero_si512();
__m512i score = length_vec;
for (size_t position = 0; position != max_candidate_length; ++position) {
__mmask8 const active = _mm512_cmpgt_epi64_mask(longer_vec, _mm512_set1_epi64((long long)position));
__m512i const symbols = _mm512_cvtepu8_epi64(
_mm_loadl_epi64((__m128i const *)(transposed_text + position * lanes_k)));
__m512i const equality = _mm512_i64gather_epi64(_mm512_add_epi64(lane_offsets, symbols), match_masks, 8);
__m512i const carry_in = _mm512_or_si512(equality, vertical_negative);
__m512i const sum = _mm512_add_epi64(_mm512_and_si512(equality, vertical_positive), vertical_positive);
__m512i const diagonal = _mm512_ternarylogic_epi64(sum, vertical_positive, equality, ternlog_xor_or_k);
__m512i horizontal_positive = _mm512_ternarylogic_epi64(vertical_negative, diagonal, vertical_positive,
ternlog_or_nor_k);
__m512i horizontal_negative = _mm512_and_si512(vertical_positive, diagonal);
score = _mm512_mask_add_epi64(score, active & _mm512_test_epi64_mask(horizontal_positive, top_mask), score,
one);
score = _mm512_mask_sub_epi64(score, active & _mm512_test_epi64_mask(horizontal_negative, top_mask), score,
one);
horizontal_positive = _mm512_or_si512(_mm512_slli_epi64(horizontal_positive, 1), one);
horizontal_negative = _mm512_slli_epi64(horizontal_negative, 1);
__m512i const next_positive = _mm512_ternarylogic_epi64(horizontal_negative, carry_in, horizontal_positive,
ternlog_or_nor_k);
__m512i const next_negative = _mm512_and_si512(horizontal_positive, carry_in);
vertical_positive = _mm512_mask_blend_epi64(active, vertical_positive, next_positive);
vertical_negative = _mm512_mask_blend_epi64(active, vertical_negative, next_negative);
}
alignas(64) u64_t final_scores[lanes_k];
_mm512_store_si512(final_scores, score);
for (index_t lane_index = 0; lane_index != candidates.size(); ++lane_index)
results[lane_index] = (size_t)final_scores[lane_index];
return status_t::success_k;
}
template <size_t words_count_, typename results_writer_>
status_t distances_8x_multiword_(lane_pairs_view<char_t> const &pairs, results_writer_ &results,
scratch_space_t scratch_space) const noexcept {
constexpr size_t words_count = words_count_;
size_t max_longer = 0;
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index)
max_longer = sz_max_of_two(max_longer, pairs.longers[lane_index].size());
size_t const match_masks_words = (size_t)256 * words_count * lanes_k;
if (scratch_space.size() < match_masks_words * sizeof(u64_t)) return status_t::bad_alloc_k;
u64_t *const match_masks = reinterpret_cast<u64_t *>(scratch_space.data());
for (size_t element = 0; element != match_masks_words; ++element) match_masks[element] = 0;
alignas(64) u64_t top_bits[lanes_k] = {0}, shorter_lengths[lanes_k] = {0}, longer_lengths[lanes_k] = {0};
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
index_t const shorter_length = (index_t)pairs.shorters[lane_index].size();
char_t const *const shorter = pairs.shorters[lane_index].data();
u64_t *const lane_table = match_masks + (size_t)lane_index * 256 * words_count;
for (index_t position = 0; position != shorter_length; ++position)
lane_table[(size_t)(u8_t)shorter[position] * words_count + (position >> 6)] |= (u64_t)1
<< (position & 63);
top_bits[lane_index] = (u64_t)1 << ((shorter_length - 1) & 63);
shorter_lengths[lane_index] = shorter_length;
longer_lengths[lane_index] = pairs.longers[lane_index].size();
}
__m512i vertical_positive[words_count_];
__m512i vertical_negative[words_count_];
for (size_t word = 0; word != words_count; ++word) {
vertical_positive[word] = _mm512_set1_epi64(-1);
vertical_negative[word] = _mm512_setzero_si512();
}
__m512i const one = _mm512_set1_epi64(1);
__m512i const top_mask = _mm512_load_si512(top_bits), longer_vec = _mm512_load_si512(longer_lengths);
__m512i score = _mm512_load_si512(shorter_lengths);
constexpr size_t last_word = words_count_ - 1;
for (size_t position = 0; position != max_longer; ++position) {
__mmask8 const active = _mm512_cmpgt_epi64_mask(longer_vec, _mm512_set1_epi64((long long)position));
alignas(64) u64_t base_offsets[lanes_k] = {0};
for (index_t lane_index = 0; lane_index != lanes_k; ++lane_index) {
bool const lane_active = (active >> lane_index) & 1u;
u8_t const symbol = lane_active ? (u8_t)pairs.longers[lane_index].data()[position] : 0;
base_offsets[lane_index] = lane_active
? (u64_t)lane_index * 256 * words_count + (u64_t)symbol * words_count
: 0;
}
__m512i addition_carry = _mm512_setzero_si512(); __m512i horizontal_positive_carry = one; __m512i horizontal_negative_carry = _mm512_setzero_si512();
for (size_t word = 0; word != words_count; ++word) {
alignas(64) u64_t equality_words[lanes_k];
for (index_t lane_index = 0; lane_index != lanes_k; ++lane_index)
equality_words[lane_index] = ((active >> lane_index) & 1u)
? match_masks[(size_t)base_offsets[lane_index] + word]
: 0;
__m512i const equality = _mm512_load_si512(equality_words);
__m512i const vertical_positive_word = vertical_positive[word];
__m512i const vertical_negative_word = vertical_negative[word];
__m512i const summand = _mm512_and_si512(equality, vertical_positive_word);
__m512i const sum_low = _mm512_add_epi64(summand, vertical_positive_word);
__mmask8 const carry_from_summand = _mm512_cmplt_epu64_mask(sum_low, summand);
__m512i const sum = _mm512_add_epi64(sum_low, addition_carry);
__mmask8 const carry_from_incoming = _mm512_cmplt_epu64_mask(sum, sum_low);
addition_carry = _mm512_maskz_set1_epi64((__mmask8)(carry_from_summand | carry_from_incoming), 1);
__m512i const carry_in = _mm512_or_si512(equality, vertical_negative_word); __m512i const diagonal = _mm512_ternarylogic_epi64(sum, vertical_positive_word, carry_in,
ternlog_xor_or_k);
__m512i horizontal_positive = _mm512_ternarylogic_epi64(
vertical_negative_word, diagonal, vertical_positive_word, ternlog_or_nor_k); __m512i horizontal_negative = _mm512_and_si512(vertical_positive_word, diagonal);
if (word == last_word) {
score = _mm512_mask_add_epi64(score, active & _mm512_test_epi64_mask(horizontal_positive, top_mask),
score, one);
score = _mm512_mask_sub_epi64(score, active & _mm512_test_epi64_mask(horizontal_negative, top_mask),
score, one);
}
__m512i const next_positive_carry = _mm512_srli_epi64(horizontal_positive, 63);
__m512i const next_negative_carry = _mm512_srli_epi64(horizontal_negative, 63);
horizontal_positive = _mm512_or_si512(_mm512_slli_epi64(horizontal_positive, 1),
horizontal_positive_carry);
horizontal_negative = _mm512_or_si512(_mm512_slli_epi64(horizontal_negative, 1),
horizontal_negative_carry);
horizontal_positive_carry = next_positive_carry;
horizontal_negative_carry = next_negative_carry;
__m512i const next_positive = _mm512_ternarylogic_epi64(horizontal_negative, carry_in,
horizontal_positive, ternlog_or_nor_k);
__m512i const next_negative = _mm512_and_si512(horizontal_positive, carry_in);
vertical_positive[word] = _mm512_mask_blend_epi64(active, vertical_positive_word, next_positive);
vertical_negative[word] = _mm512_mask_blend_epi64(active, vertical_negative_word, next_negative);
}
}
alignas(64) u64_t final_scores[lanes_k];
_mm512_store_si512(final_scores, score);
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index)
results[pairs.positions[lane_index]] = (size_t)final_scores[lane_index];
return status_t::success_k;
}
template <typename results_writer_>
status_t distances_8x_multiword_large_(lane_pairs_view<char_t> const &pairs, results_writer_ &results,
scratch_space_t scratch_space) const noexcept {
static constexpr size_t stack_words_capacity_k = 64;
size_t max_longer = 0, max_shorter = 0;
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
max_longer = sz_max_of_two(max_longer, pairs.longers[lane_index].size());
max_shorter = sz_max_of_two(max_shorter, pairs.shorters[lane_index].size());
}
size_t const words_count = (max_shorter + 63) / 64;
if (words_count == 0 || words_count > stack_words_capacity_k) return status_t::bad_alloc_k;
size_t const match_masks_words = (size_t)256 * words_count * lanes_k;
if (scratch_space.size() < match_masks_words * sizeof(u64_t)) return status_t::bad_alloc_k;
u64_t *const match_masks = reinterpret_cast<u64_t *>(scratch_space.data());
for (size_t element = 0; element != match_masks_words; ++element) match_masks[element] = 0;
alignas(64) u64_t top_bits[lanes_k] = {0}, shorter_lengths[lanes_k] = {0}, longer_lengths[lanes_k] = {0};
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
index_t const shorter_length = (index_t)pairs.shorters[lane_index].size();
char_t const *const shorter = pairs.shorters[lane_index].data();
u64_t *const lane_table = match_masks + (size_t)lane_index * 256 * words_count;
for (index_t position = 0; position != shorter_length; ++position)
lane_table[(size_t)(u8_t)shorter[position] * words_count + (position >> 6)] |= (u64_t)1
<< (position & 63);
top_bits[lane_index] = (u64_t)1 << ((shorter_length - 1) & 63);
shorter_lengths[lane_index] = shorter_length;
longer_lengths[lane_index] = pairs.longers[lane_index].size();
}
__m512i vertical_positive[stack_words_capacity_k];
__m512i vertical_negative[stack_words_capacity_k];
for (size_t word = 0; word != words_count; ++word) {
vertical_positive[word] = _mm512_set1_epi64(-1);
vertical_negative[word] = _mm512_setzero_si512();
}
__m512i const one = _mm512_set1_epi64(1);
__m512i const top_mask = _mm512_load_si512(top_bits), longer_vec = _mm512_load_si512(longer_lengths);
__m512i score = _mm512_load_si512(shorter_lengths);
size_t const last_word = words_count - 1;
for (size_t position = 0; position != max_longer; ++position) {
__mmask8 const active = _mm512_cmpgt_epi64_mask(longer_vec, _mm512_set1_epi64((long long)position));
alignas(64) u64_t base_offsets[lanes_k] = {0};
for (index_t lane_index = 0; lane_index != lanes_k; ++lane_index) {
bool const lane_active = (active >> lane_index) & 1u;
u8_t const symbol = lane_active ? (u8_t)pairs.longers[lane_index].data()[position] : 0;
base_offsets[lane_index] = lane_active
? (u64_t)lane_index * 256 * words_count + (u64_t)symbol * words_count
: 0;
}
__m512i addition_carry = _mm512_setzero_si512(); __m512i horizontal_positive_carry = one; __m512i horizontal_negative_carry = _mm512_setzero_si512();
for (size_t word = 0; word != words_count; ++word) {
alignas(64) u64_t equality_words[lanes_k];
for (index_t lane_index = 0; lane_index != lanes_k; ++lane_index)
equality_words[lane_index] = ((active >> lane_index) & 1u)
? match_masks[(size_t)base_offsets[lane_index] + word]
: 0;
__m512i const equality = _mm512_load_si512(equality_words);
__m512i const vertical_positive_word = vertical_positive[word];
__m512i const vertical_negative_word = vertical_negative[word];
__m512i const summand = _mm512_and_si512(equality, vertical_positive_word);
__m512i const sum_low = _mm512_add_epi64(summand, vertical_positive_word);
__mmask8 const carry_from_summand = _mm512_cmplt_epu64_mask(sum_low, summand);
__m512i const sum = _mm512_add_epi64(sum_low, addition_carry);
__mmask8 const carry_from_incoming = _mm512_cmplt_epu64_mask(sum, sum_low);
addition_carry = _mm512_maskz_set1_epi64((__mmask8)(carry_from_summand | carry_from_incoming), 1);
__m512i const carry_in = _mm512_or_si512(equality, vertical_negative_word); __m512i const diagonal = _mm512_ternarylogic_epi64(sum, vertical_positive_word, carry_in,
ternlog_xor_or_k);
__m512i horizontal_positive = _mm512_ternarylogic_epi64(
vertical_negative_word, diagonal, vertical_positive_word, ternlog_or_nor_k); __m512i horizontal_negative = _mm512_and_si512(vertical_positive_word, diagonal);
if (word == last_word) {
score = _mm512_mask_add_epi64(score, active & _mm512_test_epi64_mask(horizontal_positive, top_mask),
score, one);
score = _mm512_mask_sub_epi64(score, active & _mm512_test_epi64_mask(horizontal_negative, top_mask),
score, one);
}
__m512i const next_positive_carry = _mm512_srli_epi64(horizontal_positive, 63);
__m512i const next_negative_carry = _mm512_srli_epi64(horizontal_negative, 63);
horizontal_positive = _mm512_or_si512(_mm512_slli_epi64(horizontal_positive, 1),
horizontal_positive_carry);
horizontal_negative = _mm512_or_si512(_mm512_slli_epi64(horizontal_negative, 1),
horizontal_negative_carry);
horizontal_positive_carry = next_positive_carry;
horizontal_negative_carry = next_negative_carry;
__m512i const next_positive = _mm512_ternarylogic_epi64(horizontal_negative, carry_in,
horizontal_positive, ternlog_or_nor_k);
__m512i const next_negative = _mm512_and_si512(horizontal_positive, carry_in);
vertical_positive[word] = _mm512_mask_blend_epi64(active, vertical_positive_word, next_positive);
vertical_negative[word] = _mm512_mask_blend_epi64(active, vertical_negative_word, next_negative);
}
}
alignas(64) u64_t final_scores[lanes_k];
_mm512_store_si512(final_scores, score);
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index)
results[pairs.positions[lane_index]] = (size_t)final_scores[lane_index];
return status_t::success_k;
}
};
template <sz_capability_t capability_>
struct levenshtein_distance_myers<rune_t, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = rune_t;
using index_t = u32_t;
static constexpr index_t lanes_k = 8;
static constexpr int ternlog_xor_or_k = 0xBE; static constexpr int ternlog_or_nor_k = 0xF1;
static constexpr rune_t empty_slot_k = static_cast<rune_t>(0xFFFFFFFFu);
levenshtein_distance_myers() noexcept {}
static size_t words_count_for(size_t shorter_length) noexcept {
return divide_round_up<size_t>(shorter_length, 64);
}
static index_t hash_capacity_for(size_t distinct_upper_bound) noexcept {
size_t const slots_wanted = sz_max_of_two(2 * distinct_upper_bound, (size_t)1);
return static_cast<index_t>(sz_size_bit_ceil(slots_wanted));
}
static index_t hash_rune(rune_t rune, index_t capacity) noexcept {
u64_t const mixed = static_cast<u64_t>(static_cast<u32_t>(rune)) * 0x9E3779B97F4A7C15ull;
return static_cast<index_t>((mixed >> 32) & static_cast<u64_t>(capacity - 1));
}
static size_t scratch_bytes_for(size_t max_shorter) noexcept {
size_t const words_count = words_count_for(sz_max_of_two(max_shorter, (size_t)1));
size_t const capacity = hash_capacity_for(sz_max_of_two(max_shorter, (size_t)1));
size_t const slot_keys_bytes = sizeof(rune_t) * capacity * lanes_k;
size_t const slot_masks_bytes = sizeof(u64_t) * capacity * words_count * lanes_k;
size_t const absent_row_bytes = sizeof(u64_t) * words_count;
return slot_keys_bytes + slot_masks_bytes + absent_row_bytes;
}
#pragma region Per Lane Hash match_masks
static bool build_lane_hashes_(span<char_t const> const *shorters, index_t pairs_active, index_t capacity,
size_t words_count, scratch_space_t scratch_space, rune_t *&slot_keys,
u64_t *&slot_masks, u64_t *&absent_row) noexcept {
size_t const slot_keys_bytes = sizeof(rune_t) * capacity * lanes_k;
size_t const slot_masks_bytes = sizeof(u64_t) * capacity * words_count * lanes_k;
size_t const absent_row_bytes = sizeof(u64_t) * words_count;
if (scratch_space.size() < slot_keys_bytes + slot_masks_bytes + absent_row_bytes) return false;
slot_keys = reinterpret_cast<rune_t *>(scratch_space.data());
slot_masks = reinterpret_cast<u64_t *>(scratch_space.data() + slot_keys_bytes);
absent_row = reinterpret_cast<u64_t *>(scratch_space.data() + slot_keys_bytes + slot_masks_bytes);
for (size_t word = 0; word != words_count; ++word) absent_row[word] = 0;
for (size_t slot = 0; slot != (size_t)capacity * lanes_k; ++slot) slot_keys[slot] = empty_slot_k;
for (index_t lane = 0; lane != pairs_active; ++lane) {
rune_t *const lane_keys = slot_keys + (size_t)lane * capacity;
u64_t *const lane_masks = slot_masks + (size_t)lane * capacity * words_count;
index_t const shorter_length = (index_t)shorters[lane].size();
char_t const *const shorter = shorters[lane].data();
for (index_t position = 0; position != shorter_length; ++position) {
rune_t const rune = shorter[position];
index_t slot = hash_rune(rune, capacity);
for (;; slot = (slot + 1) & (capacity - 1)) {
if (lane_keys[slot] == rune) break;
if (lane_keys[slot] == empty_slot_k) {
lane_keys[slot] = rune;
for (size_t word = 0; word != words_count; ++word)
lane_masks[(size_t)slot * words_count + word] = 0;
break;
}
}
lane_masks[(size_t)slot * words_count + (position >> 6)] |= (u64_t)1 << (position & 63);
}
}
return true;
}
static u64_t const *lane_match_row_(rune_t const *slot_keys, u64_t const *slot_masks, u64_t const *absent_row,
index_t lane, index_t capacity, size_t words_count, rune_t symbol) noexcept {
rune_t const *const lane_keys = slot_keys + (size_t)lane * capacity;
u64_t const *const lane_masks = slot_masks + (size_t)lane * capacity * words_count;
for (index_t slot = hash_rune(symbol, capacity);; slot = (slot + 1) & (capacity - 1)) {
rune_t const key = lane_keys[slot];
if (key == symbol) return &lane_masks[(size_t)slot * words_count];
if (key == empty_slot_k) break;
}
return absent_row;
}
#pragma endregion Per Lane Hash match_masks
template <typename results_writer_>
status_t distances_8x64_(lane_pairs_view<char_t> const &pairs, results_writer_ &results,
scratch_space_t scratch_space) const noexcept {
size_t max_longer = 0, max_shorter = 0;
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
max_longer = sz_max_of_two(max_longer, pairs.longers[lane_index].size());
max_shorter = sz_max_of_two(max_shorter, pairs.shorters[lane_index].size());
}
index_t const capacity = hash_capacity_for(sz_max_of_two(max_shorter, (size_t)1));
rune_t *slot_keys = nullptr;
u64_t *slot_masks = nullptr, *absent_row = nullptr;
if (!build_lane_hashes_(pairs.shorters.data(), (index_t)pairs.lanes_count(), capacity, 1, scratch_space,
slot_keys, slot_masks, absent_row))
return status_t::bad_alloc_k;
alignas(64) u64_t top_bits[lanes_k] = {0}, shorter_lengths[lanes_k] = {0}, longer_lengths[lanes_k] = {0};
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
index_t const shorter_length = (index_t)pairs.shorters[lane_index].size();
top_bits[lane_index] = (u64_t)1 << (shorter_length - 1);
shorter_lengths[lane_index] = shorter_length;
longer_lengths[lane_index] = pairs.longers[lane_index].size();
}
__m512i const one = _mm512_set1_epi64(1);
__m512i const top_mask = _mm512_load_si512(top_bits), longer_vec = _mm512_load_si512(longer_lengths);
__m512i const length_vec = _mm512_load_si512(shorter_lengths);
__m512i vertical_positive = _mm512_sub_epi64(_mm512_sllv_epi64(one, length_vec), one);
__m512i vertical_negative = _mm512_setzero_si512();
__m512i score = length_vec;
for (size_t position = 0; position != max_longer; ++position) {
__mmask8 const active = _mm512_cmpgt_epi64_mask(longer_vec, _mm512_set1_epi64((long long)position));
alignas(64) u64_t equality_words[lanes_k];
for (index_t lane = 0; lane != lanes_k; ++lane) {
bool const lane_active = (active >> lane) & 1u;
if (!lane_active) {
equality_words[lane] = 0;
continue;
}
rune_t const symbol = pairs.longers[lane].data()[position];
equality_words[lane] = lane_match_row_(slot_keys, slot_masks, absent_row, lane, capacity, 1, symbol)[0];
}
__m512i const equality = _mm512_load_si512(equality_words);
__m512i const carry_in = _mm512_or_si512(equality, vertical_negative);
__m512i const sum = _mm512_add_epi64(_mm512_and_si512(equality, vertical_positive), vertical_positive);
__m512i const diagonal = _mm512_ternarylogic_epi64(sum, vertical_positive, equality, ternlog_xor_or_k);
__m512i horizontal_positive = _mm512_ternarylogic_epi64(vertical_negative, diagonal, vertical_positive,
ternlog_or_nor_k);
__m512i horizontal_negative = _mm512_and_si512(vertical_positive, diagonal);
score = _mm512_mask_add_epi64(score, active & _mm512_test_epi64_mask(horizontal_positive, top_mask), score,
one);
score = _mm512_mask_sub_epi64(score, active & _mm512_test_epi64_mask(horizontal_negative, top_mask), score,
one);
horizontal_positive = _mm512_or_si512(_mm512_slli_epi64(horizontal_positive, 1), one);
horizontal_negative = _mm512_slli_epi64(horizontal_negative, 1);
__m512i const next_positive = _mm512_ternarylogic_epi64(horizontal_negative, carry_in, horizontal_positive,
ternlog_or_nor_k);
__m512i const next_negative = _mm512_and_si512(horizontal_positive, carry_in);
vertical_positive = _mm512_mask_blend_epi64(active, vertical_positive, next_positive);
vertical_negative = _mm512_mask_blend_epi64(active, vertical_negative, next_negative);
}
alignas(64) u64_t final_scores[lanes_k];
_mm512_store_si512(final_scores, score);
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index)
results[pairs.positions[lane_index]] = (size_t)final_scores[lane_index];
return status_t::success_k;
}
template <size_t words_count_, typename results_writer_>
status_t distances_8x_multiword_(lane_pairs_view<char_t> const &pairs, results_writer_ &results,
scratch_space_t scratch_space) const noexcept {
constexpr size_t words_count = words_count_;
size_t max_longer = 0, max_shorter = 0;
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
max_longer = sz_max_of_two(max_longer, pairs.longers[lane_index].size());
max_shorter = sz_max_of_two(max_shorter, pairs.shorters[lane_index].size());
}
index_t const capacity = hash_capacity_for(sz_max_of_two(max_shorter, (size_t)1));
rune_t *slot_keys = nullptr;
u64_t *slot_masks = nullptr, *absent_row = nullptr;
if (!build_lane_hashes_(pairs.shorters.data(), (index_t)pairs.lanes_count(), capacity, words_count,
scratch_space, slot_keys, slot_masks, absent_row))
return status_t::bad_alloc_k;
alignas(64) u64_t top_bits[lanes_k] = {0}, shorter_lengths[lanes_k] = {0}, longer_lengths[lanes_k] = {0};
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
index_t const shorter_length = (index_t)pairs.shorters[lane_index].size();
top_bits[lane_index] = (u64_t)1 << ((shorter_length - 1) & 63);
shorter_lengths[lane_index] = shorter_length;
longer_lengths[lane_index] = pairs.longers[lane_index].size();
}
__m512i vertical_positive[words_count_];
__m512i vertical_negative[words_count_];
for (size_t word = 0; word != words_count; ++word) {
vertical_positive[word] = _mm512_set1_epi64(-1);
vertical_negative[word] = _mm512_setzero_si512();
}
__m512i const one = _mm512_set1_epi64(1);
__m512i const top_mask = _mm512_load_si512(top_bits), longer_vec = _mm512_load_si512(longer_lengths);
__m512i score = _mm512_load_si512(shorter_lengths);
constexpr size_t last_word = words_count_ - 1;
for (size_t position = 0; position != max_longer; ++position) {
__mmask8 const active = _mm512_cmpgt_epi64_mask(longer_vec, _mm512_set1_epi64((long long)position));
u64_t const *match_rows[lanes_k];
for (index_t lane = 0; lane != lanes_k; ++lane) {
bool const lane_active = (active >> lane) & 1u;
rune_t const symbol = lane_active ? pairs.longers[lane].data()[position] : empty_slot_k;
match_rows[lane] = lane_active ? lane_match_row_(slot_keys, slot_masks, absent_row, lane, capacity,
words_count, symbol)
: absent_row;
}
__m512i addition_carry = _mm512_setzero_si512(); __m512i horizontal_positive_carry = one; __m512i horizontal_negative_carry = _mm512_setzero_si512();
for (size_t word = 0; word != words_count; ++word) {
alignas(64) u64_t equality_words[lanes_k];
for (index_t lane = 0; lane != lanes_k; ++lane)
equality_words[lane] = ((active >> lane) & 1u) ? match_rows[lane][word] : 0;
__m512i const equality = _mm512_load_si512(equality_words);
__m512i const vertical_positive_word = vertical_positive[word];
__m512i const vertical_negative_word = vertical_negative[word];
__m512i const summand = _mm512_and_si512(equality, vertical_positive_word);
__m512i const sum_low = _mm512_add_epi64(summand, vertical_positive_word);
__mmask8 const carry_from_summand = _mm512_cmplt_epu64_mask(sum_low, summand);
__m512i const sum = _mm512_add_epi64(sum_low, addition_carry);
__mmask8 const carry_from_incoming = _mm512_cmplt_epu64_mask(sum, sum_low);
addition_carry = _mm512_maskz_set1_epi64((__mmask8)(carry_from_summand | carry_from_incoming), 1);
__m512i const carry_in = _mm512_or_si512(equality, vertical_negative_word); __m512i const diagonal = _mm512_ternarylogic_epi64(sum, vertical_positive_word, carry_in,
ternlog_xor_or_k);
__m512i horizontal_positive = _mm512_ternarylogic_epi64(
vertical_negative_word, diagonal, vertical_positive_word, ternlog_or_nor_k); __m512i horizontal_negative = _mm512_and_si512(vertical_positive_word, diagonal);
if (word == last_word) {
score = _mm512_mask_add_epi64(score, active & _mm512_test_epi64_mask(horizontal_positive, top_mask),
score, one);
score = _mm512_mask_sub_epi64(score, active & _mm512_test_epi64_mask(horizontal_negative, top_mask),
score, one);
}
__m512i const next_positive_carry = _mm512_srli_epi64(horizontal_positive, 63);
__m512i const next_negative_carry = _mm512_srli_epi64(horizontal_negative, 63);
horizontal_positive = _mm512_or_si512(_mm512_slli_epi64(horizontal_positive, 1),
horizontal_positive_carry);
horizontal_negative = _mm512_or_si512(_mm512_slli_epi64(horizontal_negative, 1),
horizontal_negative_carry);
horizontal_positive_carry = next_positive_carry;
horizontal_negative_carry = next_negative_carry;
__m512i const next_positive = _mm512_ternarylogic_epi64(horizontal_negative, carry_in,
horizontal_positive, ternlog_or_nor_k);
__m512i const next_negative = _mm512_and_si512(horizontal_positive, carry_in);
vertical_positive[word] = _mm512_mask_blend_epi64(active, vertical_positive_word, next_positive);
vertical_negative[word] = _mm512_mask_blend_epi64(active, vertical_negative_word, next_negative);
}
}
alignas(64) u64_t final_scores[lanes_k];
_mm512_store_si512(final_scores, score);
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index)
results[pairs.positions[lane_index]] = (size_t)final_scores[lane_index];
return status_t::success_k;
}
template <typename results_writer_>
status_t distances_8x_multiword_large_(lane_pairs_view<char_t> const &pairs, results_writer_ &results,
scratch_space_t scratch_space) const noexcept {
static constexpr size_t stack_words_capacity_k = 64;
size_t max_longer = 0, max_shorter = 0;
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
max_longer = sz_max_of_two(max_longer, pairs.longers[lane_index].size());
max_shorter = sz_max_of_two(max_shorter, pairs.shorters[lane_index].size());
}
size_t const words_count = words_count_for(sz_max_of_two(max_shorter, (size_t)1));
if (words_count == 0 || words_count > stack_words_capacity_k) return status_t::bad_alloc_k;
index_t const capacity = hash_capacity_for(sz_max_of_two(max_shorter, (size_t)1));
rune_t *slot_keys = nullptr;
u64_t *slot_masks = nullptr, *absent_row = nullptr;
if (!build_lane_hashes_(pairs.shorters.data(), (index_t)pairs.lanes_count(), capacity, words_count,
scratch_space, slot_keys, slot_masks, absent_row))
return status_t::bad_alloc_k;
alignas(64) u64_t top_bits[lanes_k] = {0}, shorter_lengths[lanes_k] = {0}, longer_lengths[lanes_k] = {0};
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index) {
index_t const shorter_length = (index_t)pairs.shorters[lane_index].size();
top_bits[lane_index] = (u64_t)1 << ((shorter_length - 1) & 63);
shorter_lengths[lane_index] = shorter_length;
longer_lengths[lane_index] = pairs.longers[lane_index].size();
}
__m512i vertical_positive[stack_words_capacity_k];
__m512i vertical_negative[stack_words_capacity_k];
for (size_t word = 0; word != words_count; ++word) {
vertical_positive[word] = _mm512_set1_epi64(-1);
vertical_negative[word] = _mm512_setzero_si512();
}
__m512i const one = _mm512_set1_epi64(1);
__m512i const top_mask = _mm512_load_si512(top_bits), longer_vec = _mm512_load_si512(longer_lengths);
__m512i score = _mm512_load_si512(shorter_lengths);
size_t const last_word = words_count - 1;
for (size_t position = 0; position != max_longer; ++position) {
__mmask8 const active = _mm512_cmpgt_epi64_mask(longer_vec, _mm512_set1_epi64((long long)position));
u64_t const *match_rows[lanes_k];
for (index_t lane = 0; lane != lanes_k; ++lane) {
bool const lane_active = (active >> lane) & 1u;
rune_t const symbol = lane_active ? pairs.longers[lane].data()[position] : empty_slot_k;
match_rows[lane] = lane_active ? lane_match_row_(slot_keys, slot_masks, absent_row, lane, capacity,
words_count, symbol)
: absent_row;
}
__m512i addition_carry = _mm512_setzero_si512(); __m512i horizontal_positive_carry = one; __m512i horizontal_negative_carry = _mm512_setzero_si512();
for (size_t word = 0; word != words_count; ++word) {
alignas(64) u64_t equality_words[lanes_k];
for (index_t lane = 0; lane != lanes_k; ++lane)
equality_words[lane] = ((active >> lane) & 1u) ? match_rows[lane][word] : 0;
__m512i const equality = _mm512_load_si512(equality_words);
__m512i const vertical_positive_word = vertical_positive[word];
__m512i const vertical_negative_word = vertical_negative[word];
__m512i const summand = _mm512_and_si512(equality, vertical_positive_word);
__m512i const sum_low = _mm512_add_epi64(summand, vertical_positive_word);
__mmask8 const carry_from_summand = _mm512_cmplt_epu64_mask(sum_low, summand);
__m512i const sum = _mm512_add_epi64(sum_low, addition_carry);
__mmask8 const carry_from_incoming = _mm512_cmplt_epu64_mask(sum, sum_low);
addition_carry = _mm512_maskz_set1_epi64((__mmask8)(carry_from_summand | carry_from_incoming), 1);
__m512i const carry_in = _mm512_or_si512(equality, vertical_negative_word); __m512i const diagonal = _mm512_ternarylogic_epi64(sum, vertical_positive_word, carry_in,
ternlog_xor_or_k);
__m512i horizontal_positive = _mm512_ternarylogic_epi64(
vertical_negative_word, diagonal, vertical_positive_word, ternlog_or_nor_k); __m512i horizontal_negative = _mm512_and_si512(vertical_positive_word, diagonal);
if (word == last_word) {
score = _mm512_mask_add_epi64(score, active & _mm512_test_epi64_mask(horizontal_positive, top_mask),
score, one);
score = _mm512_mask_sub_epi64(score, active & _mm512_test_epi64_mask(horizontal_negative, top_mask),
score, one);
}
__m512i const next_positive_carry = _mm512_srli_epi64(horizontal_positive, 63);
__m512i const next_negative_carry = _mm512_srli_epi64(horizontal_negative, 63);
horizontal_positive = _mm512_or_si512(_mm512_slli_epi64(horizontal_positive, 1),
horizontal_positive_carry);
horizontal_negative = _mm512_or_si512(_mm512_slli_epi64(horizontal_negative, 1),
horizontal_negative_carry);
horizontal_positive_carry = next_positive_carry;
horizontal_negative_carry = next_negative_carry;
__m512i const next_positive = _mm512_ternarylogic_epi64(horizontal_negative, carry_in,
horizontal_positive, ternlog_or_nor_k);
__m512i const next_negative = _mm512_and_si512(horizontal_positive, carry_in);
vertical_positive[word] = _mm512_mask_blend_epi64(active, vertical_positive_word, next_positive);
vertical_negative[word] = _mm512_mask_blend_epi64(active, vertical_negative_word, next_negative);
}
}
alignas(64) u64_t final_scores[lanes_k];
_mm512_store_si512(final_scores, score);
for (index_t lane_index = 0; lane_index != pairs.lanes_count(); ++lane_index)
results[pairs.positions[lane_index]] = (size_t)final_scores[lane_index];
return status_t::success_k;
}
};
template <typename gap_costs_type_, sz_capability_t capability_>
struct levenshtein_distance<char, gap_costs_type_, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using gap_costs_t = gap_costs_type_;
static constexpr sz_capability_t capability_k = capability_;
static constexpr sz_capability_t capability_wout_simd_k = (sz_capability_t)(capability_k & ~sz_cap_icelake_k);
using diagonal_u8_t = diagonal_walker<char_t, u8_t, uniform_substitution_costs_t, gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using diagonal_u16_t = diagonal_walker<char_t, u16_t, uniform_substitution_costs_t, gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using diagonal_u32_t = diagonal_walker<char_t, u32_t, uniform_substitution_costs_t, gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using diagonal_u64_t = diagonal_walker<char_t, u64_t, uniform_substitution_costs_t, gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_wout_simd_k>;
uniform_substitution_costs_t substituter_ {};
gap_costs_t gap_costs_ {};
levenshtein_distance() noexcept {}
levenshtein_distance(uniform_substitution_costs_t subs, gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(span<char_t const> first, span<char_t const> second,
cpu_specs_t const &specs) const noexcept {
using diagonal_memory_requirements_t = diagonal_memory_requirements<size_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
return requirements.total;
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, size_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
using diagonal_memory_requirements_t = diagonal_memory_requirements<size_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
if (requirements.bytes_per_cell <= 1) {
u8_t result_u8;
status_t status = diagonal_u8_t {substituter_, gap_costs_}(first, second, result_u8, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u8;
}
else if (requirements.bytes_per_cell == 2) {
u16_t result_u16;
status_t status = diagonal_u16_t {substituter_, gap_costs_}(first, second, result_u16, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u16;
}
else if (requirements.bytes_per_cell == 4) {
u32_t result_u32;
status_t status = diagonal_u32_t {substituter_, gap_costs_}(first, second, result_u32, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u32;
}
else if (requirements.bytes_per_cell == 8) {
u64_t result_u64;
status_t status = diagonal_u64_t {substituter_, gap_costs_}(first, second, result_u64, scratch_space,
executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u64;
}
return status_t::success_k;
}
};
template <sz_similarity_objective_t objective_>
struct candidate_lane_walker<char, u16_t, uniform_substitution_costs_t, linear_gap_costs_t, objective_,
sz_similarity_global_k, sz_cap_icelake_k, 32, void> {
using char_t = char;
using score_t = u16_t;
using substituter_t = uniform_substitution_costs_t;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 32;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static_assert(objective_ == sz_minimize_distance_k,
"The 16-bit candidate-lane kernel only implements distance minimization (Levenshtein).");
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, linear_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
scratch_amount_t amount {specs.cache_line_width};
amount += row_bytes; amount += row_bytes; return amount;
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t const match_cost = static_cast<score_t>(substituter_.match);
score_t const mismatch_cost = static_cast<score_t>(substituter_.mismatch);
score_t const gap_cost = static_cast<score_t>(gap_costs_.open_or_extend);
__m512i const match_vec = _mm512_set1_epi16(static_cast<short>(match_cost));
__m512i const mismatch_vec = _mm512_set1_epi16(static_cast<short>(mismatch_cost));
__m512i const gap_vec = _mm512_set1_epi16(static_cast<short>(gap_cost));
for (size_t column = 0; column <= longest_candidate; ++column)
_mm512_storeu_si512(previous_row + column * candidate_lanes_k,
_mm512_set1_epi16(static_cast<short>(static_cast<u16_t>(column * gap_cost))));
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
__m256i const query_char_vec = _mm256_set1_epi8(query[query_position - 1]);
_mm512_storeu_si512(current_row,
_mm512_set1_epi16(static_cast<short>(static_cast<u16_t>(query_position * gap_cost))));
for (size_t column = 1; column <= longest_candidate; ++column) {
__m256i const candidate_chars_vec = _mm256_loadu_epi8(candidates.position(column - 1));
__m512i const diagonal_vec = _mm512_loadu_si512(previous_row + (column - 1) * candidate_lanes_k);
__m512i const deletion_source_vec = _mm512_loadu_si512(previous_row + column * candidate_lanes_k);
__m512i const insertion_source_vec = _mm512_loadu_si512(current_row + (column - 1) * candidate_lanes_k);
__mmask32 const equal_mask = _mm256_cmpeq_epi8_mask(query_char_vec, candidate_chars_vec);
__m512i const mismatch_addend_vec = _mm512_mask_blend_epi16(equal_mask, mismatch_vec, match_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi16(diagonal_vec, mismatch_addend_vec);
__m512i const cost_if_deletion_vec = _mm512_add_epi16(deletion_source_vec, gap_vec);
__m512i const cost_if_insertion_vec = _mm512_add_epi16(insertion_source_vec, gap_vec);
__m512i const cell_score_vec = _mm512_min_epu16(
cost_if_substitution_vec, _mm512_min_epu16(cost_if_deletion_vec, cost_if_insertion_vec));
_mm512_storeu_si512(current_row + column * candidate_lanes_k, cell_score_vec);
}
trivial_swap(previous_row, current_row);
}
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
return status_t::success_k;
}
};
template <sz_similarity_objective_t objective_>
struct candidate_lane_walker<char, u32_t, uniform_substitution_costs_t, linear_gap_costs_t, objective_,
sz_similarity_global_k, sz_cap_icelake_k, 16, void> {
using char_t = char;
using score_t = u32_t;
using substituter_t = uniform_substitution_costs_t;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 16;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static_assert(objective_ == sz_minimize_distance_k,
"The 32-bit candidate-lane kernel only implements distance minimization (Levenshtein).");
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, linear_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
scratch_amount_t amount {specs.cache_line_width};
amount += row_bytes; amount += row_bytes; return amount;
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t const match_cost = static_cast<score_t>(substituter_.match);
score_t const mismatch_cost = static_cast<score_t>(substituter_.mismatch);
score_t const gap_cost = static_cast<score_t>(gap_costs_.open_or_extend);
__m512i const match_vec = _mm512_set1_epi32(static_cast<int>(match_cost));
__m512i const mismatch_vec = _mm512_set1_epi32(static_cast<int>(mismatch_cost));
__m512i const gap_vec = _mm512_set1_epi32(static_cast<int>(gap_cost));
for (size_t column = 0; column <= longest_candidate; ++column)
_mm512_storeu_epi32(previous_row + column * candidate_lanes_k,
_mm512_set1_epi32(static_cast<int>(static_cast<u32_t>(column * gap_cost))));
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
__m128i const query_char_vec = _mm_set1_epi8(query[query_position - 1]);
_mm512_storeu_epi32(current_row,
_mm512_set1_epi32(static_cast<int>(static_cast<u32_t>(query_position * gap_cost))));
for (size_t column = 1; column <= longest_candidate; ++column) {
__m128i const candidate_chars_vec = _mm_loadu_epi8(candidates.position(column - 1));
__m512i const diagonal_vec = _mm512_loadu_epi32(previous_row + (column - 1) * candidate_lanes_k);
__m512i const deletion_source_vec = _mm512_loadu_epi32(previous_row + column * candidate_lanes_k);
__m512i const insertion_source_vec = _mm512_loadu_epi32(current_row + (column - 1) * candidate_lanes_k);
__mmask16 const equal_mask = _mm_cmpeq_epi8_mask(query_char_vec, candidate_chars_vec);
__m512i const mismatch_addend_vec = _mm512_mask_blend_epi32(equal_mask, mismatch_vec, match_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi32(diagonal_vec, mismatch_addend_vec);
__m512i const cost_if_deletion_vec = _mm512_add_epi32(deletion_source_vec, gap_vec);
__m512i const cost_if_insertion_vec = _mm512_add_epi32(insertion_source_vec, gap_vec);
__m512i const cell_score_vec = _mm512_min_epu32(
cost_if_substitution_vec, _mm512_min_epu32(cost_if_deletion_vec, cost_if_insertion_vec));
_mm512_storeu_epi32(current_row + column * candidate_lanes_k, cell_score_vec);
}
trivial_swap(previous_row, current_row);
}
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
return status_t::success_k;
}
};
template <sz_similarity_objective_t objective_>
struct candidate_lane_walker<char, u16_t, uniform_substitution_costs_t, affine_gap_costs_t, objective_,
sz_similarity_global_k, sz_cap_icelake_k, 32, void> {
using char_t = char;
using score_t = u16_t;
using substituter_t = uniform_substitution_costs_t;
using gap_costs_t = affine_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 32;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static_assert(objective_ == sz_minimize_distance_k,
"The 16-bit affine candidate-lane kernel only implements distance minimization (Levenshtein).");
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, affine_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const score_row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
scratch_amount_t amount {specs.cache_line_width};
amount += score_row_bytes; amount += score_row_bytes; amount += score_row_bytes; return amount;
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t *vertical_row = current_row + row_stride;
score_t const match_cost = static_cast<score_t>(substituter_.match);
score_t const mismatch_cost = static_cast<score_t>(substituter_.mismatch);
score_t const open = static_cast<score_t>(gap_costs_.open);
score_t const extend = static_cast<score_t>(gap_costs_.extend);
__m512i const match_vec = _mm512_set1_epi16(static_cast<short>(match_cost));
__m512i const mismatch_vec = _mm512_set1_epi16(static_cast<short>(mismatch_cost));
__m512i const open_vec = _mm512_set1_epi16(static_cast<short>(open));
__m512i const extend_vec = _mm512_set1_epi16(static_cast<short>(extend));
__m512i const discard_bias_vec = _mm512_set1_epi16(static_cast<short>(static_cast<u16_t>(open + extend)));
_mm512_storeu_si512(previous_row, _mm512_setzero_si512());
_mm512_storeu_si512(vertical_row, discard_bias_vec);
for (size_t column = 1; column <= longest_candidate; ++column) {
__m512i const boundary_vec = _mm512_set1_epi16(
static_cast<short>(static_cast<u16_t>(open + extend * (u16_t)(column - 1))));
_mm512_storeu_si512(previous_row + column * candidate_lanes_k, boundary_vec);
_mm512_storeu_si512(vertical_row + column * candidate_lanes_k,
_mm512_add_epi16(discard_bias_vec, boundary_vec));
}
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
__m256i const query_char_vec = _mm256_set1_epi8(query[query_position - 1]);
__m512i const left_boundary_vec = _mm512_set1_epi16(
static_cast<short>(static_cast<u16_t>(open + extend * (u16_t)(query_position - 1))));
_mm512_storeu_si512(current_row, left_boundary_vec);
__m512i horizontal_vec = _mm512_add_epi16(discard_bias_vec, left_boundary_vec);
for (size_t column = 1; column <= longest_candidate; ++column) {
__m256i const candidate_chars_vec = _mm256_loadu_epi8(candidates.position(column - 1));
__m512i const diagonal_vec = _mm512_loadu_si512(previous_row + (column - 1) * candidate_lanes_k);
__m512i const up_vec = _mm512_loadu_si512(previous_row + column * candidate_lanes_k);
__m512i const left_vec = _mm512_loadu_si512(current_row + (column - 1) * candidate_lanes_k);
__m512i const up_vertical_vec = _mm512_loadu_si512(vertical_row + column * candidate_lanes_k);
__mmask32 const equal_mask = _mm256_cmpeq_epi8_mask(query_char_vec, candidate_chars_vec);
__m512i const substitution_addend_vec = _mm512_mask_blend_epi16(equal_mask, mismatch_vec, match_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi16(diagonal_vec, substitution_addend_vec);
__m512i const vertical_vec = _mm512_min_epu16(_mm512_add_epi16(up_vec, open_vec),
_mm512_add_epi16(up_vertical_vec, extend_vec));
horizontal_vec = _mm512_min_epu16(_mm512_add_epi16(left_vec, open_vec),
_mm512_add_epi16(horizontal_vec, extend_vec));
__m512i const cost_if_gap_vec = _mm512_min_epu16(vertical_vec, horizontal_vec);
__m512i const cell_score_vec = _mm512_min_epu16(cost_if_substitution_vec, cost_if_gap_vec);
_mm512_storeu_si512(vertical_row + column * candidate_lanes_k, vertical_vec);
_mm512_storeu_si512(current_row + column * candidate_lanes_k, cell_score_vec);
}
trivial_swap(previous_row, current_row);
}
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
return status_t::success_k;
}
};
template <sz_similarity_objective_t objective_>
struct candidate_lane_walker<char, u32_t, uniform_substitution_costs_t, affine_gap_costs_t, objective_,
sz_similarity_global_k, sz_cap_icelake_k, 16, void> {
using char_t = char;
using score_t = u32_t;
using substituter_t = uniform_substitution_costs_t;
using gap_costs_t = affine_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 16;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static_assert(objective_ == sz_minimize_distance_k,
"The 32-bit affine candidate-lane kernel only implements distance minimization (Levenshtein).");
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, affine_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const score_row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
scratch_amount_t amount {specs.cache_line_width};
amount += score_row_bytes; amount += score_row_bytes; amount += score_row_bytes; return amount;
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t *vertical_row = current_row + row_stride;
score_t const match_cost = static_cast<score_t>(substituter_.match);
score_t const mismatch_cost = static_cast<score_t>(substituter_.mismatch);
score_t const open = static_cast<score_t>(gap_costs_.open);
score_t const extend = static_cast<score_t>(gap_costs_.extend);
__m512i const match_vec = _mm512_set1_epi32(static_cast<int>(match_cost));
__m512i const mismatch_vec = _mm512_set1_epi32(static_cast<int>(mismatch_cost));
__m512i const open_vec = _mm512_set1_epi32(static_cast<int>(open));
__m512i const extend_vec = _mm512_set1_epi32(static_cast<int>(extend));
__m512i const discard_bias_vec = _mm512_set1_epi32(static_cast<int>(static_cast<u32_t>(open + extend)));
_mm512_storeu_epi32(previous_row, _mm512_setzero_si512());
_mm512_storeu_epi32(vertical_row, discard_bias_vec);
for (size_t column = 1; column <= longest_candidate; ++column) {
__m512i const boundary_vec = _mm512_set1_epi32(
static_cast<int>(static_cast<u32_t>(open + extend * (u32_t)(column - 1))));
_mm512_storeu_epi32(previous_row + column * candidate_lanes_k, boundary_vec);
_mm512_storeu_epi32(vertical_row + column * candidate_lanes_k,
_mm512_add_epi32(discard_bias_vec, boundary_vec));
}
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
__m128i const query_char_vec = _mm_set1_epi8(query[query_position - 1]);
__m512i const left_boundary_vec = _mm512_set1_epi32(
static_cast<int>(static_cast<u32_t>(open + extend * (u32_t)(query_position - 1))));
_mm512_storeu_epi32(current_row, left_boundary_vec);
__m512i horizontal_vec = _mm512_add_epi32(discard_bias_vec, left_boundary_vec);
for (size_t column = 1; column <= longest_candidate; ++column) {
__m128i const candidate_chars_vec = _mm_loadu_epi8(candidates.position(column - 1));
__m512i const diagonal_vec = _mm512_loadu_epi32(previous_row + (column - 1) * candidate_lanes_k);
__m512i const up_vec = _mm512_loadu_epi32(previous_row + column * candidate_lanes_k);
__m512i const left_vec = _mm512_loadu_epi32(current_row + (column - 1) * candidate_lanes_k);
__m512i const up_vertical_vec = _mm512_loadu_epi32(vertical_row + column * candidate_lanes_k);
__mmask16 const equal_mask = _mm_cmpeq_epi8_mask(query_char_vec, candidate_chars_vec);
__m512i const substitution_addend_vec = _mm512_mask_blend_epi32(equal_mask, mismatch_vec, match_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi32(diagonal_vec, substitution_addend_vec);
__m512i const vertical_vec = _mm512_min_epu32(_mm512_add_epi32(up_vec, open_vec),
_mm512_add_epi32(up_vertical_vec, extend_vec));
horizontal_vec = _mm512_min_epu32(_mm512_add_epi32(left_vec, open_vec),
_mm512_add_epi32(horizontal_vec, extend_vec));
__m512i const cost_if_gap_vec = _mm512_min_epu32(vertical_vec, horizontal_vec);
__m512i const cell_score_vec = _mm512_min_epu32(cost_if_substitution_vec, cost_if_gap_vec);
_mm512_storeu_epi32(vertical_row + column * candidate_lanes_k, vertical_vec);
_mm512_storeu_epi32(current_row + column * candidate_lanes_k, cell_score_vec);
}
trivial_swap(previous_row, current_row);
}
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
return status_t::success_k;
}
};
template <typename allocator_type_, sz_capability_t capability_>
struct levenshtein_distances<linear_gap_costs_t, allocator_type_, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using gap_costs_t = linear_gap_costs_t;
using allocator_t = allocator_type_;
using index_t = u32_t;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t candidate_lanes_k = 32; using scoring_t = levenshtein_distance<char, gap_costs_t, capability_k>; using myers_t = levenshtein_distance_myers<char, capability_k>; using lane_walker_narrow_t =
candidate_lane_walker<char, u16_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k,
sz_similarity_global_k, sz_cap_icelake_k, (int)candidate_lanes_k,
void>; using lane_walker_wide_t =
candidate_lane_walker<char, u32_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k,
sz_similarity_global_k, sz_cap_icelake_k, 16,
void>; using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
uniform_substitution_costs_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
safe_vector<std::byte, scratch_allocator_t> score_scratch_ {alloc_};
levenshtein_distances(allocator_t alloc = {}) noexcept : alloc_(alloc) {}
levenshtein_distances(uniform_substitution_costs_t subs, linear_gap_costs_t gaps,
allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc) {}
template <typename queries_type_, typename candidates_type_>
size_t worst_cell_scratch_(queries_type_ const &queries, candidates_type_ const &candidates,
cpu_specs_t const &specs) const noexcept {
size_t longest_query = 0, longest_query_index = 0, longest_candidate = 0, longest_candidate_index = 0;
for (size_t index = 0; index < queries.size(); ++index)
if (to_view(queries[index]).size() > longest_query)
longest_query = to_view(queries[index]).size(), longest_query_index = index;
for (size_t index = 0; index < candidates.size(); ++index)
if (to_view(candidates[index]).size() > longest_candidate)
longest_candidate = to_view(candidates[index]).size(), longest_candidate_index = index;
size_t const max_longer = sz_max_of_two(longest_query, longest_candidate);
size_t const myers_scratch = myers_t::match_masks_bytes_k + max_longer * (size_t)myers_t::lanes_k;
size_t dp_scratch = 0, eightxN_scratch = 0;
size_t const shortest_longest = sz_min_of_two(longest_query, longest_candidate);
if (queries.size() && candidates.size() && shortest_longest > 64) {
size_t const words_bound = (shortest_longest + 63) / 64;
eightxN_scratch = myers_t::match_masks_bytes_k * words_bound;
}
if (queries.size() && candidates.size() && shortest_longest > 512) {
scoring_t dp {substituter_, gap_costs_};
dp_scratch = dp.scratch_space_needed(to_view(queries[longest_query_index]),
to_view(candidates[longest_candidate_index]), specs);
}
return sz_max_of_two(sz_max_of_two(myers_scratch, dp_scratch), eightxN_scratch);
}
#pragma region Cross Product Scoring
template <typename queries_type_, typename candidates_type_, typename results_type_>
SZ_NOINLINE status_t score_range_(queries_type_ const &queries, candidates_type_ const &candidates,
results_type_ &&results, cross_similarities_t cross_kind, size_t cell_begin,
size_t cell_end, scratch_space_t scratch, cpu_specs_t const &specs) noexcept {
using value_t = remove_cvref<decltype(results.data[0])>;
size_t const candidates_count = candidates.size();
scoring_t dp {substituter_, gap_costs_};
auto const destination_for = [&](size_t query_index, size_t candidate_index) noexcept {
cross_cell_destination_t<value_t> destination;
destination.primary = results.data + query_index * results.row_stride + candidate_index;
if (cross_kind == cross_similarities_t::symmetric_k && candidate_index != query_index)
destination.mirror = results.data + candidate_index * results.row_stride + query_index;
return destination;
};
myers_t myers;
cross_cell_writer_t<value_t> writer;
dummy_executor_t dummy;
for (size_t cell_index = cell_begin; cell_index != cell_end;) {
size_t query_index = 0, candidate_index = 0;
cross_cell_to_indices_(cell_index, candidates_count, cross_kind, query_index, candidate_index);
auto const query = to_view(queries[query_index]);
auto const candidate = to_view(candidates[candidate_index]);
size_t const shorter = sz_min_of_two(query.size(), candidate.size());
if (shorter == 0) {
cross_cell_destination_t<value_t> const destination = destination_for(query_index, candidate_index);
cross_cell_writer_t<value_t> {&destination}[0] = sz_max_of_two(query.size(), candidate.size());
++cell_index;
continue;
}
if (shorter <= 64) {
span<char const> shorters[myers_t::lanes_k], longers[myers_t::lanes_k];
span<char const> candidate_views[myers_t::lanes_k]; size_t positions[myers_t::lanes_k];
cross_cell_destination_t<value_t> destinations[myers_t::lanes_k];
size_t const seed_query_index = query_index;
bool const seed_query_shorter = query.size() <= candidate.size();
shorters[0] = seed_query_shorter ? query : candidate;
longers[0] = seed_query_shorter ? candidate : query;
candidate_views[0] = candidate;
positions[0] = 0;
destinations[0] = destination_for(query_index, candidate_index);
index_t group = 1;
++cell_index;
for (; cell_index != cell_end && group != (index_t)myers_t::lanes_k; ++cell_index, ++group) {
size_t next_query_index = 0, next_candidate_index = 0;
cross_cell_to_indices_(cell_index, candidates_count, cross_kind, next_query_index,
next_candidate_index);
if (next_query_index != seed_query_index) break;
auto const next_query = to_view(queries[next_query_index]);
auto const next_candidate = to_view(candidates[next_candidate_index]);
size_t const next_shorter = sz_min_of_two(next_query.size(), next_candidate.size());
if (next_shorter == 0 || next_shorter > 64) break;
bool const next_query_shorter = next_query.size() <= next_candidate.size();
shorters[group] = next_query_shorter ? next_query : next_candidate;
longers[group] = next_query_shorter ? next_candidate : next_query;
candidate_views[group] = next_candidate;
positions[group] = group;
destinations[group] = destination_for(next_query_index, next_candidate_index);
}
writer.destinations = destinations;
auto const query_view = to_view(queries[seed_query_index]);
status_t const status =
query_view.size() <= 64
? myers.distances_8x64_shared_query_(
query_view, span<span<char const> const> {candidate_views, group}, writer, scratch)
: myers.distances_8x64_(
lane_pairs_view<char> {{shorters, group}, {longers, group}, {positions, group}}, writer,
scratch);
if (status != status_t::success_k) return status;
continue;
}
size_t const seed_bucket = (shorter + 63) / 64;
span<char const> group_shorters[myers_t::lanes_k], group_longers[myers_t::lanes_k];
size_t group_positions[myers_t::lanes_k];
cross_cell_destination_t<value_t> group_destinations[myers_t::lanes_k];
bool const seed_query_shorter = query.size() <= candidate.size();
group_shorters[0] = seed_query_shorter ? query : candidate;
group_longers[0] = seed_query_shorter ? candidate : query;
group_positions[0] = 0;
group_destinations[0] = destination_for(query_index, candidate_index);
index_t group = 1;
++cell_index;
for (; cell_index != cell_end && group != (index_t)myers_t::lanes_k; ++cell_index, ++group) {
size_t next_query_index = 0, next_candidate_index = 0;
cross_cell_to_indices_(cell_index, candidates_count, cross_kind, next_query_index,
next_candidate_index);
auto const next_query = to_view(queries[next_query_index]);
auto const next_candidate = to_view(candidates[next_candidate_index]);
size_t const next_shorter = sz_min_of_two(next_query.size(), next_candidate.size());
if (next_shorter <= 64 || (next_shorter + 63) / 64 != seed_bucket) break;
bool const next_query_shorter = next_query.size() <= next_candidate.size();
group_shorters[group] = next_query_shorter ? next_query : next_candidate;
group_longers[group] = next_query_shorter ? next_candidate : next_query;
group_positions[group] = group;
group_destinations[group] = destination_for(next_query_index, next_candidate_index);
}
cross_cell_writer_t<value_t> group_writer;
group_writer.destinations = group_destinations;
lane_pairs_view<char> const group_pairs {{group_shorters, group},
{group_longers, group},
{group_positions, group}};
if (seed_bucket > 8 && group < 2) {
size_t result_score = 0;
if (status_t const lone_status = dp(group_shorters[0], group_longers[0], result_score, scratch, dummy,
specs);
lone_status != status_t::success_k)
return lone_status;
cross_cell_writer_t<value_t> {&group_destinations[0]}[0] = result_score;
continue;
}
status_t status = dispatch_word_bucket_<2, 8>(
seed_bucket,
[&](auto bucket) {
return myers.template distances_8x_multiword_<bucket.value>(group_pairs, group_writer, scratch);
},
[&] { return myers.distances_8x_multiword_large_(group_pairs, group_writer, scratch); });
if (status == status_t::success_k) continue;
for (index_t lane = 0; lane != group; ++lane) {
size_t lane_score = 0;
if (status_t const lane_status = dp(group_shorters[lane], group_longers[lane], lane_score, scratch,
dummy, specs);
lane_status != status_t::success_k)
return lane_status;
cross_cell_writer_t<value_t> {&group_destinations[lane]}[0] = lane_score;
}
}
return status_t::success_k;
}
template <typename queries_type_, typename candidates_type_, typename results_type_, typename executor_type_>
SZ_NOINLINE status_t score_parallel_(queries_type_ const &queries, candidates_type_ const &candidates,
results_type_ &&results, cross_similarities_t cross_kind,
executor_type_ &&executor, cpu_specs_t const &specs) noexcept {
size_t const cells_count = cross_live_cells_count_(queries.size(), candidates.size(), cross_kind);
size_t const worker_scratch = worst_cell_scratch_(queries, candidates, specs);
size_t const workers = sz_max_of_two(executor.threads_count(), (size_t)1);
if (status_t status = score_scratch_.try_resize(worker_scratch * workers); status != status_t::success_k)
return status;
using prong_t = typename remove_cvref<executor_type_>::prong_t;
schedule_batches_t const schedule_batches = schedule_batches_(cells_count, workers,
lockstep_lanes_of_<myers_t>::value);
atomic_status_t status;
executor.for_n_dynamic(schedule_batches.batches_count, [&](prong_t prong) noexcept {
if (status != status_t::success_k) return;
scratch_space_t slice =
scratch_space_t(score_scratch_).subspan(prong.thread * worker_scratch, worker_scratch);
status = score_range_(queries, candidates, results, cross_kind, schedule_batches.batch_begin(prong.task),
schedule_batches.batch_end(prong.task, cells_count), slice, specs);
});
return status;
}
#pragma endregion Cross Product Scoring
#pragma region Public Cross Product Overloads
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
if (!is_unit_cost(substituter_, gap_costs_)) {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, queries, candidates, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, queries, candidates, results, cross_similarities_t::all_pairs_k, 0,
cross_live_cells_count_(queries.size(), candidates.size(), cross_similarities_t::all_pairs_k),
scratch_space_t(score_scratch_), specs);
}
if (status_t status = score_scratch_.try_resize(worst_cell_scratch_(queries, candidates, specs));
status != status_t::success_k)
return status;
return score_range_(
queries, candidates, results, cross_similarities_t::all_pairs_k, 0,
cross_live_cells_count_(queries.size(), candidates.size(), cross_similarities_t::all_pairs_k),
scratch_space_t(score_scratch_), specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
if (!is_unit_cost(substituter_, gap_costs_)) {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, queries, candidates, results,
cross_similarities_t::all_pairs_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
return score_parallel_(queries, candidates, results, cross_similarities_t::all_pairs_k,
std::forward<executor_type_>(executor), specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
if (!is_unit_cost(substituter_, gap_costs_)) {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, sequences, sequences, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, sequences, sequences, results, cross_similarities_t::symmetric_k, 0,
cross_live_cells_count_(sequences.size(), sequences.size(), cross_similarities_t::symmetric_k),
scratch_space_t(score_scratch_), specs);
}
if (status_t status = score_scratch_.try_resize(worst_cell_scratch_(sequences, sequences, specs));
status != status_t::success_k)
return status;
return score_range_(
sequences, sequences, results, cross_similarities_t::symmetric_k, 0,
cross_live_cells_count_(sequences.size(), sequences.size(), cross_similarities_t::symmetric_k),
scratch_space_t(score_scratch_), specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
if (!is_unit_cost(substituter_, gap_costs_)) {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, sequences, sequences, results,
cross_similarities_t::symmetric_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
return score_parallel_(sequences, sequences, results, cross_similarities_t::symmetric_k,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Public Cross Product Overloads
};
template <typename allocator_type_, sz_capability_t capability_>
struct levenshtein_distances<affine_gap_costs_t, allocator_type_, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using gap_costs_t = affine_gap_costs_t;
using allocator_t = allocator_type_;
using index_t = u32_t;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t candidate_lanes_k = 32;
using scoring_t = levenshtein_distance<char, affine_gap_costs_t, sz_cap_serial_k>; using lane_walker_narrow_t =
candidate_lane_walker<char, u16_t, uniform_substitution_costs_t, affine_gap_costs_t, sz_minimize_distance_k,
sz_similarity_global_k, sz_cap_icelake_k, (int)candidate_lanes_k,
void>; using lane_walker_wide_t =
candidate_lane_walker<char, u32_t, uniform_substitution_costs_t, affine_gap_costs_t, sz_minimize_distance_k,
sz_similarity_global_k, sz_cap_icelake_k, 16,
void>;
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
using linear_fallback_t = levenshtein_distances<linear_gap_costs_t, allocator_t, capability_k>;
uniform_substitution_costs_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
safe_vector<std::byte, scratch_allocator_t> score_scratch_ {alloc_};
linear_fallback_t linear_fallback_;
levenshtein_distances(allocator_t alloc = {}) noexcept : alloc_(alloc), linear_fallback_(alloc) {}
levenshtein_distances(uniform_substitution_costs_t subs, affine_gap_costs_t gaps,
allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc),
linear_fallback_(subs, linear_gap_costs_t {gaps.open}, alloc) {}
#pragma region Public Cross Product Overloads
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
return cross_(queries, candidates, results, cross_similarities_t::all_pairs_k, specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
return cross_parallel_(queries, candidates, results, cross_similarities_t::all_pairs_k,
std::forward<executor_type_>(executor), specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
return cross_(sequences, sequences, results, cross_similarities_t::symmetric_k, specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
return cross_parallel_(sequences, sequences, results, cross_similarities_t::symmetric_k,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Public Cross Product Overloads
#pragma region Cross Product Dispatch
private:
template <typename queries_type_, typename candidates_type_, typename value_type_>
status_t cross_(queries_type_ const &queries, candidates_type_ const &candidates, strided_rows<value_type_> results,
cross_similarities_t cross_kind, cpu_specs_t const &specs) noexcept {
if (gap_costs_.is_linear()) return linear_fallback_(queries, candidates, results, specs);
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, queries, candidates, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, queries, candidates, results, cross_kind, 0,
cross_live_cells_count_(queries.size(), candidates.size(), cross_kind), scratch_space_t(score_scratch_),
specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
status_t cross_parallel_(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cross_similarities_t cross_kind,
executor_type_ &&executor, cpu_specs_t const &specs) noexcept {
if (gap_costs_.is_linear())
return linear_fallback_(queries, candidates, results, std::forward<executor_type_>(executor), specs);
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, queries, candidates, results, cross_kind,
score_scratch_, std::forward<executor_type_>(executor), specs);
}
#pragma endregion Cross Product Dispatch
};
template <sz_capability_t capability_>
struct levenshtein_distance_utf8<linear_gap_costs_t, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_capability_t capability_k = capability_;
static constexpr sz_capability_t capability_wout_simd_k = (sz_capability_t)(capability_k & ~sz_cap_icelake_k);
using diagonal_u8_t = diagonal_walker<rune_t, u8_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using diagonal_u16_t = diagonal_walker<rune_t, u16_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_k>;
using diagonal_u32_t = diagonal_walker<rune_t, u32_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_wout_simd_k>;
using diagonal_u64_t = diagonal_walker<rune_t, u64_t, uniform_substitution_costs_t, linear_gap_costs_t,
sz_minimize_distance_k, sz_similarity_global_k, capability_wout_simd_k>;
using ascii_fallback_t = levenshtein_distance<char_t, linear_gap_costs_t, capability_k>;
uniform_substitution_costs_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
levenshtein_distance_utf8() noexcept {}
levenshtein_distance_utf8(uniform_substitution_costs_t subs, linear_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(span<char_t const> first, span<char_t const> second,
cpu_specs_t const &specs) const noexcept {
size_t const first_unpacking_ceiling = round_up_to_multiple(sizeof(rune_t) * first.size(),
specs.cache_line_width);
size_t const second_unpacking_ceiling = round_up_to_multiple(sizeof(rune_t) * second.size(),
specs.cache_line_width);
return ascii_fallback_t {substituter_, gap_costs_}.scratch_space_needed(first, second, specs) +
first_unpacking_ceiling + second_unpacking_ceiling;
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, size_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
if (text_is_ascii_<sz_find_byteset_icelake>(first) && text_is_ascii_<sz_find_byteset_icelake>(second))
return ascii_fallback_t {substituter_, gap_costs_}(first, second, result_ref, scratch_space, executor,
specs);
size_t const first_unpacking_ceiling = round_up_to_multiple(sizeof(rune_t) * first.size(),
specs.cache_line_width);
size_t const second_unpacking_ceiling = round_up_to_multiple(sizeof(rune_t) * second.size(),
specs.cache_line_width);
size_t const transcode_bytes = first_unpacking_ceiling + second_unpacking_ceiling;
if (scratch_space.size() < transcode_bytes) return status_t::bad_alloc_k;
rune_t *const first_data_utf32 = reinterpret_cast<rune_t *>(scratch_space.data());
rune_t *const second_data_utf32 = reinterpret_cast<rune_t *>(scratch_space.data() + first_unpacking_ceiling);
scratch_space_t const walker_scratch = scratch_space.subspan(transcode_bytes,
scratch_space.size() - transcode_bytes);
rune_length_t rune_length;
size_t first_length_utf32 = 0, second_length_utf32 = 0;
for (size_t progress_utf8 = 0; progress_utf8 < first.size();
progress_utf8 += rune_length, ++first_length_utf32) {
rune_length = sz_rune_decode_unchecked(first.data() + progress_utf8, first_data_utf32 + first_length_utf32);
if (rune_length == sz_rune_invalid_k) return status_t::invalid_utf8_k;
}
for (size_t progress_utf8 = 0; progress_utf8 < second.size();
progress_utf8 += rune_length, ++second_length_utf32) {
rune_length = sz_rune_decode_unchecked(second.data() + progress_utf8,
second_data_utf32 + second_length_utf32);
if (rune_length == sz_rune_invalid_k) return status_t::invalid_utf8_k;
}
using diagonal_memory_requirements_t = diagonal_memory_requirements<size_t>;
diagonal_memory_requirements_t requirements( first_length_utf32, second_length_utf32, gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(rune_t), specs.cache_line_width);
span<rune_t const> const first_utf32 {first_data_utf32, first_length_utf32};
span<rune_t const> const second_utf32 {second_data_utf32, second_length_utf32};
if (requirements.bytes_per_cell <= 1) {
u8_t result_u8;
status_t status = diagonal_u8_t {substituter_, gap_costs_}(first_utf32, second_utf32, result_u8,
walker_scratch, executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u8;
}
else if (requirements.bytes_per_cell == 2) {
u16_t result_u16;
status_t status = diagonal_u16_t {substituter_, gap_costs_}(first_utf32, second_utf32, result_u16,
walker_scratch, executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u16;
}
else if (requirements.bytes_per_cell == 4) {
u32_t result_u32;
status_t status = diagonal_u32_t {substituter_, gap_costs_}(first_utf32, second_utf32, result_u32,
walker_scratch, executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u32;
}
else if (requirements.bytes_per_cell == 8) {
u64_t result_u64;
status_t status = diagonal_u64_t {substituter_, gap_costs_}(first_utf32, second_utf32, result_u64,
walker_scratch, executor, specs);
if (status != status_t::success_k) return status;
result_ref = result_u64;
}
return status_t::success_k;
}
};
template <sz_similarity_objective_t objective_>
struct candidate_lane_walker<rune_t, u16_t, uniform_substitution_costs_t, linear_gap_costs_t, objective_,
sz_similarity_global_k, sz_cap_icelake_k, 32, void> {
using char_t = rune_t;
using score_t = u16_t;
using substituter_t = uniform_substitution_costs_t;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 32;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static_assert(objective_ == sz_minimize_distance_k,
"The 16-bit rune candidate-lane kernel only implements distance minimization (Levenshtein).");
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, linear_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
scratch_amount_t amount {specs.cache_line_width};
amount += row_bytes; amount += row_bytes; return amount;
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t const match_cost = static_cast<score_t>(substituter_.match);
score_t const mismatch_cost = static_cast<score_t>(substituter_.mismatch);
score_t const gap_cost = static_cast<score_t>(gap_costs_.open_or_extend);
__m512i const match_vec = _mm512_set1_epi16(static_cast<short>(match_cost));
__m512i const mismatch_vec = _mm512_set1_epi16(static_cast<short>(mismatch_cost));
__m512i const gap_vec = _mm512_set1_epi16(static_cast<short>(gap_cost));
for (size_t column = 0; column <= longest_candidate; ++column)
_mm512_storeu_si512(previous_row + column * candidate_lanes_k,
_mm512_set1_epi16(static_cast<short>(static_cast<u16_t>(column * gap_cost))));
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
__m512i const query_rune_vec = _mm512_set1_epi32(static_cast<int>(query[query_position - 1]));
_mm512_storeu_si512(current_row,
_mm512_set1_epi16(static_cast<short>(static_cast<u16_t>(query_position * gap_cost))));
for (size_t column = 1; column <= longest_candidate; ++column) {
__m512i const candidate_runes_low_vec = _mm512_loadu_si512(candidates.position(column - 1));
__m512i const candidate_runes_high_vec = _mm512_loadu_si512(candidates.position(column - 1) +
candidate_lanes_k / 2);
__m512i const diagonal_vec = _mm512_loadu_si512(previous_row + (column - 1) * candidate_lanes_k);
__m512i const deletion_source_vec = _mm512_loadu_si512(previous_row + column * candidate_lanes_k);
__m512i const insertion_source_vec = _mm512_loadu_si512(current_row + (column - 1) * candidate_lanes_k);
__mmask16 const equal_low_mask = _mm512_cmpeq_epi32_mask(query_rune_vec, candidate_runes_low_vec);
__mmask16 const equal_high_mask = _mm512_cmpeq_epi32_mask(query_rune_vec, candidate_runes_high_vec);
__mmask32 const equal_mask = static_cast<__mmask32>(equal_low_mask) |
(static_cast<__mmask32>(equal_high_mask) << 16);
__m512i const mismatch_addend_vec = _mm512_mask_blend_epi16(equal_mask, mismatch_vec, match_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi16(diagonal_vec, mismatch_addend_vec);
__m512i const cost_if_deletion_vec = _mm512_add_epi16(deletion_source_vec, gap_vec);
__m512i const cost_if_insertion_vec = _mm512_add_epi16(insertion_source_vec, gap_vec);
__m512i const cell_score_vec = _mm512_min_epu16(
cost_if_substitution_vec, _mm512_min_epu16(cost_if_deletion_vec, cost_if_insertion_vec));
_mm512_storeu_si512(current_row + column * candidate_lanes_k, cell_score_vec);
}
trivial_swap(previous_row, current_row);
}
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
return status_t::success_k;
}
};
template <sz_similarity_objective_t objective_>
struct candidate_lane_walker<rune_t, u32_t, uniform_substitution_costs_t, linear_gap_costs_t, objective_,
sz_similarity_global_k, sz_cap_icelake_k, 16, void> {
using char_t = rune_t;
using score_t = u32_t;
using substituter_t = uniform_substitution_costs_t;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 16;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static_assert(objective_ == sz_minimize_distance_k,
"The 32-bit rune candidate-lane kernel only implements distance minimization (Levenshtein).");
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, linear_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
scratch_amount_t amount {specs.cache_line_width};
amount += row_bytes; amount += row_bytes; return amount;
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t const match_cost = static_cast<score_t>(substituter_.match);
score_t const mismatch_cost = static_cast<score_t>(substituter_.mismatch);
score_t const gap_cost = static_cast<score_t>(gap_costs_.open_or_extend);
__m512i const match_vec = _mm512_set1_epi32(static_cast<int>(match_cost));
__m512i const mismatch_vec = _mm512_set1_epi32(static_cast<int>(mismatch_cost));
__m512i const gap_vec = _mm512_set1_epi32(static_cast<int>(gap_cost));
for (size_t column = 0; column <= longest_candidate; ++column)
_mm512_storeu_epi32(previous_row + column * candidate_lanes_k,
_mm512_set1_epi32(static_cast<int>(static_cast<u32_t>(column * gap_cost))));
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
__m512i const query_rune_vec = _mm512_set1_epi32(static_cast<int>(query[query_position - 1]));
_mm512_storeu_epi32(current_row,
_mm512_set1_epi32(static_cast<int>(static_cast<u32_t>(query_position * gap_cost))));
for (size_t column = 1; column <= longest_candidate; ++column) {
__m512i const candidate_runes_vec = _mm512_loadu_si512(candidates.position(column - 1));
__m512i const diagonal_vec = _mm512_loadu_epi32(previous_row + (column - 1) * candidate_lanes_k);
__m512i const deletion_source_vec = _mm512_loadu_epi32(previous_row + column * candidate_lanes_k);
__m512i const insertion_source_vec = _mm512_loadu_epi32(current_row + (column - 1) * candidate_lanes_k);
__mmask16 const equal_mask = _mm512_cmpeq_epi32_mask(query_rune_vec, candidate_runes_vec);
__m512i const mismatch_addend_vec = _mm512_mask_blend_epi32(equal_mask, mismatch_vec, match_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi32(diagonal_vec, mismatch_addend_vec);
__m512i const cost_if_deletion_vec = _mm512_add_epi32(deletion_source_vec, gap_vec);
__m512i const cost_if_insertion_vec = _mm512_add_epi32(insertion_source_vec, gap_vec);
__m512i const cell_score_vec = _mm512_min_epu32(
cost_if_substitution_vec, _mm512_min_epu32(cost_if_deletion_vec, cost_if_insertion_vec));
_mm512_storeu_epi32(current_row + column * candidate_lanes_k, cell_score_vec);
}
trivial_swap(previous_row, current_row);
}
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
return status_t::success_k;
}
};
template <sz_similarity_objective_t objective_>
struct candidate_lane_walker<rune_t, u16_t, uniform_substitution_costs_t, affine_gap_costs_t, objective_,
sz_similarity_global_k, sz_cap_icelake_k, 32, void> {
using char_t = rune_t;
using score_t = u16_t;
using substituter_t = uniform_substitution_costs_t;
using gap_costs_t = affine_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 32;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static_assert(objective_ == sz_minimize_distance_k,
"The 16-bit affine rune candidate-lane kernel only implements distance minimization (Levenshtein).");
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, affine_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const score_row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
scratch_amount_t amount {specs.cache_line_width};
amount += score_row_bytes; amount += score_row_bytes; amount += score_row_bytes; return amount;
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t *vertical_row = current_row + row_stride;
score_t const match_cost = static_cast<score_t>(substituter_.match);
score_t const mismatch_cost = static_cast<score_t>(substituter_.mismatch);
score_t const open = static_cast<score_t>(gap_costs_.open);
score_t const extend = static_cast<score_t>(gap_costs_.extend);
__m512i const match_vec = _mm512_set1_epi16(static_cast<short>(match_cost));
__m512i const mismatch_vec = _mm512_set1_epi16(static_cast<short>(mismatch_cost));
__m512i const open_vec = _mm512_set1_epi16(static_cast<short>(open));
__m512i const extend_vec = _mm512_set1_epi16(static_cast<short>(extend));
__m512i const discard_bias_vec = _mm512_set1_epi16(static_cast<short>(static_cast<u16_t>(open + extend)));
_mm512_storeu_si512(previous_row, _mm512_setzero_si512());
_mm512_storeu_si512(vertical_row, discard_bias_vec);
for (size_t column = 1; column <= longest_candidate; ++column) {
__m512i const boundary_vec = _mm512_set1_epi16(
static_cast<short>(static_cast<u16_t>(open + extend * (u16_t)(column - 1))));
_mm512_storeu_si512(previous_row + column * candidate_lanes_k, boundary_vec);
_mm512_storeu_si512(vertical_row + column * candidate_lanes_k,
_mm512_add_epi16(discard_bias_vec, boundary_vec));
}
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
__m512i const query_rune_vec = _mm512_set1_epi32(static_cast<int>(query[query_position - 1]));
__m512i const left_boundary_vec = _mm512_set1_epi16(
static_cast<short>(static_cast<u16_t>(open + extend * (u16_t)(query_position - 1))));
_mm512_storeu_si512(current_row, left_boundary_vec);
__m512i horizontal_vec = _mm512_add_epi16(discard_bias_vec, left_boundary_vec);
for (size_t column = 1; column <= longest_candidate; ++column) {
__m512i const candidate_runes_low_vec = _mm512_loadu_si512(candidates.position(column - 1));
__m512i const candidate_runes_high_vec = _mm512_loadu_si512(candidates.position(column - 1) +
candidate_lanes_k / 2);
__m512i const diagonal_vec = _mm512_loadu_si512(previous_row + (column - 1) * candidate_lanes_k);
__m512i const up_vec = _mm512_loadu_si512(previous_row + column * candidate_lanes_k);
__m512i const left_vec = _mm512_loadu_si512(current_row + (column - 1) * candidate_lanes_k);
__m512i const up_vertical_vec = _mm512_loadu_si512(vertical_row + column * candidate_lanes_k);
__mmask16 const equal_low_mask = _mm512_cmpeq_epi32_mask(query_rune_vec, candidate_runes_low_vec);
__mmask16 const equal_high_mask = _mm512_cmpeq_epi32_mask(query_rune_vec, candidate_runes_high_vec);
__mmask32 const equal_mask = static_cast<__mmask32>(equal_low_mask) |
(static_cast<__mmask32>(equal_high_mask) << 16);
__m512i const substitution_addend_vec = _mm512_mask_blend_epi16(equal_mask, mismatch_vec, match_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi16(diagonal_vec, substitution_addend_vec);
__m512i const vertical_vec = _mm512_min_epu16(_mm512_add_epi16(up_vec, open_vec),
_mm512_add_epi16(up_vertical_vec, extend_vec));
horizontal_vec = _mm512_min_epu16(_mm512_add_epi16(left_vec, open_vec),
_mm512_add_epi16(horizontal_vec, extend_vec));
__m512i const cost_if_gap_vec = _mm512_min_epu16(vertical_vec, horizontal_vec);
__m512i const cell_score_vec = _mm512_min_epu16(cost_if_substitution_vec, cost_if_gap_vec);
_mm512_storeu_si512(vertical_row + column * candidate_lanes_k, vertical_vec);
_mm512_storeu_si512(current_row + column * candidate_lanes_k, cell_score_vec);
}
trivial_swap(previous_row, current_row);
}
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
return status_t::success_k;
}
};
template <sz_similarity_objective_t objective_>
struct candidate_lane_walker<rune_t, u32_t, uniform_substitution_costs_t, affine_gap_costs_t, objective_,
sz_similarity_global_k, sz_cap_icelake_k, 16, void> {
using char_t = rune_t;
using score_t = u32_t;
using substituter_t = uniform_substitution_costs_t;
using gap_costs_t = affine_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 16;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static_assert(objective_ == sz_minimize_distance_k,
"The 32-bit affine rune candidate-lane kernel only implements distance minimization (Levenshtein).");
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, affine_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const score_row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
scratch_amount_t amount {specs.cache_line_width};
amount += score_row_bytes; amount += score_row_bytes; amount += score_row_bytes; return amount;
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t *vertical_row = current_row + row_stride;
score_t const match_cost = static_cast<score_t>(substituter_.match);
score_t const mismatch_cost = static_cast<score_t>(substituter_.mismatch);
score_t const open = static_cast<score_t>(gap_costs_.open);
score_t const extend = static_cast<score_t>(gap_costs_.extend);
__m512i const match_vec = _mm512_set1_epi32(static_cast<int>(match_cost));
__m512i const mismatch_vec = _mm512_set1_epi32(static_cast<int>(mismatch_cost));
__m512i const open_vec = _mm512_set1_epi32(static_cast<int>(open));
__m512i const extend_vec = _mm512_set1_epi32(static_cast<int>(extend));
__m512i const discard_bias_vec = _mm512_set1_epi32(static_cast<int>(static_cast<u32_t>(open + extend)));
_mm512_storeu_epi32(previous_row, _mm512_setzero_si512());
_mm512_storeu_epi32(vertical_row, discard_bias_vec);
for (size_t column = 1; column <= longest_candidate; ++column) {
__m512i const boundary_vec = _mm512_set1_epi32(
static_cast<int>(static_cast<u32_t>(open + extend * (u32_t)(column - 1))));
_mm512_storeu_epi32(previous_row + column * candidate_lanes_k, boundary_vec);
_mm512_storeu_epi32(vertical_row + column * candidate_lanes_k,
_mm512_add_epi32(discard_bias_vec, boundary_vec));
}
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
__m512i const query_rune_vec = _mm512_set1_epi32(static_cast<int>(query[query_position - 1]));
__m512i const left_boundary_vec = _mm512_set1_epi32(
static_cast<int>(static_cast<u32_t>(open + extend * (u32_t)(query_position - 1))));
_mm512_storeu_epi32(current_row, left_boundary_vec);
__m512i horizontal_vec = _mm512_add_epi32(discard_bias_vec, left_boundary_vec);
for (size_t column = 1; column <= longest_candidate; ++column) {
__m512i const candidate_runes_vec = _mm512_loadu_si512(candidates.position(column - 1));
__m512i const diagonal_vec = _mm512_loadu_epi32(previous_row + (column - 1) * candidate_lanes_k);
__m512i const up_vec = _mm512_loadu_epi32(previous_row + column * candidate_lanes_k);
__m512i const left_vec = _mm512_loadu_epi32(current_row + (column - 1) * candidate_lanes_k);
__m512i const up_vertical_vec = _mm512_loadu_epi32(vertical_row + column * candidate_lanes_k);
__mmask16 const equal_mask = _mm512_cmpeq_epi32_mask(query_rune_vec, candidate_runes_vec);
__m512i const substitution_addend_vec = _mm512_mask_blend_epi32(equal_mask, mismatch_vec, match_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi32(diagonal_vec, substitution_addend_vec);
__m512i const vertical_vec = _mm512_min_epu32(_mm512_add_epi32(up_vec, open_vec),
_mm512_add_epi32(up_vertical_vec, extend_vec));
horizontal_vec = _mm512_min_epu32(_mm512_add_epi32(left_vec, open_vec),
_mm512_add_epi32(horizontal_vec, extend_vec));
__m512i const cost_if_gap_vec = _mm512_min_epu32(vertical_vec, horizontal_vec);
__m512i const cell_score_vec = _mm512_min_epu32(cost_if_substitution_vec, cost_if_gap_vec);
_mm512_storeu_epi32(vertical_row + column * candidate_lanes_k, vertical_vec);
_mm512_storeu_epi32(current_row + column * candidate_lanes_k, cell_score_vec);
}
trivial_swap(previous_row, current_row);
}
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
return status_t::success_k;
}
};
template <typename allocator_type_, sz_capability_t capability_>
struct levenshtein_distances_utf8<linear_gap_costs_t, allocator_type_, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using gap_costs_t = linear_gap_costs_t;
using allocator_t = allocator_type_;
using index_t = u32_t;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t candidate_lanes_k = 32;
using scoring_t = levenshtein_distance_utf8<gap_costs_t, sz_cap_serial_k>; using myers_t = levenshtein_distance_myers<rune_t, capability_k>; using lane_walker_t =
candidate_lane_walker<rune_t, u16_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k,
sz_similarity_global_k, sz_cap_icelake_k, (int)candidate_lanes_k, void>;
using lane_walker_narrow_t = lane_walker_t; using lane_walker_wide_t =
candidate_lane_walker<rune_t, u32_t, uniform_substitution_costs_t, gap_costs_t, sz_minimize_distance_k,
sz_similarity_global_k, sz_cap_icelake_k, 16, void>; using rune_scoring_t = levenshtein_distance<rune_t, gap_costs_t, sz_cap_serial_k>; static constexpr index_t myers_lanes_k = myers_t::lanes_k;
static constexpr size_t myers_max_shorter_runes_k = 64 * 64;
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
using rune_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<rune_t>;
using rune_view_allocator_t =
typename std::allocator_traits<allocator_t>::template rebind_alloc<span<rune_t const>>;
using bytes_fallback_t = levenshtein_distances<linear_gap_costs_t, allocator_t, capability_k>;
uniform_substitution_costs_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
safe_vector<std::byte, scratch_allocator_t> score_scratch_ {alloc_};
safe_vector<rune_t, rune_allocator_t> query_arena_ {alloc_};
safe_vector<rune_t, rune_allocator_t> candidate_arena_ {alloc_};
safe_vector<span<rune_t const>, rune_view_allocator_t> query_runes_ {alloc_};
safe_vector<span<rune_t const>, rune_view_allocator_t> candidate_runes_ {alloc_};
bytes_fallback_t bytes_fallback_;
levenshtein_distances_utf8(allocator_t alloc = {}) noexcept : alloc_(alloc), bytes_fallback_(alloc) {}
levenshtein_distances_utf8(uniform_substitution_costs_t subs, linear_gap_costs_t gaps,
allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc), bytes_fallback_(subs, gaps, alloc) {}
template <typename sequences_type_>
bool transcode_views_(sequences_type_ const &sequences, safe_vector<rune_t, rune_allocator_t> &arena,
safe_vector<span<rune_t const>, rune_view_allocator_t> &views) const noexcept {
size_t total_bytes = 0;
for (size_t index = 0; index < sequences.size(); ++index) total_bytes += to_view(sequences[index]).size();
if (arena.try_reserve(total_bytes) != status_t::success_k) return false;
if (arena.try_resize(0) != status_t::success_k) return false;
if (views.try_resize(sequences.size()) != status_t::success_k) return false;
for (size_t index = 0; index < sequences.size(); ++index) {
auto const bytes = to_view(sequences[index]);
size_t const rune_begin = arena.size();
rune_length_t rune_length;
for (size_t progress = 0; progress < bytes.size(); progress += rune_length) {
rune_t rune;
rune_length = sz_rune_decode_unchecked(bytes.data() + progress, &rune);
if (rune_length == sz_rune_invalid_k) return false;
if (arena.try_resize(arena.size() + 1) != status_t::success_k) return false;
arena[arena.size() - 1] = rune;
}
views[index] = span<rune_t const> {arena.data() + rune_begin, arena.size() - rune_begin};
}
return true;
}
size_t query_runes_bytes_(size_t byte_length, cpu_specs_t const &specs) const noexcept {
return round_up_to_multiple(sizeof(rune_t) * byte_length, specs.cache_line_width);
}
template <typename queries_type_, typename candidates_type_>
size_t worst_cell_scratch_(queries_type_ const &queries, candidates_type_ const &candidates,
cpu_specs_t const &specs) const noexcept {
size_t longest_query = 0, longest_query_index = 0, longest_candidate = 0, longest_candidate_index = 0;
for (size_t index = 0; index < queries.size(); ++index)
if (to_view(queries[index]).size() > longest_query)
longest_query = to_view(queries[index]).size(), longest_query_index = index;
for (size_t index = 0; index < candidates.size(); ++index)
if (to_view(candidates[index]).size() > longest_candidate)
longest_candidate = to_view(candidates[index]).size(), longest_candidate_index = index;
lane_walker_t lane_walker {substituter_, gap_costs_};
size_t const query_rune_bytes = query_runes_bytes_(longest_query, specs);
size_t const transpose_bytes = candidate_lanes_k * longest_candidate * sizeof(rune_t);
size_t const walker_scratch = longest_candidate ? lane_walker.scratch_space_needed(longest_candidate, specs)
: 0;
size_t const myers_transcode_bytes = round_up_to_multiple(
(size_t)myers_lanes_k * (longest_query + longest_candidate) * sizeof(rune_t), specs.cache_line_width);
size_t const myers_match_masks_bytes = myers_t::scratch_bytes_for(
sz_min_of_two(sz_min_of_two(longest_query, longest_candidate), myers_max_shorter_runes_k));
size_t dp_scratch = 0;
if (queries.size() && candidates.size()) {
scoring_t dp {substituter_, gap_costs_};
dp_scratch = dp.scratch_space_needed(to_view(queries[longest_query_index]),
to_view(candidates[longest_candidate_index]), specs);
}
size_t const lane_walker_path = query_rune_bytes + transpose_bytes + walker_scratch;
size_t const myers_path = myers_transcode_bytes + myers_match_masks_bytes;
return sz_max_of_two(sz_max_of_two(lane_walker_path, myers_path), dp_scratch);
}
#pragma region Cross Product Scoring
template <typename queries_type_, typename candidates_type_, typename results_type_>
SZ_NOINLINE status_t score_range_(queries_type_ const &queries, candidates_type_ const &candidates,
results_type_ &&results, cross_similarities_t cross_kind, size_t cell_begin,
size_t cell_end, scratch_space_t scratch, cpu_specs_t const &specs) noexcept {
using value_t = remove_cvref<decltype(results.data[0])>;
size_t const candidates_count = candidates.size();
size_t longest_query = 0, longest_candidate = 0;
for (size_t cell_index = cell_begin; cell_index != cell_end; ++cell_index) {
size_t query_index = 0, candidate_index = 0;
cross_cell_to_indices_(cell_index, candidates_count, cross_kind, query_index, candidate_index);
longest_query = sz_max_of_two(longest_query, to_view(queries[query_index]).size());
longest_candidate = sz_max_of_two(longest_candidate, to_view(candidates[candidate_index]).size());
}
size_t const transcode_bytes = round_up_to_multiple(
(size_t)myers_lanes_k * (longest_query + longest_candidate) * sizeof(rune_t), specs.cache_line_width);
rune_t *const rune_arena = reinterpret_cast<rune_t *>(scratch.data());
size_t const rune_arena_runes = transcode_bytes / sizeof(rune_t);
scratch_space_t const match_masks_scratch = transcode_bytes <= scratch.size()
? scratch.subspan(transcode_bytes,
scratch.size() - transcode_bytes)
: scratch_space_t {};
scratch_space_t const dp_scratch_space = scratch;
scoring_t dp {substituter_, gap_costs_};
dummy_executor_t dummy;
auto const destination_for = [&](size_t query_index, size_t candidate_index) noexcept {
cross_cell_destination_t<value_t> destination;
destination.primary = results.data + query_index * results.row_stride + candidate_index;
if (cross_kind == cross_similarities_t::symmetric_k && candidate_index != query_index)
destination.mirror = results.data + candidate_index * results.row_stride + query_index;
return destination;
};
auto const scatter = [&](cross_cell_destination_t<value_t> const &destination, size_t score) noexcept {
*destination.primary = static_cast<value_t>(score);
if (destination.mirror) *destination.mirror = static_cast<value_t>(score);
};
myers_t myers;
auto const transcode_cell = [&](span<char_t const> query, span<char_t const> candidate, size_t &arena_used,
span<rune_t const> &shorter_runes,
span<rune_t const> &longer_runes) noexcept -> bool {
size_t const query_offset = arena_used;
size_t query_runes_count = 0;
rune_length_t rune_length;
for (size_t progress = 0; progress < query.size(); progress += rune_length, ++query_runes_count) {
if (query_offset + query_runes_count >= rune_arena_runes) return false;
rune_length = sz_rune_decode_unchecked(query.data() + progress,
rune_arena + query_offset + query_runes_count);
if (rune_length == sz_rune_invalid_k) return false;
}
size_t const candidate_offset = query_offset + query_runes_count;
size_t candidate_runes_count = 0;
for (size_t progress = 0; progress < candidate.size(); progress += rune_length, ++candidate_runes_count) {
if (candidate_offset + candidate_runes_count >= rune_arena_runes) return false;
rune_length = sz_rune_decode_unchecked(candidate.data() + progress,
rune_arena + candidate_offset + candidate_runes_count);
if (rune_length == sz_rune_invalid_k) return false;
}
arena_used = candidate_offset + candidate_runes_count;
span<rune_t const> const query_view {rune_arena + query_offset, query_runes_count};
span<rune_t const> const candidate_view {rune_arena + candidate_offset, candidate_runes_count};
bool const query_is_shorter = query_runes_count <= candidate_runes_count;
shorter_runes = query_is_shorter ? query_view : candidate_view;
longer_runes = query_is_shorter ? candidate_view : query_view;
return true;
};
static constexpr size_t stack_words_capacity_k =
64; for (size_t cell_index = cell_begin; cell_index != cell_end;) {
size_t query_index = 0, candidate_index = 0;
cross_cell_to_indices_(cell_index, candidates_count, cross_kind, query_index, candidate_index);
auto const query = to_view(queries[query_index]);
auto const candidate = to_view(candidates[candidate_index]);
bool const fits_myers = query.size() != 0 && candidate.size() != 0 &&
sz_min_of_two(query.size(), candidate.size()) <= stack_words_capacity_k * 64;
if (!fits_myers || !match_masks_scratch.size()) {
size_t result_distance = 0;
if (status_t status = dp(query, candidate, result_distance, dp_scratch_space, dummy, specs);
status != status_t::success_k)
return status;
scatter(destination_for(query_index, candidate_index), result_distance);
++cell_index;
continue;
}
size_t arena_used = 0;
span<rune_t const> group_shorters[myers_lanes_k], group_longers[myers_lanes_k];
size_t group_positions[myers_lanes_k];
cross_cell_destination_t<value_t> group_destinations[myers_lanes_k];
size_t group_query_indices[myers_lanes_k], group_candidate_indices[myers_lanes_k];
span<rune_t const> seed_shorter, seed_longer;
if (!transcode_cell(query, candidate, arena_used, seed_shorter, seed_longer)) {
size_t result_distance = 0;
if (status_t status = dp(query, candidate, result_distance, dp_scratch_space, dummy, specs);
status != status_t::success_k)
return status;
scatter(destination_for(query_index, candidate_index), result_distance);
++cell_index;
continue;
}
size_t const seed_shorter_runes = seed_shorter.size();
if (seed_shorter_runes == 0) {
size_t result_distance = 0;
if (status_t status = dp(query, candidate, result_distance, dp_scratch_space, dummy, specs);
status != status_t::success_k)
return status;
scatter(destination_for(query_index, candidate_index), result_distance);
++cell_index;
continue;
}
size_t const seed_bucket = divide_round_up<size_t>(seed_shorter_runes, 64);
group_shorters[0] = seed_shorter;
group_longers[0] = seed_longer;
group_positions[0] = 0;
group_destinations[0] = destination_for(query_index, candidate_index);
group_query_indices[0] = query_index;
group_candidate_indices[0] = candidate_index;
index_t group = 1;
++cell_index;
for (; cell_index != cell_end && group != myers_lanes_k; ++cell_index) {
size_t next_query_index = 0, next_candidate_index = 0;
cross_cell_to_indices_(cell_index, candidates_count, cross_kind, next_query_index,
next_candidate_index);
auto const next_query = to_view(queries[next_query_index]);
auto const next_candidate = to_view(candidates[next_candidate_index]);
if (next_query.size() == 0 || next_candidate.size() == 0 ||
sz_min_of_two(next_query.size(), next_candidate.size()) > stack_words_capacity_k * 64)
break;
size_t const arena_before = arena_used;
span<rune_t const> next_shorter, next_longer;
if (!transcode_cell(next_query, next_candidate, arena_used, next_shorter, next_longer)) {
arena_used = arena_before; break;
}
if (next_shorter.size() == 0 || divide_round_up<size_t>(next_shorter.size(), 64) != seed_bucket) {
arena_used = arena_before; break;
}
group_shorters[group] = next_shorter;
group_longers[group] = next_longer;
group_positions[group] = group;
group_destinations[group] = destination_for(next_query_index, next_candidate_index);
group_query_indices[group] = next_query_index;
group_candidate_indices[group] = next_candidate_index;
++group;
}
cross_cell_writer_t<value_t> group_writer;
group_writer.destinations = group_destinations;
lane_pairs_view<rune_t> const group_pairs {{group_shorters, group},
{group_longers, group},
{group_positions, group}};
status_t status = dispatch_word_bucket_<1, 8>(
seed_bucket,
[&](auto bucket) {
if constexpr (bucket.value == 1)
return myers.distances_8x64_(group_pairs, group_writer, match_masks_scratch);
else
return myers.template distances_8x_multiword_<bucket.value>(group_pairs, group_writer,
match_masks_scratch);
},
[&] { return myers.distances_8x_multiword_large_(group_pairs, group_writer, match_masks_scratch); });
if (status == status_t::success_k) continue;
for (index_t lane = 0; lane != group; ++lane) {
size_t lane_score = 0;
auto const lane_query = to_view(queries[group_query_indices[lane]]);
auto const lane_candidate = to_view(candidates[group_candidate_indices[lane]]);
if (status_t lane_status = dp(lane_query, lane_candidate, lane_score, dp_scratch_space, dummy, specs);
lane_status != status_t::success_k)
return lane_status;
scatter(group_destinations[lane], lane_score);
}
}
return status_t::success_k;
}
template <typename queries_type_, typename candidates_type_, typename results_type_, typename executor_type_>
SZ_NOINLINE status_t score_parallel_(queries_type_ const &queries, candidates_type_ const &candidates,
results_type_ &&results, cross_similarities_t cross_kind,
executor_type_ &&executor, cpu_specs_t const &specs) noexcept {
size_t const cells_count = cross_live_cells_count_(queries.size(), candidates.size(), cross_kind);
size_t const worker_scratch = worst_cell_scratch_(queries, candidates, specs);
size_t const workers = sz_max_of_two(executor.threads_count(), (size_t)1);
if (status_t status = score_scratch_.try_resize(worker_scratch * workers); status != status_t::success_k)
return status;
using prong_t = typename remove_cvref<executor_type_>::prong_t;
schedule_batches_t const schedule_batches = schedule_batches_(cells_count, workers,
lockstep_lanes_of_<myers_t>::value);
atomic_status_t status;
executor.for_n_dynamic(schedule_batches.batches_count, [&](prong_t prong) noexcept {
if (status != status_t::success_k) return;
scratch_space_t slice =
scratch_space_t(score_scratch_).subspan(prong.thread * worker_scratch, worker_scratch);
status = score_range_(queries, candidates, results, cross_kind, schedule_batches.batch_begin(prong.task),
schedule_batches.batch_end(prong.task, cells_count), slice, specs);
});
return status;
}
#pragma endregion Cross Product Scoring
#pragma region Non Unit Cross Product via Rune Lane Driver
template <typename queries_type_, typename candidates_type_, typename results_type_>
status_t cross_via_lanes_(queries_type_ const &queries, candidates_type_ const &candidates, results_type_ &&results,
cross_similarities_t cross_kind, cpu_specs_t const &specs) noexcept {
bool const same = static_cast<void const *>(&queries) == static_cast<void const *>(&candidates);
if (!transcode_views_(queries, query_arena_, query_runes_) ||
(!same && !transcode_views_(candidates, candidate_arena_, candidate_runes_)))
return cross_sequentially_<size_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_kind, score_scratch_, specs);
auto const &candidate_views = same ? query_runes_ : candidate_runes_;
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
rune_scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, query_runes_, candidate_views, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, query_runes_, candidate_views, results, cross_kind, 0,
cross_live_cells_count_(query_runes_.size(), candidate_views.size(), cross_kind),
scratch_space_t(score_scratch_), specs);
}
template <typename queries_type_, typename candidates_type_, typename results_type_, typename executor_type_>
status_t cross_via_lanes_parallel_(queries_type_ const &queries, candidates_type_ const &candidates,
results_type_ &&results, cross_similarities_t cross_kind,
executor_type_ &&executor, cpu_specs_t const &specs) noexcept {
bool const same = static_cast<void const *>(&queries) == static_cast<void const *>(&candidates);
if (!transcode_views_(queries, query_arena_, query_runes_) ||
(!same && !transcode_views_(candidates, candidate_arena_, candidate_runes_)))
return cross_in_parallel_<size_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_kind, score_scratch_, std::forward<executor_type_>(executor),
specs);
auto const &candidate_views = same ? query_runes_ : candidate_runes_;
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
rune_scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, query_runes_, candidate_views, results,
cross_kind, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Non Unit Cross Product via Rune Lane Driver
#pragma region Public Cross Product Overloads
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
return cross_(queries, candidates, results, cross_similarities_t::all_pairs_k, specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
return cross_parallel_(queries, candidates, results, cross_similarities_t::all_pairs_k,
std::forward<executor_type_>(executor), specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
return cross_(sequences, sequences, results, cross_similarities_t::symmetric_k, specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
return cross_parallel_(sequences, sequences, results, cross_similarities_t::symmetric_k,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Public Cross Product Overloads
#pragma region Cross Product Dispatch
private:
template <typename queries_type_, typename candidates_type_, typename value_type_>
status_t cross_(queries_type_ const &queries, candidates_type_ const &candidates, strided_rows<value_type_> results,
cross_similarities_t cross_kind, cpu_specs_t const &specs) noexcept {
if (corpus_is_ascii_<sz_find_byteset_icelake>(queries) && corpus_is_ascii_<sz_find_byteset_icelake>(candidates))
return bytes_fallback_(queries, candidates, results, specs);
if (!is_unit_cost(substituter_, gap_costs_))
return cross_via_lanes_(queries, candidates, results, cross_kind, specs);
if (status_t status = score_scratch_.try_resize(worst_cell_scratch_(queries, candidates, specs));
status != status_t::success_k)
return status;
return score_range_(queries, candidates, results, cross_kind, 0,
cross_live_cells_count_(queries.size(), candidates.size(), cross_kind),
scratch_space_t(score_scratch_), specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
status_t cross_parallel_(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cross_similarities_t cross_kind,
executor_type_ &&executor, cpu_specs_t const &specs) noexcept {
if (corpus_is_ascii_<sz_find_byteset_icelake>(queries) && corpus_is_ascii_<sz_find_byteset_icelake>(candidates))
return bytes_fallback_(queries, candidates, results, std::forward<executor_type_>(executor), specs);
if (!is_unit_cost(substituter_, gap_costs_))
return cross_via_lanes_parallel_(queries, candidates, results, cross_kind,
std::forward<executor_type_>(executor), specs);
return score_parallel_(queries, candidates, results, cross_kind, std::forward<executor_type_>(executor), specs);
}
#pragma endregion Cross Product Dispatch
};
template <typename allocator_type_, sz_capability_t capability_>
struct levenshtein_distances_utf8<affine_gap_costs_t, allocator_type_, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using gap_costs_t = affine_gap_costs_t;
using allocator_t = allocator_type_;
using index_t = u32_t;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t candidate_lanes_k = 32;
using scoring_t = levenshtein_distance_utf8<gap_costs_t, sz_cap_serial_k>; using lane_walker_narrow_t =
candidate_lane_walker<rune_t, u16_t, uniform_substitution_costs_t, affine_gap_costs_t, sz_minimize_distance_k,
sz_similarity_global_k, sz_cap_icelake_k, (int)candidate_lanes_k,
void>; using lane_walker_wide_t =
candidate_lane_walker<rune_t, u32_t, uniform_substitution_costs_t, affine_gap_costs_t, sz_minimize_distance_k,
sz_similarity_global_k, sz_cap_icelake_k, 16, void>; using rune_scoring_t = levenshtein_distance<rune_t, gap_costs_t, sz_cap_serial_k>;
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
using rune_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<rune_t>;
using rune_view_allocator_t =
typename std::allocator_traits<allocator_t>::template rebind_alloc<span<rune_t const>>;
using linear_fallback_t = levenshtein_distances_utf8<linear_gap_costs_t, allocator_t, capability_k>;
using bytes_fallback_t = levenshtein_distances<affine_gap_costs_t, allocator_t, capability_k>;
uniform_substitution_costs_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
safe_vector<std::byte, scratch_allocator_t> score_scratch_ {alloc_};
safe_vector<rune_t, rune_allocator_t> query_arena_ {alloc_};
safe_vector<rune_t, rune_allocator_t> candidate_arena_ {alloc_};
safe_vector<span<rune_t const>, rune_view_allocator_t> query_runes_ {alloc_};
safe_vector<span<rune_t const>, rune_view_allocator_t> candidate_runes_ {alloc_};
linear_fallback_t linear_fallback_;
bytes_fallback_t bytes_fallback_;
levenshtein_distances_utf8(allocator_t alloc = {}) noexcept
: alloc_(alloc), linear_fallback_(alloc), bytes_fallback_(alloc) {}
levenshtein_distances_utf8(uniform_substitution_costs_t subs, affine_gap_costs_t gaps,
allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc),
linear_fallback_(subs, linear_gap_costs_t {gaps.open}, alloc), bytes_fallback_(subs, gaps, alloc) {}
template <typename sequences_type_>
bool transcode_views_(sequences_type_ const &sequences, safe_vector<rune_t, rune_allocator_t> &arena,
safe_vector<span<rune_t const>, rune_view_allocator_t> &views) const noexcept {
size_t total_bytes = 0;
for (size_t index = 0; index < sequences.size(); ++index) total_bytes += to_view(sequences[index]).size();
if (arena.try_reserve(total_bytes) != status_t::success_k) return false;
if (arena.try_resize(0) != status_t::success_k) return false;
if (views.try_resize(sequences.size()) != status_t::success_k) return false;
for (size_t index = 0; index < sequences.size(); ++index) {
auto const bytes = to_view(sequences[index]);
size_t const rune_begin = arena.size();
rune_length_t rune_length;
for (size_t progress = 0; progress < bytes.size(); progress += rune_length) {
rune_t rune;
rune_length = sz_rune_decode_unchecked(bytes.data() + progress, &rune);
if (rune_length == sz_rune_invalid_k) return false;
if (arena.try_resize(arena.size() + 1) != status_t::success_k) return false;
arena[arena.size() - 1] = rune;
}
views[index] = span<rune_t const> {arena.data() + rune_begin, arena.size() - rune_begin};
}
return true;
}
#pragma region Cross Product via Lane Driver
template <typename queries_type_, typename candidates_type_, typename results_type_>
status_t cross_via_lanes_(queries_type_ const &queries, candidates_type_ const &candidates, results_type_ &&results,
cross_similarities_t cross_kind, cpu_specs_t const &specs) noexcept {
if (gap_costs_.is_linear()) return linear_fallback_(queries, candidates, results, specs);
if (corpus_is_ascii_<sz_find_byteset_icelake>(queries) && corpus_is_ascii_<sz_find_byteset_icelake>(candidates))
return bytes_fallback_(queries, candidates, results, specs);
bool const same = static_cast<void const *>(&queries) == static_cast<void const *>(&candidates);
if (!transcode_views_(queries, query_arena_, query_runes_) ||
(!same && !transcode_views_(candidates, candidate_arena_, candidate_runes_)))
return cross_sequentially_<size_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_kind, score_scratch_, specs);
auto const &candidate_views = same ? query_runes_ : candidate_runes_;
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
rune_scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, query_runes_, candidate_views, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, query_runes_, candidate_views, results, cross_kind, 0,
cross_live_cells_count_(query_runes_.size(), candidate_views.size(), cross_kind),
scratch_space_t(score_scratch_), specs);
}
template <typename queries_type_, typename candidates_type_, typename results_type_, typename executor_type_>
status_t cross_via_lanes_parallel_(queries_type_ const &queries, candidates_type_ const &candidates,
results_type_ &&results, cross_similarities_t cross_kind,
executor_type_ &&executor, cpu_specs_t const &specs) noexcept {
if (gap_costs_.is_linear())
return linear_fallback_(queries, candidates, results, std::forward<executor_type_>(executor), specs);
if (corpus_is_ascii_<sz_find_byteset_icelake>(queries) && corpus_is_ascii_<sz_find_byteset_icelake>(candidates))
return bytes_fallback_(queries, candidates, results, std::forward<executor_type_>(executor), specs);
bool const same = static_cast<void const *>(&queries) == static_cast<void const *>(&candidates);
if (!transcode_views_(queries, query_arena_, query_runes_) ||
(!same && !transcode_views_(candidates, candidate_arena_, candidate_runes_)))
return cross_in_parallel_<size_t>(scoring_t {substituter_, gap_costs_}, queries, candidates, results,
cross_kind, score_scratch_, std::forward<executor_type_>(executor),
specs);
auto const &candidate_views = same ? query_runes_ : candidate_runes_;
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
rune_scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, query_runes_, candidate_views, results,
cross_kind, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Cross Product via Lane Driver
#pragma region Public Cross Product Overloads
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
return cross_via_lanes_(queries, candidates, results, cross_similarities_t::all_pairs_k, specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
return cross_via_lanes_parallel_(queries, candidates, results, cross_similarities_t::all_pairs_k,
std::forward<executor_type_>(executor), specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
return cross_via_lanes_(sequences, sequences, results, cross_similarities_t::symmetric_k, specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
return cross_via_lanes_parallel_(sequences, sequences, results, cross_similarities_t::symmetric_k,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Public Cross Product Overloads
};
struct substitution_lookup_icelake_t {
u512_vec_t byte_to_class_vecs_[4];
u512_vec_t is_third_or_fourth_vec_, is_second_or_fourth_vec_;
u512_vec_t cost_windows_vecs_[16];
substitution_lookup_icelake_t() noexcept {
char is_third_or_fourth_check, is_second_or_fourth_check;
*(u8_t *)&is_third_or_fourth_check = 0x80, *(u8_t *)&is_second_or_fourth_check = 0x40;
is_third_or_fourth_vec_.zmm = _mm512_set1_epi8(is_third_or_fourth_check);
is_second_or_fourth_vec_.zmm = _mm512_set1_epi8(is_second_or_fourth_check);
}
void reload_classes(u8_t const *byte_to_class) noexcept {
byte_to_class_vecs_[0].zmm = _mm512_loadu_si512(byte_to_class + 64 * 0);
byte_to_class_vecs_[1].zmm = _mm512_loadu_si512(byte_to_class + 64 * 1);
byte_to_class_vecs_[2].zmm = _mm512_loadu_si512(byte_to_class + 64 * 2);
byte_to_class_vecs_[3].zmm = _mm512_loadu_si512(byte_to_class + 64 * 3);
}
void reload_costs(
error_cost_t const (&class_substitution_costs)[error_costs_classes_count_k][error_costs_classes_count_k],
bool transpose) noexcept {
alignas(64) error_cost_t windows[16 * 64];
for (size_t window = 0; window != 16; ++window)
for (size_t low_bit = 0; low_bit != 2; ++low_bit) {
size_t const first_class = window * 2 + low_bit;
for (size_t second_class = 0; second_class != error_costs_classes_count_k; ++second_class)
windows[window * 64 + low_bit * 32 + second_class] =
transpose ? class_substitution_costs[second_class][first_class]
: class_substitution_costs[first_class][second_class];
}
for (size_t window = 0; window != 16; ++window)
cost_windows_vecs_[window].zmm = _mm512_load_si512(windows + window * 64);
}
SZ_INLINE u512_vec_t classify64(u512_vec_t const &text_vec) const noexcept {
u512_vec_t shuffled_class_vecs[4];
u512_vec_t class_vec;
__mmask64 is_third_or_fourth, is_second_or_fourth;
shuffled_class_vecs[0].zmm = _mm512_permutexvar_epi8(text_vec.zmm, byte_to_class_vecs_[0].zmm);
shuffled_class_vecs[1].zmm = _mm512_permutexvar_epi8(text_vec.zmm, byte_to_class_vecs_[1].zmm);
shuffled_class_vecs[2].zmm = _mm512_permutexvar_epi8(text_vec.zmm, byte_to_class_vecs_[2].zmm);
shuffled_class_vecs[3].zmm = _mm512_permutexvar_epi8(text_vec.zmm, byte_to_class_vecs_[3].zmm);
is_third_or_fourth = _mm512_test_epi8_mask(text_vec.zmm, is_third_or_fourth_vec_.zmm);
is_second_or_fourth = _mm512_test_epi8_mask(text_vec.zmm, is_second_or_fourth_vec_.zmm);
class_vec.zmm = _mm512_mask_blend_epi8(
is_third_or_fourth,
_mm512_mask_blend_epi8(is_second_or_fourth, shuffled_class_vecs[0].zmm, shuffled_class_vecs[1].zmm),
_mm512_mask_blend_epi8(is_second_or_fourth, shuffled_class_vecs[2].zmm, shuffled_class_vecs[3].zmm));
return class_vec;
}
SZ_INLINE u512_vec_t lookup64(u512_vec_t const &first_class_vec,
u512_vec_t const &second_class_vec) const noexcept {
u512_vec_t index_vec, substituted_vec;
u512_vec_t permuted_vecs[16], blend4_vecs[8], blend3_vecs[4], blend2_vecs[2];
u512_vec_t window_vec;
index_vec.zmm = _mm512_or_si512( _mm512_and_si512(_mm512_slli_epi16(_mm512_and_si512(first_class_vec.zmm, _mm512_set1_epi8(1)), 5), _mm512_set1_epi8((char)0x20)), second_class_vec.zmm);
for (size_t window = 0; window != 16; ++window)
permuted_vecs[window].zmm = _mm512_permutexvar_epi8(index_vec.zmm, cost_windows_vecs_[window].zmm);
window_vec.zmm = _mm512_and_si512(_mm512_srli_epi16(first_class_vec.zmm, 1), _mm512_set1_epi8(15));
__mmask64 const window_bit0 = _mm512_test_epi8_mask(window_vec.zmm, _mm512_set1_epi8(1));
for (size_t pair = 0; pair != 8; ++pair)
blend4_vecs[pair].zmm = _mm512_mask_blend_epi8(window_bit0, permuted_vecs[2 * pair].zmm,
permuted_vecs[2 * pair + 1].zmm);
__mmask64 const window_bit1 = _mm512_test_epi8_mask(window_vec.zmm, _mm512_set1_epi8(2));
for (size_t pair = 0; pair != 4; ++pair)
blend3_vecs[pair].zmm = _mm512_mask_blend_epi8(window_bit1, blend4_vecs[2 * pair].zmm,
blend4_vecs[2 * pair + 1].zmm);
__mmask64 const window_bit2 = _mm512_test_epi8_mask(window_vec.zmm, _mm512_set1_epi8(4));
for (size_t pair = 0; pair != 2; ++pair)
blend2_vecs[pair].zmm = _mm512_mask_blend_epi8(window_bit2, blend3_vecs[2 * pair].zmm,
blend3_vecs[2 * pair + 1].zmm);
__mmask64 const window_bit3 = _mm512_test_epi8_mask(window_vec.zmm, _mm512_set1_epi8(8));
substituted_vec.zmm = _mm512_mask_blend_epi8(window_bit3, blend2_vecs[0].zmm, blend2_vecs[1].zmm);
return substituted_vec;
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, linear_gap_costs_t,
sz_maximize_score_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_maximize_score_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
substitution_lookup_icelake_t lookup_;
void prepare(bool transpose) noexcept {
lookup_.reload_costs(this->substituter_.class_substitution_costs, transpose);
}
SZ_INLINE void slice_aligned64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t *scores_new, u512_vec_t gap_cost_vec) const noexcept {
u512_vec_t first_vec, second_vec;
u512_vec_t cost_of_substitution_i8_vec, cost_of_substitution_i16_vecs[2];
u512_vec_t pre_substitution_vecs[2], pre_insert_vecs[2], pre_delete_vecs[2];
u512_vec_t cost_if_substitution_vecs[2], cost_if_gap_vecs[2], cell_score_vecs[2];
first_vec.zmm = _mm512_loadu_epi8(first_reversed_slice);
second_vec.zmm = _mm512_loadu_epi8(second_slice);
pre_substitution_vecs[0].zmm = _mm512_loadu_epi16(scores_pre_substitution + 0);
pre_substitution_vecs[1].zmm = _mm512_loadu_epi16(scores_pre_substitution + 32);
pre_insert_vecs[0].zmm = _mm512_loadu_epi16(scores_pre_insertion + 0);
pre_insert_vecs[1].zmm = _mm512_loadu_epi16(scores_pre_insertion + 32);
pre_delete_vecs[0].zmm = _mm512_loadu_epi16(scores_pre_deletion + 0);
pre_delete_vecs[1].zmm = _mm512_loadu_epi16(scores_pre_deletion + 32);
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i16_vecs[0].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i16_vecs[1].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 1));
cost_if_substitution_vecs[0].zmm = _mm512_add_epi16(pre_substitution_vecs[0].zmm,
cost_of_substitution_i16_vecs[0].zmm);
cost_if_substitution_vecs[1].zmm = _mm512_add_epi16(pre_substitution_vecs[1].zmm,
cost_of_substitution_i16_vecs[1].zmm);
cost_if_gap_vecs[0].zmm = _mm512_add_epi16(_mm512_max_epi16(pre_insert_vecs[0].zmm, pre_delete_vecs[0].zmm),
gap_cost_vec.zmm);
cost_if_gap_vecs[1].zmm = _mm512_add_epi16(_mm512_max_epi16(pre_insert_vecs[1].zmm, pre_delete_vecs[1].zmm),
gap_cost_vec.zmm);
cell_score_vecs[0].zmm = _mm512_max_epi16(cost_if_substitution_vecs[0].zmm, cost_if_gap_vecs[0].zmm);
cell_score_vecs[1].zmm = _mm512_max_epi16(cost_if_substitution_vecs[1].zmm, cost_if_gap_vecs[1].zmm);
_mm512_store_si512(scores_new + 0, cell_score_vecs[0].zmm);
_mm512_store_si512(scores_new + 32, cell_score_vecs[1].zmm);
}
SZ_INLINE void slice_upto64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, size_t n, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t *scores_new, u512_vec_t gap_cost_vec) const noexcept {
__mmask64 load_mask;
__mmask32 load_masks[2];
u512_vec_t first_vec, second_vec;
u512_vec_t cost_of_substitution_i8_vec, cost_of_substitution_i16_vecs[2];
u512_vec_t pre_substitution_vecs[2], pre_insert_vecs[2], pre_delete_vecs[2];
u512_vec_t cost_if_substitution_vecs[2], cost_if_gap_vecs[2], cell_score_vecs[2];
load_mask = sz_u64_mask_until_(n);
load_masks[0] = sz_u32_mask_until_(n);
load_masks[1] = sz_u32_mask_until_(n > 32 ? n - 32 : 0);
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
pre_substitution_vecs[0].zmm = _mm512_maskz_loadu_epi16(load_masks[0], scores_pre_substitution + 0);
pre_substitution_vecs[1].zmm = _mm512_maskz_loadu_epi16(load_masks[1], scores_pre_substitution + 32);
pre_insert_vecs[0].zmm = _mm512_maskz_loadu_epi16(load_masks[0], scores_pre_insertion + 0);
pre_insert_vecs[1].zmm = _mm512_maskz_loadu_epi16(load_masks[1], scores_pre_insertion + 32);
pre_delete_vecs[0].zmm = _mm512_maskz_loadu_epi16(load_masks[0], scores_pre_deletion + 0);
pre_delete_vecs[1].zmm = _mm512_maskz_loadu_epi16(load_masks[1], scores_pre_deletion + 32);
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i16_vecs[0].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i16_vecs[1].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 1));
cost_if_substitution_vecs[0].zmm = _mm512_add_epi16(pre_substitution_vecs[0].zmm,
cost_of_substitution_i16_vecs[0].zmm);
cost_if_substitution_vecs[1].zmm = _mm512_add_epi16(pre_substitution_vecs[1].zmm,
cost_of_substitution_i16_vecs[1].zmm);
cost_if_gap_vecs[0].zmm = _mm512_add_epi16(_mm512_max_epi16(pre_insert_vecs[0].zmm, pre_delete_vecs[0].zmm),
gap_cost_vec.zmm);
cost_if_gap_vecs[1].zmm = _mm512_add_epi16(_mm512_max_epi16(pre_insert_vecs[1].zmm, pre_delete_vecs[1].zmm),
gap_cost_vec.zmm);
cell_score_vecs[0].zmm = _mm512_max_epi16(cost_if_substitution_vecs[0].zmm, cost_if_gap_vecs[0].zmm);
cell_score_vecs[1].zmm = _mm512_max_epi16(cost_if_substitution_vecs[1].zmm, cost_if_gap_vecs[1].zmm);
_mm512_mask_storeu_epi16(scores_new + 0, load_masks[0], cell_score_vecs[0].zmm);
_mm512_mask_storeu_epi16(scores_new + 32, load_masks[1], cell_score_vecs[1].zmm);
}
SZ_NOINLINE void score_slice_trampoline_( u8_t const *first_reversed_classes, u8_t const *second_classes, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t *scores_new, u512_vec_t gap_cost_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_aligned64chars( first_reversed_classes + progress, second_classes + progress, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_new + progress, gap_cost_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t *scores_new, executor_type_ &&executor = {}) noexcept {
u8_t const *first_reversed_classes = (u8_t const *)first_reversed_slice;
u8_t const *second_classes = (u8_t const *)second_slice;
u512_vec_t gap_cost_vec;
gap_cost_vec.zmm = _mm512_set1_epi16(this->gap_costs_.open_or_extend);
if (length <= step_k) {
slice_upto64chars( first_reversed_classes, second_classes, length, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec);
this->last_score_ = scores_new[0];
return;
}
head_body_tail_t hbt = head_body_tail<step_k>(scores_new, length);
if (hbt.head)
slice_upto64chars( first_reversed_classes, second_classes, hbt.head, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec);
first_reversed_classes += hbt.head, second_classes += hbt.head, scores_pre_substitution += hbt.head,
scores_pre_insertion += hbt.head, scores_pre_deletion += hbt.head, scores_new += hbt.head;
if (hbt.tail)
slice_upto64chars( first_reversed_classes + hbt.body, second_classes + hbt.body, hbt.tail, scores_pre_substitution + hbt.body, scores_pre_insertion + hbt.body, scores_pre_deletion + hbt.body, scores_new + hbt.body, gap_cost_vec);
size_t const body_pages = hbt.body / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_classes, second_classes, scores_pre_substitution,
scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec, from, to);
});
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, linear_gap_costs_t,
sz_maximize_score_k, sz_similarity_local_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_maximize_score_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_local_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
substitution_lookup_icelake_t lookup_;
void prepare(bool transpose) noexcept {
lookup_.reload_costs(this->substituter_.class_substitution_costs, transpose);
}
SZ_INLINE void slice_aligned64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t *scores_new, u512_vec_t gap_cost_vec) const noexcept {
u512_vec_t first_vec, second_vec;
u512_vec_t cost_of_substitution_i8_vec, cost_of_substitution_i16_vecs[2];
u512_vec_t pre_substitution_vecs[2], pre_insert_vecs[2], pre_delete_vecs[2];
u512_vec_t cost_if_substitution_vecs[2], cost_if_gap_vecs[2], cell_score_vecs[2];
first_vec.zmm = _mm512_loadu_epi8(first_reversed_slice);
second_vec.zmm = _mm512_loadu_epi8(second_slice);
pre_substitution_vecs[0].zmm = _mm512_loadu_epi16(scores_pre_substitution + 0);
pre_substitution_vecs[1].zmm = _mm512_loadu_epi16(scores_pre_substitution + 32);
pre_insert_vecs[0].zmm = _mm512_loadu_epi16(scores_pre_insertion + 0);
pre_insert_vecs[1].zmm = _mm512_loadu_epi16(scores_pre_insertion + 32);
pre_delete_vecs[0].zmm = _mm512_loadu_epi16(scores_pre_deletion + 0);
pre_delete_vecs[1].zmm = _mm512_loadu_epi16(scores_pre_deletion + 32);
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i16_vecs[0].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i16_vecs[1].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 1));
cost_if_substitution_vecs[0].zmm = _mm512_add_epi16(pre_substitution_vecs[0].zmm,
cost_of_substitution_i16_vecs[0].zmm);
cost_if_substitution_vecs[1].zmm = _mm512_add_epi16(pre_substitution_vecs[1].zmm,
cost_of_substitution_i16_vecs[1].zmm);
cost_if_gap_vecs[0].zmm = _mm512_add_epi16(_mm512_max_epi16(pre_insert_vecs[0].zmm, pre_delete_vecs[0].zmm),
gap_cost_vec.zmm);
cost_if_gap_vecs[1].zmm = _mm512_add_epi16(_mm512_max_epi16(pre_insert_vecs[1].zmm, pre_delete_vecs[1].zmm),
gap_cost_vec.zmm);
cell_score_vecs[0].zmm = _mm512_max_epi16(cost_if_substitution_vecs[0].zmm, cost_if_gap_vecs[0].zmm);
cell_score_vecs[1].zmm = _mm512_max_epi16(cost_if_substitution_vecs[1].zmm, cost_if_gap_vecs[1].zmm);
cell_score_vecs[0].zmm = _mm512_max_epi16(cell_score_vecs[0].zmm, _mm512_setzero_epi32());
cell_score_vecs[1].zmm = _mm512_max_epi16(cell_score_vecs[1].zmm, _mm512_setzero_epi32());
_mm512_store_si512(scores_new + 0, cell_score_vecs[0].zmm);
_mm512_store_si512(scores_new + 32, cell_score_vecs[1].zmm);
}
SZ_INLINE void slice_upto64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, size_t n, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t *scores_new, u512_vec_t gap_cost_vec) const noexcept {
__mmask64 load_mask;
__mmask32 load_masks[2];
u512_vec_t first_vec, second_vec;
u512_vec_t cost_of_substitution_i8_vec, cost_of_substitution_i16_vecs[2];
u512_vec_t pre_substitution_vecs[2], pre_insert_vecs[2], pre_delete_vecs[2];
u512_vec_t cost_if_substitution_vecs[2], cost_if_gap_vecs[2], cell_score_vecs[2];
load_mask = sz_u64_mask_until_(n);
load_masks[0] = sz_u32_mask_until_(n);
load_masks[1] = sz_u32_mask_until_(n > 32 ? n - 32 : 0);
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
pre_substitution_vecs[0].zmm = _mm512_maskz_loadu_epi16(load_masks[0], scores_pre_substitution + 0);
pre_substitution_vecs[1].zmm = _mm512_maskz_loadu_epi16(load_masks[1], scores_pre_substitution + 32);
pre_insert_vecs[0].zmm = _mm512_maskz_loadu_epi16(load_masks[0], scores_pre_insertion + 0);
pre_insert_vecs[1].zmm = _mm512_maskz_loadu_epi16(load_masks[1], scores_pre_insertion + 32);
pre_delete_vecs[0].zmm = _mm512_maskz_loadu_epi16(load_masks[0], scores_pre_deletion + 0);
pre_delete_vecs[1].zmm = _mm512_maskz_loadu_epi16(load_masks[1], scores_pre_deletion + 32);
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i16_vecs[0].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i16_vecs[1].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 1));
cost_if_substitution_vecs[0].zmm = _mm512_add_epi16(pre_substitution_vecs[0].zmm,
cost_of_substitution_i16_vecs[0].zmm);
cost_if_substitution_vecs[1].zmm = _mm512_add_epi16(pre_substitution_vecs[1].zmm,
cost_of_substitution_i16_vecs[1].zmm);
cost_if_gap_vecs[0].zmm = _mm512_add_epi16(_mm512_max_epi16(pre_insert_vecs[0].zmm, pre_delete_vecs[0].zmm),
gap_cost_vec.zmm);
cost_if_gap_vecs[1].zmm = _mm512_add_epi16(_mm512_max_epi16(pre_insert_vecs[1].zmm, pre_delete_vecs[1].zmm),
gap_cost_vec.zmm);
cell_score_vecs[0].zmm = _mm512_max_epi16(cost_if_substitution_vecs[0].zmm, cost_if_gap_vecs[0].zmm);
cell_score_vecs[1].zmm = _mm512_max_epi16(cost_if_substitution_vecs[1].zmm, cost_if_gap_vecs[1].zmm);
cell_score_vecs[0].zmm = _mm512_max_epi16(cell_score_vecs[0].zmm, _mm512_setzero_epi32());
cell_score_vecs[1].zmm = _mm512_max_epi16(cell_score_vecs[1].zmm, _mm512_setzero_epi32());
_mm512_mask_storeu_epi16(scores_new + 0, load_masks[0], cell_score_vecs[0].zmm);
_mm512_mask_storeu_epi16(scores_new + 32, load_masks[1], cell_score_vecs[1].zmm);
}
SZ_NOINLINE void score_slice_trampoline_( u8_t const *first_reversed_classes, u8_t const *second_classes, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t *scores_new, u512_vec_t gap_cost_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_aligned64chars( first_reversed_classes + progress, second_classes + progress, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_new + progress, gap_cost_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t *scores_new, executor_type_ &&executor = {}) noexcept {
u8_t const *first_reversed_classes = (u8_t const *)first_reversed_slice;
u8_t const *second_classes = (u8_t const *)second_slice;
i16_t *const scores_new_begin = scores_new;
u512_vec_t gap_cost_vec;
gap_cost_vec.zmm = _mm512_set1_epi16(this->gap_costs_.open_or_extend);
if (length <= step_k) {
slice_upto64chars( first_reversed_classes, second_classes, length, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec);
}
else {
head_body_tail_t hbt = head_body_tail<step_k>(scores_new, length);
if (hbt.head)
slice_upto64chars( first_reversed_classes, second_classes, hbt.head, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec);
first_reversed_classes += hbt.head, second_classes += hbt.head, scores_pre_substitution += hbt.head,
scores_pre_insertion += hbt.head, scores_pre_deletion += hbt.head, scores_new += hbt.head;
if (hbt.tail)
slice_upto64chars( first_reversed_classes + hbt.body, second_classes + hbt.body, hbt.tail, scores_pre_substitution + hbt.body, scores_pre_insertion + hbt.body, scores_pre_deletion + hbt.body, scores_new + hbt.body, gap_cost_vec);
size_t const body_pages = hbt.body / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_classes, second_classes, scores_pre_substitution,
scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec, from, to);
});
}
i16_t best_in_diagonal = this->best_score_;
for (size_t i = 0; i != length; ++i) best_in_diagonal = sz_max_of_two(best_in_diagonal, scores_new_begin[i]);
this->best_score_ = best_in_diagonal;
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, linear_gap_costs_t,
sz_maximize_score_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_maximize_score_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
substitution_lookup_icelake_t lookup_;
void prepare(bool transpose) noexcept {
lookup_.reload_costs(this->substituter_.class_substitution_costs, transpose);
}
SZ_INLINE void slice_aligned64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t *scores_new, u512_vec_t gap_cost_vec) const noexcept {
u512_vec_t first_vec, second_vec, cost_of_substitution_i8_vec;
u512_vec_t cost_of_substitution_i32_vecs[4];
u512_vec_t pre_substitution_vecs[4], pre_insert_vecs[4], pre_delete_vecs[4];
u512_vec_t cost_if_substitution_vecs[4], cost_if_gap_vecs[4], cell_score_vecs[4];
first_vec.zmm = _mm512_loadu_epi8(first_reversed_slice);
second_vec.zmm = _mm512_loadu_epi8(second_slice);
for (size_t part = 0; part != 4; ++part) {
pre_substitution_vecs[part].zmm = _mm512_loadu_epi32(scores_pre_substitution + part * 16);
pre_insert_vecs[part].zmm = _mm512_loadu_epi32(scores_pre_insertion + part * 16);
pre_delete_vecs[part].zmm = _mm512_loadu_epi32(scores_pre_deletion + part * 16);
}
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i32_vecs[0].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i32_vecs[1].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 1));
cost_of_substitution_i32_vecs[2].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 2));
cost_of_substitution_i32_vecs[3].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 3));
for (size_t part = 0; part != 4; ++part) {
cost_if_substitution_vecs[part].zmm = _mm512_add_epi32(pre_substitution_vecs[part].zmm,
cost_of_substitution_i32_vecs[part].zmm);
cost_if_gap_vecs[part].zmm = _mm512_add_epi32(
_mm512_max_epi32(pre_insert_vecs[part].zmm, pre_delete_vecs[part].zmm), gap_cost_vec.zmm);
cell_score_vecs[part].zmm = _mm512_max_epi32(cost_if_substitution_vecs[part].zmm,
cost_if_gap_vecs[part].zmm);
_mm512_store_si512(scores_new + part * 16, cell_score_vecs[part].zmm);
}
}
SZ_INLINE void slice_upto64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, size_t n, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t *scores_new, u512_vec_t gap_cost_vec) const noexcept {
u512_vec_t first_vec, second_vec, cost_of_substitution_i8_vec;
u512_vec_t cost_of_substitution_i32_vecs[4];
u512_vec_t pre_substitution_vecs[4], pre_insert_vecs[4], pre_delete_vecs[4];
u512_vec_t cost_if_substitution_vecs[4], cost_if_gap_vecs[4], cell_score_vecs[4];
__mmask64 const load_mask = sz_u64_mask_until_(n);
__mmask16 const load_masks[4] = {(__mmask16)load_mask, (__mmask16)(load_mask >> 16),
(__mmask16)(load_mask >> 32), (__mmask16)(load_mask >> 48)};
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
for (size_t part = 0; part != 4; ++part) {
pre_substitution_vecs[part].zmm = _mm512_maskz_loadu_epi32(load_masks[part],
scores_pre_substitution + part * 16);
pre_insert_vecs[part].zmm = _mm512_maskz_loadu_epi32(load_masks[part], scores_pre_insertion + part * 16);
pre_delete_vecs[part].zmm = _mm512_maskz_loadu_epi32(load_masks[part], scores_pre_deletion + part * 16);
}
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i32_vecs[0].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i32_vecs[1].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 1));
cost_of_substitution_i32_vecs[2].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 2));
cost_of_substitution_i32_vecs[3].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 3));
for (size_t part = 0; part != 4; ++part) {
cost_if_substitution_vecs[part].zmm = _mm512_add_epi32(pre_substitution_vecs[part].zmm,
cost_of_substitution_i32_vecs[part].zmm);
cost_if_gap_vecs[part].zmm = _mm512_add_epi32(
_mm512_max_epi32(pre_insert_vecs[part].zmm, pre_delete_vecs[part].zmm), gap_cost_vec.zmm);
cell_score_vecs[part].zmm = _mm512_max_epi32(cost_if_substitution_vecs[part].zmm,
cost_if_gap_vecs[part].zmm);
_mm512_mask_storeu_epi32(scores_new + part * 16, load_masks[part], cell_score_vecs[part].zmm);
}
}
SZ_NOINLINE void score_slice_trampoline_( u8_t const *first_reversed_classes, u8_t const *second_classes, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t *scores_new, u512_vec_t gap_cost_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_aligned64chars( first_reversed_classes + progress, second_classes + progress, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_new + progress, gap_cost_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t *scores_new, executor_type_ &&executor = {}) noexcept {
u8_t const *first_reversed_classes = (u8_t const *)first_reversed_slice;
u8_t const *second_classes = (u8_t const *)second_slice;
u512_vec_t gap_cost_vec;
gap_cost_vec.zmm = _mm512_set1_epi32(this->gap_costs_.open_or_extend);
if (length <= step_k) {
slice_upto64chars( first_reversed_classes, second_classes, length, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec);
this->last_score_ = scores_new[0];
return;
}
head_body_tail_t hbt = head_body_tail<step_k>(scores_new, length);
if (hbt.head)
slice_upto64chars( first_reversed_classes, second_classes, hbt.head, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec);
first_reversed_classes += hbt.head, second_classes += hbt.head, scores_pre_substitution += hbt.head,
scores_pre_insertion += hbt.head, scores_pre_deletion += hbt.head, scores_new += hbt.head;
if (hbt.tail)
slice_upto64chars( first_reversed_classes + hbt.body, second_classes + hbt.body, hbt.tail, scores_pre_substitution + hbt.body, scores_pre_insertion + hbt.body, scores_pre_deletion + hbt.body, scores_new + hbt.body, gap_cost_vec);
size_t const body_pages = hbt.body / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_classes, second_classes, scores_pre_substitution,
scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec, from, to);
});
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, linear_gap_costs_t,
sz_maximize_score_k, sz_similarity_local_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_maximize_score_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_local_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
substitution_lookup_icelake_t lookup_;
void prepare(bool transpose) noexcept {
lookup_.reload_costs(this->substituter_.class_substitution_costs, transpose);
}
SZ_INLINE void slice_aligned64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t *scores_new, u512_vec_t gap_cost_vec) const noexcept {
u512_vec_t first_vec, second_vec, cost_of_substitution_i8_vec;
u512_vec_t cost_of_substitution_i32_vecs[4];
u512_vec_t pre_substitution_vecs[4], pre_insert_vecs[4], pre_delete_vecs[4];
u512_vec_t cost_if_substitution_vecs[4], cost_if_gap_vecs[4], cell_score_vecs[4];
first_vec.zmm = _mm512_loadu_epi8(first_reversed_slice);
second_vec.zmm = _mm512_loadu_epi8(second_slice);
for (size_t part = 0; part != 4; ++part) {
pre_substitution_vecs[part].zmm = _mm512_loadu_epi32(scores_pre_substitution + part * 16);
pre_insert_vecs[part].zmm = _mm512_loadu_epi32(scores_pre_insertion + part * 16);
pre_delete_vecs[part].zmm = _mm512_loadu_epi32(scores_pre_deletion + part * 16);
}
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i32_vecs[0].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i32_vecs[1].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 1));
cost_of_substitution_i32_vecs[2].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 2));
cost_of_substitution_i32_vecs[3].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 3));
for (size_t part = 0; part != 4; ++part) {
cost_if_substitution_vecs[part].zmm = _mm512_add_epi32(pre_substitution_vecs[part].zmm,
cost_of_substitution_i32_vecs[part].zmm);
cost_if_gap_vecs[part].zmm = _mm512_add_epi32(
_mm512_max_epi32(pre_insert_vecs[part].zmm, pre_delete_vecs[part].zmm), gap_cost_vec.zmm);
cell_score_vecs[part].zmm = _mm512_max_epi32(cost_if_substitution_vecs[part].zmm,
cost_if_gap_vecs[part].zmm);
cell_score_vecs[part].zmm = _mm512_max_epi32(cell_score_vecs[part].zmm, _mm512_setzero_epi32());
_mm512_store_si512(scores_new + part * 16, cell_score_vecs[part].zmm);
}
}
SZ_INLINE void slice_upto64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, size_t n, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t *scores_new, u512_vec_t gap_cost_vec) const noexcept {
u512_vec_t first_vec, second_vec, cost_of_substitution_i8_vec;
u512_vec_t cost_of_substitution_i32_vecs[4];
u512_vec_t pre_substitution_vecs[4], pre_insert_vecs[4], pre_delete_vecs[4];
u512_vec_t cost_if_substitution_vecs[4], cost_if_gap_vecs[4], cell_score_vecs[4];
__mmask64 const load_mask = sz_u64_mask_until_(n);
__mmask16 const load_masks[4] = {(__mmask16)load_mask, (__mmask16)(load_mask >> 16),
(__mmask16)(load_mask >> 32), (__mmask16)(load_mask >> 48)};
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
for (size_t part = 0; part != 4; ++part) {
pre_substitution_vecs[part].zmm = _mm512_maskz_loadu_epi32(load_masks[part],
scores_pre_substitution + part * 16);
pre_insert_vecs[part].zmm = _mm512_maskz_loadu_epi32(load_masks[part], scores_pre_insertion + part * 16);
pre_delete_vecs[part].zmm = _mm512_maskz_loadu_epi32(load_masks[part], scores_pre_deletion + part * 16);
}
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i32_vecs[0].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i32_vecs[1].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 1));
cost_of_substitution_i32_vecs[2].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 2));
cost_of_substitution_i32_vecs[3].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 3));
for (size_t part = 0; part != 4; ++part) {
cost_if_substitution_vecs[part].zmm = _mm512_add_epi32(pre_substitution_vecs[part].zmm,
cost_of_substitution_i32_vecs[part].zmm);
cost_if_gap_vecs[part].zmm = _mm512_add_epi32(
_mm512_max_epi32(pre_insert_vecs[part].zmm, pre_delete_vecs[part].zmm), gap_cost_vec.zmm);
cell_score_vecs[part].zmm = _mm512_max_epi32(cost_if_substitution_vecs[part].zmm,
cost_if_gap_vecs[part].zmm);
cell_score_vecs[part].zmm = _mm512_max_epi32(cell_score_vecs[part].zmm, _mm512_setzero_epi32());
_mm512_mask_storeu_epi32(scores_new + part * 16, load_masks[part], cell_score_vecs[part].zmm);
}
}
SZ_NOINLINE void score_slice_trampoline_( u8_t const *first_reversed_classes, u8_t const *second_classes, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t *scores_new, u512_vec_t gap_cost_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_aligned64chars( first_reversed_classes + progress, second_classes + progress, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_new + progress, gap_cost_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t *scores_new, executor_type_ &&executor = {}) noexcept {
u8_t const *first_reversed_classes = (u8_t const *)first_reversed_slice;
u8_t const *second_classes = (u8_t const *)second_slice;
i32_t *const scores_new_begin = scores_new;
u512_vec_t gap_cost_vec;
gap_cost_vec.zmm = _mm512_set1_epi32(this->gap_costs_.open_or_extend);
if (length <= step_k) {
slice_upto64chars( first_reversed_classes, second_classes, length, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec);
}
else {
head_body_tail_t hbt = head_body_tail<step_k>(scores_new, length);
if (hbt.head)
slice_upto64chars( first_reversed_classes, second_classes, hbt.head, scores_pre_substitution, scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec);
first_reversed_classes += hbt.head, second_classes += hbt.head, scores_pre_substitution += hbt.head,
scores_pre_insertion += hbt.head, scores_pre_deletion += hbt.head, scores_new += hbt.head;
if (hbt.tail)
slice_upto64chars( first_reversed_classes + hbt.body, second_classes + hbt.body, hbt.tail, scores_pre_substitution + hbt.body, scores_pre_insertion + hbt.body, scores_pre_deletion + hbt.body, scores_new + hbt.body, gap_cost_vec);
size_t const body_pages = hbt.body / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_classes, second_classes, scores_pre_substitution,
scores_pre_insertion, scores_pre_deletion, scores_new, gap_cost_vec, from, to);
});
}
i32_t best_in_diagonal = this->best_score_;
for (size_t i = 0; i != length; ++i) best_in_diagonal = sz_max_of_two(best_in_diagonal, scores_new_begin[i]);
this->best_score_ = best_in_diagonal;
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, affine_gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, affine_gap_costs_t,
sz_maximize_score_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, affine_gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_maximize_score_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
substitution_lookup_icelake_t lookup_;
void prepare(bool transpose) noexcept {
lookup_.reload_costs(this->substituter_.class_substitution_costs, transpose);
}
SZ_INLINE void slice_upto64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, size_t n, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t const *scores_running_insertions, i16_t const *scores_running_deletions, i16_t *scores_new, i16_t *scores_new_insertions, i16_t *scores_new_deletions, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec) const noexcept {
u512_vec_t first_vec, second_vec, cost_of_substitution_i8_vec, cost_of_substitution_i16_vecs[2];
__mmask64 const load_mask = sz_u64_mask_until_(n);
__mmask32 const load_masks[2] = {(__mmask32)load_mask, (__mmask32)(load_mask >> 32)};
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i16_vecs[0].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i16_vecs[1].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 1));
for (size_t part = 0; part != 2; ++part) {
__mmask32 const part_mask = load_masks[part];
size_t const offset = part * 32;
u512_vec_t pre_substitution, pre_insert_open, pre_delete_open, run_insert, run_delete;
u512_vec_t cost_if_insert, cost_if_delete, cell_score;
pre_substitution.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_pre_substitution + offset);
pre_insert_open.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_pre_insertion + offset);
pre_delete_open.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_pre_deletion + offset);
run_insert.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_running_insertions + offset);
run_delete.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_running_deletions + offset);
cost_if_insert.zmm = _mm512_max_epi16(_mm512_add_epi16(run_insert.zmm, gap_expand_vec.zmm),
_mm512_add_epi16(pre_insert_open.zmm, gap_open_vec.zmm));
cost_if_delete.zmm = _mm512_max_epi16(_mm512_add_epi16(run_delete.zmm, gap_expand_vec.zmm),
_mm512_add_epi16(pre_delete_open.zmm, gap_open_vec.zmm));
cell_score.zmm = _mm512_max_epi16(
_mm512_add_epi16(pre_substitution.zmm, cost_of_substitution_i16_vecs[part].zmm),
_mm512_max_epi16(cost_if_insert.zmm, cost_if_delete.zmm));
_mm512_mask_storeu_epi16(scores_new + offset, part_mask, cell_score.zmm);
_mm512_mask_storeu_epi16(scores_new_insertions + offset, part_mask, cost_if_insert.zmm);
_mm512_mask_storeu_epi16(scores_new_deletions + offset, part_mask, cost_if_delete.zmm);
}
}
SZ_NOINLINE void score_slice_trampoline_( u8_t const *first_reversed_classes, u8_t const *second_classes, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t const *scores_running_insertions, i16_t const *scores_running_deletions, i16_t *scores_new, i16_t *scores_new_insertions, i16_t *scores_new_deletions, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_upto64chars( first_reversed_classes + progress, second_classes + progress, step_k, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, gap_open_vec, gap_expand_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t const *scores_running_insertions, i16_t const *scores_running_deletions, i16_t *scores_new, i16_t *scores_new_insertions, i16_t *scores_new_deletions, executor_type_ &&executor = {}) noexcept {
u8_t const *first_reversed_classes = (u8_t const *)first_reversed_slice;
u8_t const *second_classes = (u8_t const *)second_slice;
u512_vec_t gap_open_vec, gap_expand_vec;
gap_open_vec.zmm = _mm512_set1_epi16(this->gap_costs_.open);
gap_expand_vec.zmm = _mm512_set1_epi16(this->gap_costs_.extend);
size_t const body_pages = length / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_classes, second_classes, scores_pre_substitution,
scores_pre_insertion, scores_pre_deletion, scores_running_insertions,
scores_running_deletions, scores_new, scores_new_insertions, scores_new_deletions,
gap_open_vec, gap_expand_vec, from, to);
});
size_t const progress = body_pages * step_k;
size_t const tail = length - progress;
if (tail)
slice_upto64chars( first_reversed_classes + progress, second_classes + progress, tail, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, gap_open_vec, gap_expand_vec);
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, affine_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, affine_gap_costs_t,
sz_maximize_score_k, sz_similarity_local_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, i16_t, error_costs_32x32_t, affine_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_maximize_score_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_local_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
substitution_lookup_icelake_t lookup_;
void prepare(bool transpose) noexcept {
lookup_.reload_costs(this->substituter_.class_substitution_costs, transpose);
}
SZ_INLINE void slice_upto64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, size_t n, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t const *scores_running_insertions, i16_t const *scores_running_deletions, i16_t *scores_new, i16_t *scores_new_insertions, i16_t *scores_new_deletions, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec) const noexcept {
u512_vec_t first_vec, second_vec, cost_of_substitution_i8_vec, cost_of_substitution_i16_vecs[2];
__mmask64 const load_mask = sz_u64_mask_until_(n);
__mmask32 const load_masks[2] = {(__mmask32)load_mask, (__mmask32)(load_mask >> 32)};
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i16_vecs[0].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i16_vecs[1].zmm = _mm512_cvtepi8_epi16(
_mm512_extracti64x4_epi64(cost_of_substitution_i8_vec.zmm, 1));
for (size_t part = 0; part != 2; ++part) {
__mmask32 const part_mask = load_masks[part];
size_t const offset = part * 32;
u512_vec_t pre_substitution, pre_insert_open, pre_delete_open, run_insert, run_delete;
u512_vec_t cost_if_insert, cost_if_delete, cell_score;
pre_substitution.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_pre_substitution + offset);
pre_insert_open.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_pre_insertion + offset);
pre_delete_open.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_pre_deletion + offset);
run_insert.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_running_insertions + offset);
run_delete.zmm = _mm512_maskz_loadu_epi16(part_mask, scores_running_deletions + offset);
cost_if_insert.zmm = _mm512_max_epi16(_mm512_add_epi16(run_insert.zmm, gap_expand_vec.zmm),
_mm512_add_epi16(pre_insert_open.zmm, gap_open_vec.zmm));
cost_if_delete.zmm = _mm512_max_epi16(_mm512_add_epi16(run_delete.zmm, gap_expand_vec.zmm),
_mm512_add_epi16(pre_delete_open.zmm, gap_open_vec.zmm));
cell_score.zmm = _mm512_max_epi16(
_mm512_max_epi16(_mm512_add_epi16(pre_substitution.zmm, cost_of_substitution_i16_vecs[part].zmm),
_mm512_setzero_epi32()),
_mm512_max_epi16(cost_if_insert.zmm, cost_if_delete.zmm));
_mm512_mask_storeu_epi16(scores_new + offset, part_mask, cell_score.zmm);
_mm512_mask_storeu_epi16(scores_new_insertions + offset, part_mask, cost_if_insert.zmm);
_mm512_mask_storeu_epi16(scores_new_deletions + offset, part_mask, cost_if_delete.zmm);
}
}
SZ_NOINLINE void score_slice_trampoline_( u8_t const *first_reversed_classes, u8_t const *second_classes, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t const *scores_running_insertions, i16_t const *scores_running_deletions, i16_t *scores_new, i16_t *scores_new_insertions, i16_t *scores_new_deletions, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_upto64chars( first_reversed_classes + progress, second_classes + progress, step_k, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, gap_open_vec, gap_expand_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, i16_t const *scores_pre_substitution, i16_t const *scores_pre_insertion, i16_t const *scores_pre_deletion, i16_t const *scores_running_insertions, i16_t const *scores_running_deletions, i16_t *scores_new, i16_t *scores_new_insertions, i16_t *scores_new_deletions, executor_type_ &&executor = {}) noexcept {
u8_t const *first_reversed_classes = (u8_t const *)first_reversed_slice;
u8_t const *second_classes = (u8_t const *)second_slice;
i16_t *const scores_new_begin = scores_new;
u512_vec_t gap_open_vec, gap_expand_vec;
gap_open_vec.zmm = _mm512_set1_epi16(this->gap_costs_.open);
gap_expand_vec.zmm = _mm512_set1_epi16(this->gap_costs_.extend);
size_t const body_pages = length / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_classes, second_classes, scores_pre_substitution,
scores_pre_insertion, scores_pre_deletion, scores_running_insertions,
scores_running_deletions, scores_new, scores_new_insertions, scores_new_deletions,
gap_open_vec, gap_expand_vec, from, to);
});
size_t const progress = body_pages * step_k;
size_t const tail = length - progress;
if (tail)
slice_upto64chars( first_reversed_classes + progress, second_classes + progress, tail, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, gap_open_vec, gap_expand_vec);
i16_t best_in_diagonal = this->best_score_;
for (size_t i = 0; i != length; ++i) best_in_diagonal = sz_max_of_two(best_in_diagonal, scores_new_begin[i]);
this->best_score_ = best_in_diagonal;
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, affine_gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, affine_gap_costs_t,
sz_maximize_score_k, sz_similarity_global_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, affine_gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_maximize_score_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_global_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
substitution_lookup_icelake_t lookup_;
void prepare(bool transpose) noexcept {
lookup_.reload_costs(this->substituter_.class_substitution_costs, transpose);
}
SZ_INLINE void slice_upto64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, size_t n, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t const *scores_running_insertions, i32_t const *scores_running_deletions, i32_t *scores_new, i32_t *scores_new_insertions, i32_t *scores_new_deletions, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec) const noexcept {
u512_vec_t first_vec, second_vec, cost_of_substitution_i8_vec, cost_of_substitution_i32_vecs[4];
__mmask64 const load_mask = sz_u64_mask_until_(n);
__mmask16 const load_masks[4] = {(__mmask16)load_mask, (__mmask16)(load_mask >> 16),
(__mmask16)(load_mask >> 32), (__mmask16)(load_mask >> 48)};
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i32_vecs[0].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i32_vecs[1].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 1));
cost_of_substitution_i32_vecs[2].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 2));
cost_of_substitution_i32_vecs[3].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 3));
for (size_t part = 0; part != 4; ++part) {
__mmask16 const part_mask = load_masks[part];
size_t const offset = part * 16;
u512_vec_t pre_substitution, pre_insert_open, pre_delete_open, run_insert, run_delete;
u512_vec_t cost_if_insert, cost_if_delete, cell_score;
pre_substitution.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_pre_substitution + offset);
pre_insert_open.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_pre_insertion + offset);
pre_delete_open.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_pre_deletion + offset);
run_insert.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_running_insertions + offset);
run_delete.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_running_deletions + offset);
cost_if_insert.zmm = _mm512_max_epi32(_mm512_add_epi32(run_insert.zmm, gap_expand_vec.zmm),
_mm512_add_epi32(pre_insert_open.zmm, gap_open_vec.zmm));
cost_if_delete.zmm = _mm512_max_epi32(_mm512_add_epi32(run_delete.zmm, gap_expand_vec.zmm),
_mm512_add_epi32(pre_delete_open.zmm, gap_open_vec.zmm));
cell_score.zmm = _mm512_max_epi32(
_mm512_add_epi32(pre_substitution.zmm, cost_of_substitution_i32_vecs[part].zmm),
_mm512_max_epi32(cost_if_insert.zmm, cost_if_delete.zmm));
_mm512_mask_storeu_epi32(scores_new + offset, part_mask, cell_score.zmm);
_mm512_mask_storeu_epi32(scores_new_insertions + offset, part_mask, cost_if_insert.zmm);
_mm512_mask_storeu_epi32(scores_new_deletions + offset, part_mask, cost_if_delete.zmm);
}
}
SZ_NOINLINE void score_slice_trampoline_( u8_t const *first_reversed_classes, u8_t const *second_classes, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t const *scores_running_insertions, i32_t const *scores_running_deletions, i32_t *scores_new, i32_t *scores_new_insertions, i32_t *scores_new_deletions, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_upto64chars( first_reversed_classes + progress, second_classes + progress, step_k, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, gap_open_vec, gap_expand_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t const *scores_running_insertions, i32_t const *scores_running_deletions, i32_t *scores_new, i32_t *scores_new_insertions, i32_t *scores_new_deletions, executor_type_ &&executor = {}) noexcept {
u8_t const *first_reversed_classes = (u8_t const *)first_reversed_slice;
u8_t const *second_classes = (u8_t const *)second_slice;
u512_vec_t gap_open_vec, gap_expand_vec;
gap_open_vec.zmm = _mm512_set1_epi32(this->gap_costs_.open);
gap_expand_vec.zmm = _mm512_set1_epi32(this->gap_costs_.extend);
size_t const body_pages = length / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_classes, second_classes, scores_pre_substitution,
scores_pre_insertion, scores_pre_deletion, scores_running_insertions,
scores_running_deletions, scores_new, scores_new_insertions, scores_new_deletions,
gap_open_vec, gap_expand_vec, from, to);
});
size_t const progress = body_pages * step_k;
size_t const tail = length - progress;
if (tail)
slice_upto64chars( first_reversed_classes + progress, second_classes + progress, tail, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, gap_open_vec, gap_expand_vec);
if (length == 1) this->last_score_ = scores_new[0];
}
};
template <sz_capability_t capability_>
struct tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, affine_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, capability_, std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>>
: public tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, affine_gap_costs_t,
sz_maximize_score_k, sz_similarity_local_k, sz_cap_serial_k, void> {
using tile_scorer<char const *, char const *, i32_t, error_costs_32x32_t, affine_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_serial_k, void>::tile_scorer;
static constexpr sz_similarity_objective_t objective_k = sz_maximize_score_k;
static constexpr sz_similarity_locality_t locality_k = sz_similarity_local_k;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t step_k = 64;
substitution_lookup_icelake_t lookup_;
void prepare(bool transpose) noexcept {
lookup_.reload_costs(this->substituter_.class_substitution_costs, transpose);
}
SZ_INLINE void slice_upto64chars( u8_t const *first_reversed_slice, u8_t const *second_slice, size_t n, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t const *scores_running_insertions, i32_t const *scores_running_deletions, i32_t *scores_new, i32_t *scores_new_insertions, i32_t *scores_new_deletions, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec) const noexcept {
u512_vec_t first_vec, second_vec, cost_of_substitution_i8_vec, cost_of_substitution_i32_vecs[4];
__mmask64 const load_mask = sz_u64_mask_until_(n);
__mmask16 const load_masks[4] = {(__mmask16)load_mask, (__mmask16)(load_mask >> 16),
(__mmask16)(load_mask >> 32), (__mmask16)(load_mask >> 48)};
first_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, first_reversed_slice);
second_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, second_slice);
cost_of_substitution_i8_vec = lookup_.lookup64(first_vec, second_vec);
cost_of_substitution_i32_vecs[0].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 0));
cost_of_substitution_i32_vecs[1].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 1));
cost_of_substitution_i32_vecs[2].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 2));
cost_of_substitution_i32_vecs[3].zmm = _mm512_cvtepi8_epi32(
_mm512_extracti32x4_epi32(cost_of_substitution_i8_vec.zmm, 3));
for (size_t part = 0; part != 4; ++part) {
__mmask16 const part_mask = load_masks[part];
size_t const offset = part * 16;
u512_vec_t pre_substitution, pre_insert_open, pre_delete_open, run_insert, run_delete;
u512_vec_t cost_if_insert, cost_if_delete, cell_score;
pre_substitution.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_pre_substitution + offset);
pre_insert_open.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_pre_insertion + offset);
pre_delete_open.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_pre_deletion + offset);
run_insert.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_running_insertions + offset);
run_delete.zmm = _mm512_maskz_loadu_epi32(part_mask, scores_running_deletions + offset);
cost_if_insert.zmm = _mm512_max_epi32(_mm512_add_epi32(run_insert.zmm, gap_expand_vec.zmm),
_mm512_add_epi32(pre_insert_open.zmm, gap_open_vec.zmm));
cost_if_delete.zmm = _mm512_max_epi32(_mm512_add_epi32(run_delete.zmm, gap_expand_vec.zmm),
_mm512_add_epi32(pre_delete_open.zmm, gap_open_vec.zmm));
cell_score.zmm = _mm512_max_epi32(
_mm512_max_epi32(_mm512_add_epi32(pre_substitution.zmm, cost_of_substitution_i32_vecs[part].zmm),
_mm512_setzero_epi32()),
_mm512_max_epi32(cost_if_insert.zmm, cost_if_delete.zmm));
_mm512_mask_storeu_epi32(scores_new + offset, part_mask, cell_score.zmm);
_mm512_mask_storeu_epi32(scores_new_insertions + offset, part_mask, cost_if_insert.zmm);
_mm512_mask_storeu_epi32(scores_new_deletions + offset, part_mask, cost_if_delete.zmm);
}
}
SZ_NOINLINE void score_slice_trampoline_( u8_t const *first_reversed_classes, u8_t const *second_classes, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t const *scores_running_insertions, i32_t const *scores_running_deletions, i32_t *scores_new, i32_t *scores_new_insertions, i32_t *scores_new_deletions, u512_vec_t gap_open_vec, u512_vec_t gap_expand_vec, size_t from, size_t to) noexcept {
for (size_t page = from; page < to; ++page) {
size_t const progress = page * step_k;
slice_upto64chars( first_reversed_classes + progress, second_classes + progress, step_k, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, gap_open_vec, gap_expand_vec);
}
}
template <typename executor_type_ = dummy_executor_t>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
void operator()( char const *first_reversed_slice, char const *second_slice, size_t const length, i32_t const *scores_pre_substitution, i32_t const *scores_pre_insertion, i32_t const *scores_pre_deletion, i32_t const *scores_running_insertions, i32_t const *scores_running_deletions, i32_t *scores_new, i32_t *scores_new_insertions, i32_t *scores_new_deletions, executor_type_ &&executor = {}) noexcept {
u8_t const *first_reversed_classes = (u8_t const *)first_reversed_slice;
u8_t const *second_classes = (u8_t const *)second_slice;
i32_t *const scores_new_begin = scores_new;
u512_vec_t gap_open_vec, gap_expand_vec;
gap_open_vec.zmm = _mm512_set1_epi32(this->gap_costs_.open);
gap_expand_vec.zmm = _mm512_set1_epi32(this->gap_costs_.extend);
size_t const body_pages = length / step_k;
executor.for_slices(body_pages, [&](size_t from, size_t to) noexcept {
score_slice_trampoline_(first_reversed_classes, second_classes, scores_pre_substitution,
scores_pre_insertion, scores_pre_deletion, scores_running_insertions,
scores_running_deletions, scores_new, scores_new_insertions, scores_new_deletions,
gap_open_vec, gap_expand_vec, from, to);
});
size_t const progress = body_pages * step_k;
size_t const tail = length - progress;
if (tail)
slice_upto64chars( first_reversed_classes + progress, second_classes + progress, tail, scores_pre_substitution + progress, scores_pre_insertion + progress, scores_pre_deletion + progress, scores_running_insertions + progress, scores_running_deletions + progress, scores_new + progress, scores_new_insertions + progress, scores_new_deletions + progress, gap_open_vec, gap_expand_vec);
i32_t best_in_diagonal = this->best_score_;
for (size_t i = 0; i != length; ++i) best_in_diagonal = sz_max_of_two(best_in_diagonal, scores_new_begin[i]);
this->best_score_ = best_in_diagonal;
}
};
template <typename char_type_, typename score_type_, typename substituter_type_, typename gap_costs_type_,
sz_similarity_objective_t objective_, sz_similarity_locality_t locality_>
struct horizontal_walker<char_type_, score_type_, substituter_type_, gap_costs_type_, objective_, locality_,
sz_cap_icelake_k, void>
: public horizontal_walker<char_type_, score_type_, substituter_type_, gap_costs_type_, objective_, locality_,
sz_cap_serial_k, void> {
using base_t = horizontal_walker<char_type_, score_type_, substituter_type_, gap_costs_type_, objective_, locality_,
sz_cap_serial_k, void>;
using base_t::base_t;
using base_t::operator();
};
template <typename score_type_, sz_similarity_objective_t objective_, sz_similarity_locality_t locality_>
struct diagonal_walker<char, score_type_, error_costs_32x32_t, linear_gap_costs_t, objective_, locality_,
sz_cap_icelake_k, void> {
using char_t = char;
using score_t = score_type_;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = linear_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = locality_;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t step_classes_k = 64;
using tile_scorer_t = tile_scorer<char_t const *, char_t const *, score_t, substituter_t, gap_costs_t, objective_k,
locality_k, capability_k>;
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
diagonal_walker() noexcept {}
diagonal_walker(substituter_t subs, linear_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
struct layout_t {
size_t previous_scores = 0; size_t current_scores = 0;
size_t next_scores = 0;
size_t shorter_reversed = 0; size_t shorter_reversed_classes = 0; size_t longer_classes = 0; size_t total = 0; constexpr operator size_t() const noexcept { return total; }
};
layout_t layout(span<char_t const> first, span<char_t const> second, cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = sz_min_of_two(first.size(), second.size());
size_t const longer_length = sz_max_of_two(first.size(), second.size());
size_t const diagonal_bytes = sizeof(score_t) * (shorter_length + 1); size_t const shorter_stream_bytes = shorter_length + step_classes_k; size_t const longer_stream_bytes = longer_length + step_classes_k;
scratch_amount_t amount {specs.cache_line_width};
layout_t at;
at.previous_scores = amount, amount += diagonal_bytes;
at.current_scores = amount, amount += diagonal_bytes;
at.next_scores = amount, amount += diagonal_bytes;
at.shorter_reversed = amount, amount += shorter_stream_bytes;
at.shorter_reversed_classes = amount, amount += shorter_stream_bytes;
at.longer_classes = amount, amount += longer_stream_bytes;
at.total = amount;
return at;
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, score_t &result_ref,
scratch_space_t scratch_space, executor_type_ &&executor,
cpu_specs_t const &specs) const noexcept {
if (first.empty() || second.empty()) {
result_ref = 0;
if constexpr (locality_k == sz_similarity_global_k) {
if (!first.empty() && second.empty()) { result_ref = gap_costs_.open_or_extend * first.size(); }
else if (first.empty() && !second.empty()) { result_ref = gap_costs_.open_or_extend * second.size(); }
}
return status_t::success_k;
}
char_t const *shorter = first.data(), *longer = second.data();
size_t shorter_length = first.size(), longer_length = second.size();
bool transpose = false;
if (shorter_length > longer_length) {
trivial_swap(shorter, longer);
trivial_swap(shorter_length, longer_length);
transpose = true;
}
size_t const shorter_dim = shorter_length + 1;
size_t const longer_dim = longer_length + 1;
size_t const diagonals_count = shorter_dim + longer_dim - 1;
size_t const max_diagonal_length = shorter_length + 1;
layout_t const at = layout(first, second, specs);
if (scratch_space.size() < at.total) return status_t::bad_alloc_k;
score_t *previous_scores = (score_t *)(scratch_space.data() + at.previous_scores);
score_t *current_scores = (score_t *)(scratch_space.data() + at.current_scores);
score_t *next_scores = (score_t *)(scratch_space.data() + at.next_scores);
char_t *const shorter_reversed = (char_t *)(scratch_space.data() + at.shorter_reversed);
char_t *const shorter_reversed_classes = (char_t *)(scratch_space.data() + at.shorter_reversed_classes);
char_t *const longer_classes = (char_t *)(scratch_space.data() + at.longer_classes);
for (size_t i = 0; i != shorter_length; ++i) shorter_reversed[i] = shorter[shorter_length - 1 - i];
tile_scorer_t scorer {substituter_, gap_costs_};
scorer.lookup_.reload_classes(substituter_.byte_to_class);
scorer.prepare(transpose);
classify_into_(scorer.lookup_, shorter_reversed, shorter_length, shorter_reversed_classes);
classify_into_(scorer.lookup_, longer, longer_length, longer_classes);
scorer.init_score(previous_scores[0], 0);
scorer.init_score(current_scores[0], 1);
scorer.init_score(current_scores[1], 1);
size_t next_diagonal_index = 2;
for (; next_diagonal_index < shorter_dim; ++next_diagonal_index) {
size_t const next_diagonal_length = next_diagonal_index + 1;
scorer( shorter_reversed_classes + shorter_length - next_diagonal_index + 1, longer_classes, next_diagonal_length - 2, previous_scores, current_scores, current_scores + 1, next_scores + 1, executor);
scorer.init_score(next_scores[0], next_diagonal_index);
scorer.init_score(next_scores[next_diagonal_length - 1], next_diagonal_index);
rotate_three(previous_scores, current_scores, next_scores);
}
for (; next_diagonal_index < longer_dim; ++next_diagonal_index) {
size_t const next_diagonal_length = shorter_dim;
scorer( shorter_reversed_classes + shorter_length - shorter_dim + 1, longer_classes + next_diagonal_index - shorter_dim, next_diagonal_length - 1, previous_scores, current_scores, current_scores + 1, next_scores, executor);
scorer.init_score(next_scores[next_diagonal_length - 1], next_diagonal_index);
rotate_three(previous_scores, current_scores, next_scores);
sz_move_serial((ptr_t)(previous_scores), (ptr_t)(previous_scores + 1),
(max_diagonal_length - 1) * sizeof(score_t));
}
for (; next_diagonal_index < diagonals_count; ++next_diagonal_index) {
size_t const next_diagonal_length = diagonals_count - next_diagonal_index;
scorer( shorter_reversed_classes + shorter_length - shorter_dim + 1, longer_classes + next_diagonal_index - shorter_dim, next_diagonal_length, previous_scores, current_scores, current_scores + 1, next_scores, executor);
rotate_three(previous_scores, current_scores, next_scores);
previous_scores++;
}
result_ref = scorer.score();
return status_t::success_k;
}
private:
static void classify_into_(substitution_lookup_icelake_t const &lookup, char_t const *source, size_t length,
char_t *classes) noexcept {
u512_vec_t source_vec, classes_vec;
size_t progress = 0;
for (; progress + step_classes_k <= length; progress += step_classes_k) {
source_vec.zmm = _mm512_loadu_epi8(source + progress);
classes_vec = lookup.classify64(source_vec);
_mm512_storeu_epi8(classes + progress, classes_vec.zmm);
}
if (progress < length) {
__mmask64 const load_mask = sz_u64_mask_until_(length - progress);
source_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, source + progress);
classes_vec = lookup.classify64(source_vec);
_mm512_mask_storeu_epi8(classes + progress, load_mask, classes_vec.zmm);
}
}
};
template <typename score_type_, sz_similarity_objective_t objective_, sz_similarity_locality_t locality_>
struct diagonal_walker<char, score_type_, error_costs_32x32_t, affine_gap_costs_t, objective_, locality_,
sz_cap_icelake_k, void> {
using char_t = char;
using score_t = score_type_;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = affine_gap_costs_t;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = locality_;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t step_classes_k = 64;
using tile_scorer_t = tile_scorer<char_t const *, char_t const *, score_t, substituter_t, gap_costs_t, objective_k,
locality_k, capability_k>;
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
diagonal_walker() noexcept {}
diagonal_walker(substituter_t subs, affine_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
struct layout_t {
size_t previous_scores = 0; size_t current_scores = 0;
size_t next_scores = 0;
size_t current_inserts = 0; size_t next_inserts = 0;
size_t current_deletes = 0; size_t next_deletes = 0;
size_t shorter_reversed = 0; size_t shorter_reversed_classes = 0; size_t longer_classes = 0; size_t total = 0; constexpr operator size_t() const noexcept { return total; }
};
layout_t layout(span<char_t const> first, span<char_t const> second, cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = sz_min_of_two(first.size(), second.size());
size_t const longer_length = sz_max_of_two(first.size(), second.size());
size_t const diagonal_bytes = sizeof(score_t) * (shorter_length + 1); size_t const shorter_stream_bytes = shorter_length + step_classes_k; size_t const longer_stream_bytes = longer_length + step_classes_k;
scratch_amount_t amount {specs.cache_line_width};
layout_t at;
at.previous_scores = amount, amount += diagonal_bytes;
at.current_scores = amount, amount += diagonal_bytes;
at.next_scores = amount, amount += diagonal_bytes;
at.current_inserts = amount, amount += diagonal_bytes;
at.next_inserts = amount, amount += diagonal_bytes;
at.current_deletes = amount, amount += diagonal_bytes;
at.next_deletes = amount, amount += diagonal_bytes;
at.shorter_reversed = amount, amount += shorter_stream_bytes;
at.shorter_reversed_classes = amount, amount += shorter_stream_bytes;
at.longer_classes = amount, amount += longer_stream_bytes;
at.total = amount;
return at;
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, score_t &result_ref,
scratch_space_t scratch_space, executor_type_ &&executor,
cpu_specs_t const &specs) const noexcept {
if (first.empty() || second.empty()) {
result_ref = 0;
if constexpr (locality_k == sz_similarity_global_k) {
if (!first.empty() && second.empty()) {
result_ref = gap_costs_.open + gap_costs_.extend * (first.size() - 1);
}
else if (first.empty() && !second.empty()) {
result_ref = gap_costs_.open + gap_costs_.extend * (second.size() - 1);
}
}
return status_t::success_k;
}
char_t const *shorter = first.data(), *longer = second.data();
size_t shorter_length = first.size(), longer_length = second.size();
bool transpose = false;
if (shorter_length > longer_length) {
trivial_swap(shorter, longer);
trivial_swap(shorter_length, longer_length);
transpose = true;
}
size_t const shorter_dim = shorter_length + 1;
size_t const longer_dim = longer_length + 1;
size_t const diagonals_count = shorter_dim + longer_dim - 1;
size_t const max_diagonal_length = shorter_length + 1;
layout_t const at = layout(first, second, specs);
if (scratch_space.size() < at.total) return status_t::bad_alloc_k;
score_t *previous_scores = (score_t *)(scratch_space.data() + at.previous_scores);
score_t *current_scores = (score_t *)(scratch_space.data() + at.current_scores);
score_t *next_scores = (score_t *)(scratch_space.data() + at.next_scores);
score_t *current_inserts = (score_t *)(scratch_space.data() + at.current_inserts);
score_t *next_inserts = (score_t *)(scratch_space.data() + at.next_inserts);
score_t *current_deletes = (score_t *)(scratch_space.data() + at.current_deletes);
score_t *next_deletes = (score_t *)(scratch_space.data() + at.next_deletes);
char_t *const shorter_reversed = (char_t *)(scratch_space.data() + at.shorter_reversed);
char_t *const shorter_reversed_classes = (char_t *)(scratch_space.data() + at.shorter_reversed_classes);
char_t *const longer_classes = (char_t *)(scratch_space.data() + at.longer_classes);
for (size_t i = 0; i != shorter_length; ++i) shorter_reversed[i] = shorter[shorter_length - 1 - i];
tile_scorer_t scorer {substituter_, gap_costs_};
scorer.lookup_.reload_classes(substituter_.byte_to_class);
scorer.prepare(transpose);
classify_into_(scorer.lookup_, shorter_reversed, shorter_length, shorter_reversed_classes);
classify_into_(scorer.lookup_, longer, longer_length, longer_classes);
scorer.init_score(previous_scores[0], 0);
scorer.init_score(current_scores[0], 1);
scorer.init_score(current_scores[1], 1);
scorer.init_gap(current_inserts[0], 1);
scorer.init_gap(current_deletes[1], 1);
size_t next_diagonal_index = 2;
for (; next_diagonal_index < shorter_dim; ++next_diagonal_index) {
size_t const next_diagonal_length = next_diagonal_index + 1;
scorer( shorter_reversed_classes + shorter_length - next_diagonal_index + 1, longer_classes, next_diagonal_length - 2, previous_scores, current_scores, current_scores + 1, current_inserts, current_deletes + 1, next_scores + 1, next_inserts + 1, next_deletes + 1, executor);
scorer.init_score(next_scores[0], next_diagonal_index);
scorer.init_score(next_scores[next_diagonal_length - 1], next_diagonal_index);
scorer.init_gap(next_inserts[0], next_diagonal_index);
scorer.init_gap(next_deletes[next_diagonal_length - 1], next_diagonal_index);
rotate_three(previous_scores, current_scores, next_scores);
trivial_swap(current_inserts, next_inserts);
trivial_swap(current_deletes, next_deletes);
}
for (; next_diagonal_index < longer_dim; ++next_diagonal_index) {
size_t const next_diagonal_length = shorter_dim;
scorer( shorter_reversed_classes + shorter_length - shorter_dim + 1, longer_classes + next_diagonal_index - shorter_dim, next_diagonal_length - 1, previous_scores, current_scores, current_scores + 1, current_inserts, current_deletes + 1, next_scores, next_inserts, next_deletes, executor);
scorer.init_score(next_scores[next_diagonal_length - 1], next_diagonal_index);
scorer.init_gap(next_deletes[next_diagonal_length - 1], next_diagonal_index);
rotate_three(previous_scores, current_scores, next_scores);
trivial_swap(current_inserts, next_inserts);
trivial_swap(current_deletes, next_deletes);
sz_move_serial((ptr_t)(previous_scores), (ptr_t)(previous_scores + 1),
(max_diagonal_length - 1) * sizeof(score_t));
}
for (; next_diagonal_index < diagonals_count; ++next_diagonal_index) {
size_t const next_diagonal_length = diagonals_count - next_diagonal_index;
scorer( shorter_reversed_classes + shorter_length - shorter_dim + 1, longer_classes + next_diagonal_index - shorter_dim, next_diagonal_length, previous_scores, current_scores, current_scores + 1, current_inserts, current_deletes + 1, next_scores, next_inserts, next_deletes, executor);
rotate_three(previous_scores, current_scores, next_scores);
trivial_swap(current_inserts, next_inserts);
trivial_swap(current_deletes, next_deletes);
previous_scores++;
}
result_ref = scorer.score();
return status_t::success_k;
}
private:
static void classify_into_(substitution_lookup_icelake_t const &lookup, char_t const *source, size_t length,
char_t *classes) noexcept {
u512_vec_t source_vec, classes_vec;
size_t progress = 0;
for (; progress + step_classes_k <= length; progress += step_classes_k) {
source_vec.zmm = _mm512_loadu_epi8(source + progress);
classes_vec = lookup.classify64(source_vec);
_mm512_storeu_epi8(classes + progress, classes_vec.zmm);
}
if (progress < length) {
__mmask64 const load_mask = sz_u64_mask_until_(length - progress);
source_vec.zmm = _mm512_maskz_loadu_epi8(load_mask, source + progress);
classes_vec = lookup.classify64(source_vec);
_mm512_mask_storeu_epi8(classes + progress, load_mask, classes_vec.zmm);
}
}
};
template <>
struct needleman_wunsch_score<char, error_costs_32x32_t, linear_gap_costs_t, sz_caps_sil_k> {
using char_t = char;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = linear_gap_costs_t;
static constexpr size_t diagonal_buffers_count_k = 3;
using diagonal_i16_t = diagonal_walker<char_t, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_icelake_k>;
using diagonal_i32_t = diagonal_walker<char_t, i32_t, substituter_t, gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_icelake_k>;
using diagonal_i64_t = diagonal_walker<char_t, i64_t, substituter_t, gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_serial_k>;
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
needleman_wunsch_score() noexcept {}
needleman_wunsch_score(substituter_t subs, linear_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(span<char_t const> first, span<char_t const> second,
cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = std::min(first.size(), second.size());
size_t const longer_length = std::max(first.size(), second.size());
size_t const max_diagonal_length = shorter_length + 1;
size_t const padded_diagonal_length =
round_up_to_multiple(sizeof(i64_t) * max_diagonal_length, specs.cache_line_width) / sizeof(i64_t);
size_t const padded_shorter_stream_length = round_up_to_multiple(
shorter_length + diagonal_i16_t::step_classes_k, specs.cache_line_width);
size_t const padded_longer_stream_length = round_up_to_multiple(longer_length + diagonal_i16_t::step_classes_k,
specs.cache_line_width);
return sizeof(i64_t) * padded_diagonal_length * diagonal_buffers_count_k + padded_shorter_stream_length * 2 +
padded_longer_stream_length;
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, ssize_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
using diagonal_memory_requirements_t = diagonal_memory_requirements<ssize_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
status_t status = status_t::success_k;
if (requirements.bytes_per_cell <= 2) {
i16_t result_i16;
status = diagonal_i16_t {substituter_, gap_costs_}(first, second, result_i16, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i16;
}
else if (requirements.bytes_per_cell == 4) {
i32_t result_i32;
status = diagonal_i32_t {substituter_, gap_costs_}(first, second, result_i32, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i32;
}
else if (requirements.bytes_per_cell == 8) {
i64_t result_i64;
status = diagonal_i64_t {substituter_, gap_costs_}(first, second, result_i64, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i64;
}
return status;
}
};
template <>
struct needleman_wunsch_score<char, error_costs_32x32_t, affine_gap_costs_t, sz_caps_sil_k> {
using char_t = char;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = affine_gap_costs_t;
static constexpr size_t diagonal_buffers_count_k = 7;
using diagonal_i16_t = diagonal_walker<char_t, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_icelake_k>;
using diagonal_i32_t = diagonal_walker<char_t, i32_t, substituter_t, gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_icelake_k>;
using diagonal_i64_t = diagonal_walker<char_t, i64_t, substituter_t, gap_costs_t, sz_maximize_score_k,
sz_similarity_global_k, sz_cap_serial_k>;
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
needleman_wunsch_score() noexcept {}
needleman_wunsch_score(substituter_t subs, affine_gap_costs_t gaps) noexcept
: substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(span<char_t const> first, span<char_t const> second,
cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = std::min(first.size(), second.size());
size_t const longer_length = std::max(first.size(), second.size());
size_t const max_diagonal_length = shorter_length + 1;
size_t const padded_diagonal_length =
round_up_to_multiple(sizeof(i64_t) * max_diagonal_length, specs.cache_line_width) / sizeof(i64_t);
size_t const padded_shorter_stream_length = round_up_to_multiple(
shorter_length + diagonal_i16_t::step_classes_k, specs.cache_line_width);
size_t const padded_longer_stream_length = round_up_to_multiple(longer_length + diagonal_i16_t::step_classes_k,
specs.cache_line_width);
return sizeof(i64_t) * padded_diagonal_length * diagonal_buffers_count_k + padded_shorter_stream_length * 2 +
padded_longer_stream_length;
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, ssize_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
using diagonal_memory_requirements_t = diagonal_memory_requirements<ssize_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
status_t status = status_t::success_k;
if (requirements.bytes_per_cell <= 2) {
i16_t result_i16;
status = diagonal_i16_t {substituter_, gap_costs_}(first, second, result_i16, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i16;
}
else if (requirements.bytes_per_cell == 4) {
i32_t result_i32;
status = diagonal_i32_t {substituter_, gap_costs_}(first, second, result_i32, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i32;
}
else if (requirements.bytes_per_cell == 8) {
i64_t result_i64;
status = diagonal_i64_t {substituter_, gap_costs_}(first, second, result_i64, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i64;
}
return status;
}
};
template <>
struct smith_waterman_score<char, error_costs_32x32_t, linear_gap_costs_t, sz_caps_sil_k> {
using char_t = char;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = linear_gap_costs_t;
static constexpr size_t diagonal_buffers_count_k = 3;
using diagonal_i16_t = diagonal_walker<char_t, i16_t, substituter_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_icelake_k>;
using diagonal_i32_t = diagonal_walker<char_t, i32_t, substituter_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_icelake_k>;
using diagonal_i64_t = diagonal_walker<char_t, i64_t, substituter_t, linear_gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_serial_k>;
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
smith_waterman_score() noexcept {}
smith_waterman_score(substituter_t subs, linear_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(span<char_t const> first, span<char_t const> second,
cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = std::min(first.size(), second.size());
size_t const longer_length = std::max(first.size(), second.size());
size_t const max_diagonal_length = shorter_length + 1;
size_t const padded_diagonal_length =
round_up_to_multiple(sizeof(i64_t) * max_diagonal_length, specs.cache_line_width) / sizeof(i64_t);
size_t const padded_shorter_stream_length = round_up_to_multiple(
shorter_length + diagonal_i16_t::step_classes_k, specs.cache_line_width);
size_t const padded_longer_stream_length = round_up_to_multiple(longer_length + diagonal_i16_t::step_classes_k,
specs.cache_line_width);
return sizeof(i64_t) * padded_diagonal_length * diagonal_buffers_count_k + padded_shorter_stream_length * 2 +
padded_longer_stream_length;
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, ssize_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
using diagonal_memory_requirements_t = diagonal_memory_requirements<ssize_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
status_t status = status_t::success_k;
if (requirements.bytes_per_cell <= 2) {
i16_t result_i16;
status = diagonal_i16_t {substituter_, gap_costs_}(first, second, result_i16, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i16;
}
else if (requirements.bytes_per_cell == 4) {
i32_t result_i32;
status = diagonal_i32_t {substituter_, gap_costs_}(first, second, result_i32, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i32;
}
else if (requirements.bytes_per_cell == 8) {
i64_t result_i64;
status = diagonal_i64_t {substituter_, gap_costs_}(first, second, result_i64, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i64;
}
return status;
}
};
template <>
struct smith_waterman_score<char, error_costs_32x32_t, affine_gap_costs_t, sz_caps_sil_k> {
using char_t = char;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = affine_gap_costs_t;
static constexpr size_t diagonal_buffers_count_k = 7;
using diagonal_i16_t = diagonal_walker<char_t, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_icelake_k>;
using diagonal_i32_t = diagonal_walker<char_t, i32_t, substituter_t, gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_icelake_k>;
using diagonal_i64_t = diagonal_walker<char_t, i64_t, substituter_t, gap_costs_t, sz_maximize_score_k,
sz_similarity_local_k, sz_cap_serial_k>;
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
smith_waterman_score() noexcept {}
smith_waterman_score(substituter_t subs, affine_gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(span<char_t const> first, span<char_t const> second,
cpu_specs_t const &specs) const noexcept {
size_t const shorter_length = std::min(first.size(), second.size());
size_t const longer_length = std::max(first.size(), second.size());
size_t const max_diagonal_length = shorter_length + 1;
size_t const padded_diagonal_length =
round_up_to_multiple(sizeof(i64_t) * max_diagonal_length, specs.cache_line_width) / sizeof(i64_t);
size_t const padded_shorter_stream_length = round_up_to_multiple(
shorter_length + diagonal_i16_t::step_classes_k, specs.cache_line_width);
size_t const padded_longer_stream_length = round_up_to_multiple(longer_length + diagonal_i16_t::step_classes_k,
specs.cache_line_width);
return sizeof(i64_t) * padded_diagonal_length * diagonal_buffers_count_k + padded_shorter_stream_length * 2 +
padded_longer_stream_length;
}
template <typename executor_type_>
#if SZ_HAS_CONCEPTS_
requires executor_like<executor_type_>
#endif
status_t operator()(span<char_t const> const &first, span<char_t const> const &second, ssize_t &result_ref,
scratch_space_t scratch_space, executor_type_ &executor,
cpu_specs_t const &specs) const noexcept {
using diagonal_memory_requirements_t = diagonal_memory_requirements<ssize_t>;
diagonal_memory_requirements_t requirements( first.size(), second.size(), gap_type<gap_costs_t>(), substituter_.magnitude(), gap_costs_.magnitude(), sizeof(char_t), specs.cache_line_width);
status_t status = status_t::success_k;
if (requirements.bytes_per_cell <= 2) {
i16_t result_i16;
status = diagonal_i16_t {substituter_, gap_costs_}(first, second, result_i16, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i16;
}
else if (requirements.bytes_per_cell == 4) {
i32_t result_i32;
status = diagonal_i32_t {substituter_, gap_costs_}(first, second, result_i32, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i32;
}
else if (requirements.bytes_per_cell == 8) {
i64_t result_i64;
status = diagonal_i64_t {substituter_, gap_costs_}(first, second, result_i64, scratch_space, executor,
specs);
if (status == status_t::success_k) result_ref = result_i64;
}
return status;
}
};
#pragma region Inter Sequence Candidate Lanes
template <typename gap_costs_type_, sz_similarity_locality_t locality_, sz_similarity_objective_t objective_>
struct candidate_lane_walker<char, i16_t, error_costs_32x32_t, gap_costs_type_, objective_, locality_, sz_cap_icelake_k,
32, void> {
using char_t = char;
using score_t = i16_t;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = gap_costs_type_;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = locality_;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 32;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static constexpr bool is_affine_k = is_same_type<gap_costs_type_, affine_gap_costs_t>::value;
static constexpr bool is_local_k = locality_ == sz_similarity_local_k;
static_assert(is_affine_k || is_same_type<gap_costs_type_, linear_gap_costs_t>::value,
"The weighted candidate-lane kernel only supports linear and affine gap costs.");
static_assert(
objective_ == sz_maximize_score_k,
"The weighted candidate-lane kernel only implements score " "maximization (Needleman-Wunsch / " "Smith-" "Water" "man)" ".");
substituter_t substituter_ {};
gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const score_row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
size_t const class_bytes = candidate_lanes_k * longest_candidate * sizeof(u8_t);
scratch_amount_t amount {specs.cache_line_width};
amount += score_row_bytes; amount += score_row_bytes; if constexpr (is_affine_k) {
amount += score_row_bytes; amount += score_row_bytes; }
amount += class_bytes; return amount;
}
SZ_INLINE error_cost_t read_linear_gap_() const noexcept {
if constexpr (is_affine_k) { return (error_cost_t)0; }
else { return gap_costs_.open_or_extend; }
}
SZ_INLINE error_cost_t read_affine_open_() const noexcept {
if constexpr (is_affine_k) { return gap_costs_.open; }
else { return (error_cost_t)0; }
}
SZ_INLINE error_cost_t read_affine_extend_() const noexcept {
if constexpr (is_affine_k) { return gap_costs_.extend; }
else { return (error_cost_t)0; }
}
SZ_INLINE __m256i classify32_(__m512i const text_vec, __m512i const (&byte_to_class_vecs)[4],
__m512i const is_third_or_fourth_vec,
__m512i const is_second_or_fourth_vec) const noexcept {
__m512i const shuffled0 = _mm512_permutexvar_epi8(text_vec, byte_to_class_vecs[0]);
__m512i const shuffled1 = _mm512_permutexvar_epi8(text_vec, byte_to_class_vecs[1]);
__m512i const shuffled2 = _mm512_permutexvar_epi8(text_vec, byte_to_class_vecs[2]);
__m512i const shuffled3 = _mm512_permutexvar_epi8(text_vec, byte_to_class_vecs[3]);
__mmask64 const is_third_or_fourth = _mm512_test_epi8_mask(text_vec, is_third_or_fourth_vec);
__mmask64 const is_second_or_fourth = _mm512_test_epi8_mask(text_vec, is_second_or_fourth_vec);
__m512i const class_vec = _mm512_mask_blend_epi8(
is_third_or_fourth, _mm512_mask_blend_epi8(is_second_or_fourth, shuffled0, shuffled1),
_mm512_mask_blend_epi8(is_second_or_fourth, shuffled2, shuffled3));
return _mm512_castsi512_si256(class_vec);
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t *previous_deletes = nullptr;
score_t *current_deletes = nullptr;
u8_t *candidate_classes = nullptr;
if constexpr (is_affine_k) {
previous_deletes = current_row + row_stride;
current_deletes = previous_deletes + row_stride;
candidate_classes = reinterpret_cast<u8_t *>(current_deletes + row_stride);
}
else { candidate_classes = reinterpret_cast<u8_t *>(current_row + row_stride); }
__m512i byte_to_class_vecs[4];
byte_to_class_vecs[0] = _mm512_loadu_si512(substituter_.byte_to_class + 64 * 0);
byte_to_class_vecs[1] = _mm512_loadu_si512(substituter_.byte_to_class + 64 * 1);
byte_to_class_vecs[2] = _mm512_loadu_si512(substituter_.byte_to_class + 64 * 2);
byte_to_class_vecs[3] = _mm512_loadu_si512(substituter_.byte_to_class + 64 * 3);
__m512i const is_third_or_fourth_vec = _mm512_set1_epi8((char)0x80);
__m512i const is_second_or_fourth_vec = _mm512_set1_epi8((char)0x40);
for (size_t column = 0; column < longest_candidate; ++column) {
__m256i const candidate_chars_vec = _mm256_loadu_epi8(candidates.position(column));
__m512i const candidate_chars_zvec = _mm512_castsi256_si512(candidate_chars_vec);
__m256i const candidate_classes_vec = classify32_(candidate_chars_zvec, byte_to_class_vecs,
is_third_or_fourth_vec, is_second_or_fourth_vec);
_mm256_storeu_epi8(candidate_classes + column * candidate_lanes_k, candidate_classes_vec);
}
__m512i const zero_vec = _mm512_setzero_si512();
error_cost_t const gap = read_linear_gap_();
error_cost_t const gap_open = read_affine_open_();
error_cost_t const gap_extend = read_affine_extend_();
__m512i const gap_vec = _mm512_set1_epi16(static_cast<short>(gap));
__m512i const gap_open_vec = _mm512_set1_epi16(static_cast<short>(gap_open));
__m512i const gap_extend_vec = _mm512_set1_epi16(static_cast<short>(gap_extend));
__m512i const gap_boundary_vec = _mm512_set1_epi16(static_cast<short>(gap_open + gap_extend));
alignas(64) i16_t lane_lengths[candidate_lanes_k] = {0};
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index)
lane_lengths[lane_index] = static_cast<i16_t>(candidates.lengths[lane_index]);
__m512i const lane_lengths_vec = _mm512_load_si512(lane_lengths);
__m512i running_max_vec = zero_vec;
for (size_t column = 0; column <= longest_candidate; ++column) {
if constexpr (is_local_k) {
_mm512_storeu_si512(previous_row + column * candidate_lanes_k, zero_vec);
if constexpr (is_affine_k)
_mm512_storeu_si512(previous_deletes + column * candidate_lanes_k, gap_boundary_vec);
}
else if constexpr (is_affine_k) {
i16_t const score_boundary = column ? static_cast<i16_t>(gap_open + gap_extend * (i16_t)(column - 1))
: (i16_t)0;
i16_t const gap_boundary = static_cast<i16_t>((gap_open + gap_extend) + score_boundary);
_mm512_storeu_si512(previous_row + column * candidate_lanes_k,
_mm512_set1_epi16(static_cast<short>(score_boundary)));
_mm512_storeu_si512(previous_deletes + column * candidate_lanes_k,
_mm512_set1_epi16(static_cast<short>(gap_boundary)));
}
else {
_mm512_storeu_si512(previous_row + column * candidate_lanes_k,
_mm512_set1_epi16(static_cast<short>(static_cast<i16_t>(gap * (i16_t)column))));
}
}
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
u8_t const query_class = substituter_.byte_to_class[(u8_t)query[query_position - 1]];
__m512i const cost_row_vec = _mm512_castsi256_si512(
_mm256_loadu_epi8(&substituter_.class_substitution_costs[query_class][0]));
__m512i insert_vec = zero_vec;
if constexpr (is_local_k) {
_mm512_storeu_si512(current_row, zero_vec);
if constexpr (is_affine_k) insert_vec = gap_boundary_vec;
}
else if constexpr (is_affine_k) {
i16_t const row_score_boundary = static_cast<i16_t>(gap_open +
gap_extend * (i16_t)(query_position - 1));
i16_t const row_gap_boundary = static_cast<i16_t>((gap_open + gap_extend) + row_score_boundary);
_mm512_storeu_si512(current_row, _mm512_set1_epi16(static_cast<short>(row_score_boundary)));
insert_vec = _mm512_set1_epi16(static_cast<short>(row_gap_boundary));
}
else {
_mm512_storeu_si512(
current_row,
_mm512_set1_epi16(static_cast<short>(static_cast<i16_t>(gap * (i16_t)query_position))));
}
for (size_t column = 1; column <= longest_candidate; ++column) {
__m256i const candidate_classes_vec = _mm256_loadu_epi8(candidate_classes +
(column - 1) * candidate_lanes_k);
__m512i const diagonal_vec = _mm512_loadu_si512(previous_row + (column - 1) * candidate_lanes_k);
__m256i const cost_i8_vec = _mm512_castsi512_si256(
_mm512_permutexvar_epi8(_mm512_castsi256_si512(candidate_classes_vec), cost_row_vec));
__m512i const cost_i16_vec = _mm512_cvtepi8_epi16(cost_i8_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi16(diagonal_vec, cost_i16_vec);
__m512i cell_score_vec;
if constexpr (is_affine_k) {
__m512i const up_score_vec = _mm512_loadu_si512(previous_row + column * candidate_lanes_k);
__m512i const left_score_vec = _mm512_loadu_si512(current_row + (column - 1) * candidate_lanes_k);
__m512i const up_delete_vec = _mm512_loadu_si512(previous_deletes + column * candidate_lanes_k);
insert_vec = _mm512_max_epi16(_mm512_add_epi16(left_score_vec, gap_open_vec),
_mm512_add_epi16(insert_vec, gap_extend_vec));
__m512i const delete_vec = _mm512_max_epi16(_mm512_add_epi16(up_score_vec, gap_open_vec),
_mm512_add_epi16(up_delete_vec, gap_extend_vec));
if constexpr (is_local_k) {
__m512i const substitution_or_reset_vec = _mm512_max_epi16(cost_if_substitution_vec, zero_vec);
cell_score_vec = _mm512_max_epi16(substitution_or_reset_vec,
_mm512_max_epi16(insert_vec, delete_vec));
}
else {
cell_score_vec = _mm512_max_epi16(cost_if_substitution_vec,
_mm512_max_epi16(insert_vec, delete_vec));
}
_mm512_storeu_si512(current_deletes + column * candidate_lanes_k, delete_vec);
}
else {
__m512i const up_vec = _mm512_loadu_si512(previous_row + column * candidate_lanes_k);
__m512i const left_vec = _mm512_loadu_si512(current_row + (column - 1) * candidate_lanes_k);
__m512i const cost_if_gap_vec = _mm512_add_epi16(_mm512_max_epi16(up_vec, left_vec), gap_vec);
cell_score_vec = _mm512_max_epi16(cost_if_substitution_vec, cost_if_gap_vec);
if constexpr (is_local_k) cell_score_vec = _mm512_max_epi16(zero_vec, cell_score_vec);
}
_mm512_storeu_si512(current_row + column * candidate_lanes_k, cell_score_vec);
if constexpr (is_local_k) {
__mmask32 const column_live = _mm512_cmpgt_epi16_mask(
lane_lengths_vec, _mm512_set1_epi16(static_cast<short>(column - 1)));
running_max_vec = _mm512_mask_max_epi16(running_max_vec, column_live, running_max_vec,
cell_score_vec);
}
}
trivial_swap(previous_row, current_row);
if constexpr (is_affine_k) trivial_swap(previous_deletes, current_deletes);
}
if constexpr (is_local_k) {
alignas(64) i16_t final_max[candidate_lanes_k];
_mm512_store_si512(final_max, running_max_vec);
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index)
result_lanes[lane_index] = final_max[lane_index];
}
else {
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
}
return status_t::success_k;
}
};
template <typename gap_costs_type_, sz_similarity_locality_t locality_, sz_similarity_objective_t objective_>
struct candidate_lane_walker<char, i32_t, error_costs_32x32_t, gap_costs_type_, objective_, locality_, sz_cap_icelake_k,
16, void> {
using char_t = char;
using score_t = i32_t;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = gap_costs_type_;
static constexpr sz_similarity_objective_t objective_k = objective_;
static constexpr sz_similarity_locality_t locality_k = locality_;
static constexpr sz_capability_t capability_k = sz_cap_icelake_k;
static constexpr size_t candidate_lanes_k = 16;
static constexpr size_t capacity_k = (size_t)std::numeric_limits<score_t>::max();
static constexpr bool is_affine_k = is_same_type<gap_costs_type_, affine_gap_costs_t>::value;
static constexpr bool is_local_k = locality_ == sz_similarity_local_k;
static_assert(is_affine_k || is_same_type<gap_costs_type_, linear_gap_costs_t>::value,
"The weighted candidate-lane kernel only supports linear and affine gap costs.");
static_assert(
objective_ == sz_maximize_score_k,
"The weighted candidate-lane kernel only implements score " "maximization (Needleman-Wunsch / " "Smith-" "Water" "man)" ".");
substituter_t substituter_ {};
gap_costs_t gap_costs_ {};
candidate_lane_walker() noexcept {}
candidate_lane_walker(substituter_t subs, gap_costs_t gaps) noexcept : substituter_(subs), gap_costs_(gaps) {}
size_t scratch_space_needed(size_t longest_candidate, cpu_specs_t const &specs) const noexcept {
size_t const score_row_bytes = candidate_lanes_k * (longest_candidate + 1) * sizeof(score_t);
size_t const class_bytes = candidate_lanes_k * longest_candidate * sizeof(u8_t);
scratch_amount_t amount {specs.cache_line_width};
amount += score_row_bytes; amount += score_row_bytes; if constexpr (is_affine_k) {
amount += score_row_bytes; amount += score_row_bytes; }
amount += class_bytes; return amount;
}
SZ_INLINE error_cost_t read_linear_gap_() const noexcept {
if constexpr (is_affine_k) { return (error_cost_t)0; }
else { return gap_costs_.open_or_extend; }
}
SZ_INLINE error_cost_t read_affine_open_() const noexcept {
if constexpr (is_affine_k) { return gap_costs_.open; }
else { return (error_cost_t)0; }
}
SZ_INLINE error_cost_t read_affine_extend_() const noexcept {
if constexpr (is_affine_k) { return gap_costs_.extend; }
else { return (error_cost_t)0; }
}
SZ_INLINE __m128i classify16_(__m512i const text_vec, __m512i const (&byte_to_class_vecs)[4],
__m512i const is_third_or_fourth_vec,
__m512i const is_second_or_fourth_vec) const noexcept {
__m512i const shuffled0 = _mm512_permutexvar_epi8(text_vec, byte_to_class_vecs[0]);
__m512i const shuffled1 = _mm512_permutexvar_epi8(text_vec, byte_to_class_vecs[1]);
__m512i const shuffled2 = _mm512_permutexvar_epi8(text_vec, byte_to_class_vecs[2]);
__m512i const shuffled3 = _mm512_permutexvar_epi8(text_vec, byte_to_class_vecs[3]);
__mmask64 const is_third_or_fourth = _mm512_test_epi8_mask(text_vec, is_third_or_fourth_vec);
__mmask64 const is_second_or_fourth = _mm512_test_epi8_mask(text_vec, is_second_or_fourth_vec);
__m512i const class_vec = _mm512_mask_blend_epi8(
is_third_or_fourth, _mm512_mask_blend_epi8(is_second_or_fourth, shuffled0, shuffled1),
_mm512_mask_blend_epi8(is_second_or_fourth, shuffled2, shuffled3));
return _mm512_castsi512_si128(class_vec);
}
status_t operator()(span<char_t const> query, candidate_lanes_block<char_t> candidates, score_t *result_lanes,
scratch_space_t scratch_space, cpu_specs_t const &specs) const noexcept {
sz_unused_(specs);
size_t const query_length = query.size();
size_t const longest_candidate = candidates.longest_candidate;
size_t const row_stride = candidate_lanes_k * (longest_candidate + 1);
score_t *previous_row = reinterpret_cast<score_t *>(scratch_space.data());
score_t *current_row = previous_row + row_stride;
score_t *previous_deletes = nullptr;
score_t *current_deletes = nullptr;
u8_t *candidate_classes = nullptr;
if constexpr (is_affine_k) {
previous_deletes = current_row + row_stride;
current_deletes = previous_deletes + row_stride;
candidate_classes = reinterpret_cast<u8_t *>(current_deletes + row_stride);
}
else { candidate_classes = reinterpret_cast<u8_t *>(current_row + row_stride); }
__m512i byte_to_class_vecs[4];
byte_to_class_vecs[0] = _mm512_loadu_si512(substituter_.byte_to_class + 64 * 0);
byte_to_class_vecs[1] = _mm512_loadu_si512(substituter_.byte_to_class + 64 * 1);
byte_to_class_vecs[2] = _mm512_loadu_si512(substituter_.byte_to_class + 64 * 2);
byte_to_class_vecs[3] = _mm512_loadu_si512(substituter_.byte_to_class + 64 * 3);
__m512i const is_third_or_fourth_vec = _mm512_set1_epi8((char)0x80);
__m512i const is_second_or_fourth_vec = _mm512_set1_epi8((char)0x40);
for (size_t column = 0; column < longest_candidate; ++column) {
__m128i const candidate_chars_vec = _mm_loadu_epi8(candidates.position(column));
__m512i const candidate_chars_zvec = _mm512_castsi128_si512(candidate_chars_vec);
__m128i const candidate_classes_vec = classify16_(candidate_chars_zvec, byte_to_class_vecs,
is_third_or_fourth_vec, is_second_or_fourth_vec);
_mm_storeu_epi8(candidate_classes + column * candidate_lanes_k, candidate_classes_vec);
}
__m512i const zero_vec = _mm512_setzero_si512();
error_cost_t const gap = read_linear_gap_();
error_cost_t const gap_open = read_affine_open_();
error_cost_t const gap_extend = read_affine_extend_();
__m512i const gap_vec = _mm512_set1_epi32(static_cast<int>(gap));
__m512i const gap_open_vec = _mm512_set1_epi32(static_cast<int>(gap_open));
__m512i const gap_extend_vec = _mm512_set1_epi32(static_cast<int>(gap_extend));
__m512i const gap_boundary_vec = _mm512_set1_epi32(static_cast<int>(gap_open + gap_extend));
alignas(64) i32_t lane_lengths[candidate_lanes_k] = {0};
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index)
lane_lengths[lane_index] = static_cast<i32_t>(candidates.lengths[lane_index]);
__m512i const lane_lengths_vec = _mm512_load_si512(lane_lengths);
__m512i running_max_vec = zero_vec;
for (size_t column = 0; column <= longest_candidate; ++column) {
if constexpr (is_local_k) {
_mm512_storeu_epi32(previous_row + column * candidate_lanes_k, zero_vec);
if constexpr (is_affine_k)
_mm512_storeu_epi32(previous_deletes + column * candidate_lanes_k, gap_boundary_vec);
}
else if constexpr (is_affine_k) {
i32_t const score_boundary = column ? static_cast<i32_t>(gap_open + gap_extend * (i32_t)(column - 1))
: (i32_t)0;
i32_t const gap_boundary = static_cast<i32_t>((gap_open + gap_extend) + score_boundary);
_mm512_storeu_epi32(previous_row + column * candidate_lanes_k,
_mm512_set1_epi32(static_cast<int>(score_boundary)));
_mm512_storeu_epi32(previous_deletes + column * candidate_lanes_k,
_mm512_set1_epi32(static_cast<int>(gap_boundary)));
}
else {
_mm512_storeu_epi32(previous_row + column * candidate_lanes_k,
_mm512_set1_epi32(static_cast<int>(static_cast<i32_t>(gap * (i32_t)column))));
}
}
for (size_t query_position = 1; query_position <= query_length; ++query_position) {
u8_t const query_class = substituter_.byte_to_class[(u8_t)query[query_position - 1]];
__m512i const cost_row_vec = _mm512_castsi256_si512(
_mm256_loadu_epi8(&substituter_.class_substitution_costs[query_class][0]));
__m512i insert_vec = zero_vec;
if constexpr (is_local_k) {
_mm512_storeu_epi32(current_row, zero_vec);
if constexpr (is_affine_k) insert_vec = gap_boundary_vec;
}
else if constexpr (is_affine_k) {
i32_t const row_score_boundary = static_cast<i32_t>(gap_open +
gap_extend * (i32_t)(query_position - 1));
i32_t const row_gap_boundary = static_cast<i32_t>((gap_open + gap_extend) + row_score_boundary);
_mm512_storeu_epi32(current_row, _mm512_set1_epi32(static_cast<int>(row_score_boundary)));
insert_vec = _mm512_set1_epi32(static_cast<int>(row_gap_boundary));
}
else {
_mm512_storeu_epi32(
current_row, _mm512_set1_epi32(static_cast<int>(static_cast<i32_t>(gap * (i32_t)query_position))));
}
for (size_t column = 1; column <= longest_candidate; ++column) {
__m128i const candidate_classes_vec = _mm_loadu_epi8(candidate_classes +
(column - 1) * candidate_lanes_k);
__m512i const diagonal_vec = _mm512_loadu_epi32(previous_row + (column - 1) * candidate_lanes_k);
__m128i const cost_i8_vec = _mm512_castsi512_si128(
_mm512_permutexvar_epi8(_mm512_castsi128_si512(candidate_classes_vec), cost_row_vec));
__m512i const cost_i32_vec = _mm512_cvtepi8_epi32(cost_i8_vec);
__m512i const cost_if_substitution_vec = _mm512_add_epi32(diagonal_vec, cost_i32_vec);
__m512i cell_score_vec;
if constexpr (is_affine_k) {
__m512i const up_score_vec = _mm512_loadu_epi32(previous_row + column * candidate_lanes_k);
__m512i const left_score_vec = _mm512_loadu_epi32(current_row + (column - 1) * candidate_lanes_k);
__m512i const up_delete_vec = _mm512_loadu_epi32(previous_deletes + column * candidate_lanes_k);
insert_vec = _mm512_max_epi32(_mm512_add_epi32(left_score_vec, gap_open_vec),
_mm512_add_epi32(insert_vec, gap_extend_vec));
__m512i const delete_vec = _mm512_max_epi32(_mm512_add_epi32(up_score_vec, gap_open_vec),
_mm512_add_epi32(up_delete_vec, gap_extend_vec));
if constexpr (is_local_k) {
__m512i const substitution_or_reset_vec = _mm512_max_epi32(cost_if_substitution_vec, zero_vec);
cell_score_vec = _mm512_max_epi32(substitution_or_reset_vec,
_mm512_max_epi32(insert_vec, delete_vec));
}
else {
cell_score_vec = _mm512_max_epi32(cost_if_substitution_vec,
_mm512_max_epi32(insert_vec, delete_vec));
}
_mm512_storeu_epi32(current_deletes + column * candidate_lanes_k, delete_vec);
}
else {
__m512i const up_vec = _mm512_loadu_epi32(previous_row + column * candidate_lanes_k);
__m512i const left_vec = _mm512_loadu_epi32(current_row + (column - 1) * candidate_lanes_k);
__m512i const cost_if_gap_vec = _mm512_add_epi32(_mm512_max_epi32(up_vec, left_vec), gap_vec);
cell_score_vec = _mm512_max_epi32(cost_if_substitution_vec, cost_if_gap_vec);
if constexpr (is_local_k) cell_score_vec = _mm512_max_epi32(zero_vec, cell_score_vec);
}
_mm512_storeu_epi32(current_row + column * candidate_lanes_k, cell_score_vec);
if constexpr (is_local_k) {
__mmask16 const column_live = _mm512_cmpgt_epi32_mask(
lane_lengths_vec, _mm512_set1_epi32(static_cast<int>(column - 1)));
running_max_vec = _mm512_mask_max_epi32(running_max_vec, column_live, running_max_vec,
cell_score_vec);
}
}
trivial_swap(previous_row, current_row);
if constexpr (is_affine_k) trivial_swap(previous_deletes, current_deletes);
}
if constexpr (is_local_k) {
alignas(64) i32_t final_max[candidate_lanes_k];
_mm512_store_si512(final_max, running_max_vec);
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index)
result_lanes[lane_index] = final_max[lane_index];
}
else {
for (size_t lane_index = 0; lane_index < candidates.lanes_count; ++lane_index) {
size_t const candidate_length = candidates.lengths[lane_index];
result_lanes[lane_index] = previous_row[candidate_length * candidate_lanes_k + lane_index];
}
}
return status_t::success_k;
}
};
template <typename allocator_type_, sz_capability_t capability_>
struct needleman_wunsch_scores<error_costs_32x32_t, linear_gap_costs_t, allocator_type_, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = linear_gap_costs_t;
using allocator_t = allocator_type_;
using index_t = u32_t;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t candidate_lanes_k = 32;
using scoring_t =
needleman_wunsch_score<char, substituter_t, gap_costs_t, sz_caps_sil_k>; using lane_walker_narrow_t =
candidate_lane_walker<char, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_global_k,
sz_cap_icelake_k, (int)candidate_lanes_k, void>; using lane_walker_wide_t =
candidate_lane_walker<char, i32_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_global_k,
sz_cap_icelake_k, 16, void>;
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
safe_vector<std::byte, scratch_allocator_t> score_scratch_ {alloc_};
needleman_wunsch_scores(allocator_t alloc = {}) noexcept : alloc_(alloc) {}
needleman_wunsch_scores(substituter_t subs, linear_gap_costs_t gaps, allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc) {}
#pragma region Public Cross Product Overloads
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, queries, candidates, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, queries, candidates, results, cross_similarities_t::all_pairs_k, 0,
cross_live_cells_count_(queries.size(), candidates.size(), cross_similarities_t::all_pairs_k),
scratch_space_t(score_scratch_), specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, queries, candidates, results,
cross_similarities_t::all_pairs_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, sequences, sequences, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, sequences, sequences, results, cross_similarities_t::symmetric_k, 0,
cross_live_cells_count_(sequences.size(), sequences.size(), cross_similarities_t::symmetric_k),
scratch_space_t(score_scratch_), specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, sequences, sequences, results,
cross_similarities_t::symmetric_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Public Cross Product Overloads
};
template <typename allocator_type_, sz_capability_t capability_>
struct needleman_wunsch_scores<error_costs_32x32_t, affine_gap_costs_t, allocator_type_, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = affine_gap_costs_t;
using allocator_t = allocator_type_;
using index_t = u32_t;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t candidate_lanes_k = 32;
using scoring_t =
needleman_wunsch_score<char, substituter_t, gap_costs_t, sz_caps_sil_k>; using lane_walker_narrow_t =
candidate_lane_walker<char, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_global_k,
sz_cap_icelake_k, (int)candidate_lanes_k, void>; using lane_walker_wide_t =
candidate_lane_walker<char, i32_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_global_k,
sz_cap_icelake_k, 16, void>;
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
safe_vector<std::byte, scratch_allocator_t> score_scratch_ {alloc_};
needleman_wunsch_scores(allocator_t alloc = {}) noexcept : alloc_(alloc) {}
needleman_wunsch_scores(substituter_t subs, affine_gap_costs_t gaps, allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc) {}
#pragma region Public Cross Product Overloads
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, queries, candidates, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, queries, candidates, results, cross_similarities_t::all_pairs_k, 0,
cross_live_cells_count_(queries.size(), candidates.size(), cross_similarities_t::all_pairs_k),
scratch_space_t(score_scratch_), specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, queries, candidates, results,
cross_similarities_t::all_pairs_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, sequences, sequences, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, sequences, sequences, results, cross_similarities_t::symmetric_k, 0,
cross_live_cells_count_(sequences.size(), sequences.size(), cross_similarities_t::symmetric_k),
scratch_space_t(score_scratch_), specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, sequences, sequences, results,
cross_similarities_t::symmetric_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Public Cross Product Overloads
};
template <typename allocator_type_, sz_capability_t capability_>
struct smith_waterman_scores<error_costs_32x32_t, linear_gap_costs_t, allocator_type_, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = linear_gap_costs_t;
using allocator_t = allocator_type_;
using index_t = u32_t;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t candidate_lanes_k = 32;
using scoring_t = smith_waterman_score<char, substituter_t, gap_costs_t, sz_caps_sil_k>; using lane_walker_narrow_t =
candidate_lane_walker<char, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_local_k,
sz_cap_icelake_k, (int)candidate_lanes_k, void>; using lane_walker_wide_t =
candidate_lane_walker<char, i32_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_local_k,
sz_cap_icelake_k, 16, void>;
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
substituter_t substituter_ {};
linear_gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
safe_vector<std::byte, scratch_allocator_t> score_scratch_ {alloc_};
smith_waterman_scores(allocator_t alloc = {}) noexcept : alloc_(alloc) {}
smith_waterman_scores(substituter_t subs, linear_gap_costs_t gaps, allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc) {}
#pragma region Public Cross Product Overloads
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, queries, candidates, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, queries, candidates, results, cross_similarities_t::all_pairs_k, 0,
cross_live_cells_count_(queries.size(), candidates.size(), cross_similarities_t::all_pairs_k),
scratch_space_t(score_scratch_), specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, queries, candidates, results,
cross_similarities_t::all_pairs_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, sequences, sequences, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, sequences, sequences, results, cross_similarities_t::symmetric_k, 0,
cross_live_cells_count_(sequences.size(), sequences.size(), cross_similarities_t::symmetric_k),
scratch_space_t(score_scratch_), specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, sequences, sequences, results,
cross_similarities_t::symmetric_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Public Cross Product Overloads
};
template <typename allocator_type_, sz_capability_t capability_>
struct smith_waterman_scores<error_costs_32x32_t, affine_gap_costs_t, allocator_type_, capability_,
std::enable_if_t<(capability_ & sz_cap_icelake_k) != 0>> {
using char_t = char;
using substituter_t = error_costs_32x32_t;
using gap_costs_t = affine_gap_costs_t;
using allocator_t = allocator_type_;
using index_t = u32_t;
static constexpr sz_capability_t capability_k = capability_;
static constexpr size_t candidate_lanes_k = 32;
using scoring_t = smith_waterman_score<char, substituter_t, gap_costs_t, sz_caps_sil_k>; using lane_walker_narrow_t =
candidate_lane_walker<char, i16_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_local_k,
sz_cap_icelake_k, (int)candidate_lanes_k, void>; using lane_walker_wide_t =
candidate_lane_walker<char, i32_t, substituter_t, gap_costs_t, sz_maximize_score_k, sz_similarity_local_k,
sz_cap_icelake_k, 16, void>;
using scratch_allocator_t = typename std::allocator_traits<allocator_t>::template rebind_alloc<std::byte>;
substituter_t substituter_ {};
affine_gap_costs_t gap_costs_ {};
allocator_t alloc_ {};
safe_vector<std::byte, scratch_allocator_t> score_scratch_ {alloc_};
smith_waterman_scores(allocator_t alloc = {}) noexcept : alloc_(alloc) {}
smith_waterman_scores(substituter_t subs, affine_gap_costs_t gaps, allocator_t alloc = allocator_t {}) noexcept
: substituter_(subs), gap_costs_(gaps), alloc_(alloc) {}
#pragma region Public Cross Product Overloads
template <typename queries_type_, typename candidates_type_, typename value_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, queries, candidates, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, queries, candidates, results, cross_similarities_t::all_pairs_k, 0,
cross_live_cells_count_(queries.size(), candidates.size(), cross_similarities_t::all_pairs_k),
scratch_space_t(score_scratch_), specs);
}
template <typename queries_type_, typename candidates_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(queries_type_ const &queries, candidates_type_ const &candidates,
strided_rows<value_type_> results, executor_type_ &&executor,
cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, queries, candidates, results,
cross_similarities_t::all_pairs_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
template <typename sequences_type_, typename value_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
if (status_t status = score_scratch_.try_resize(
cross_product_candidate_lanes_scratch_(narrow, wide, fallback, sequences, sequences, specs));
status != status_t::success_k)
return status;
return cross_product_candidate_lanes_range_(
narrow, wide, fallback, sequences, sequences, results, cross_similarities_t::symmetric_k, 0,
cross_live_cells_count_(sequences.size(), sequences.size(), cross_similarities_t::symmetric_k),
scratch_space_t(score_scratch_), specs);
}
template <typename sequences_type_, typename value_type_, typename executor_type_>
SZ_NOIPA status_t operator()(sequences_type_ const &sequences, strided_rows<value_type_> results,
executor_type_ &&executor, cpu_specs_t const &specs = {}) noexcept {
lane_walker_narrow_t narrow {substituter_, gap_costs_};
lane_walker_wide_t wide {substituter_, gap_costs_};
scoring_t fallback {substituter_, gap_costs_};
return cross_product_candidate_lanes_parallel_(narrow, wide, fallback, sequences, sequences, results,
cross_similarities_t::symmetric_k, score_scratch_,
std::forward<executor_type_>(executor), specs);
}
#pragma endregion Public Cross Product Overloads
};
#pragma endregion Inter Sequence Candidate Lanes
#if defined(__clang__)
#pragma clang attribute pop
#elif defined(__GNUC__)
#pragma GCC pop_options
#endif
#endif #pragma endregion
} }
#endif