use super::super::bt::BtMatcher;
use super::super::cost_model::{HcOptState, HcOptimalCostProfile};
use super::super::opt::types::HcOptimalNode;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
unsafe fn priceset_improved_mask8_avx2(next_cost: &[u32; 8], node_price: &[u32]) -> u8 {
#[cfg(target_arch = "x86")]
use core::arch::x86::{
__m256i, _mm256_andnot_si256, _mm256_castsi256_ps, _mm256_cmpeq_epi32, _mm256_loadu_si256,
_mm256_min_epu32, _mm256_movemask_ps,
};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
__m256i, _mm256_andnot_si256, _mm256_castsi256_ps, _mm256_cmpeq_epi32, _mm256_loadu_si256,
_mm256_min_epu32, _mm256_movemask_ps,
};
let nc = unsafe { _mm256_loadu_si256(next_cost.as_ptr() as *const __m256i) };
let np = unsafe { _mm256_loadu_si256(node_price.as_ptr() as *const __m256i) };
let min = _mm256_min_epu32(nc, np);
let le = _mm256_cmpeq_epi32(min, nc); let eq = _mm256_cmpeq_epi32(nc, np); let lt = _mm256_andnot_si256(eq, le); _mm256_movemask_ps(_mm256_castsi256_ps(lt)) as u8
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
fn priceset_next_cost(
profile: HcOptimalCostProfile,
stats: &HcOptState,
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
match_len: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
) -> u32 {
let ml_price =
BtMatcher::cached_match_length_price(profile, stats, match_len, ml_cache, ml_stamp);
let seq_cost = BtMatcher::add_prices(
ll0_price,
profile.match_price_from_parts(off_price, ml_price, stats),
);
BtMatcher::add_prices(base_cost, seq_cost)
}
#[inline]
#[allow(clippy::too_many_arguments)]
#[cfg_attr(
any(
all(target_arch = "aarch64", target_endian = "little"),
all(target_arch = "wasm32", target_feature = "simd128")
),
allow(dead_code)
)]
pub(crate) fn priceset_range_nonabort_scalar(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
let mut new_last = last_pos;
for ml in start..=max {
let next_cost = priceset_next_cost(
profile, stats, ml_cache, ml_stamp, ml, ll0_price, off_price, base_cost,
);
let next = pos + ml;
if next_cost < node_prices[next] {
node_prices[next] = next_cost;
nodes[next] = HcOptimalNode {
off,
mlen: ml as u32,
litlen: 0,
reps,
};
if next > new_last {
new_last = next;
}
}
}
new_last
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
#[cfg_attr(
not(any(
target_arch = "x86",
target_arch = "x86_64",
all(target_arch = "aarch64", target_endian = "little"),
all(target_arch = "wasm32", target_feature = "simd128")
)),
allow(dead_code)
)]
fn priceset_range_vec<const W: usize>(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
deint: impl Fn(&[[u32; 2]], u32) -> Option<[u32; W]>,
mask: impl Fn(&[u32; W], &[u32]) -> u8,
) -> usize {
let mut new_last = last_pos;
let mut buf = [0u32; W];
let c_base = base_cost
.wrapping_add(ll0_price)
.wrapping_add(profile.match_price_from_parts(off_price, 0, stats));
let mut ml = start;
while ml + W <= max + 1 {
let vectorised = if ml + W <= ml_cache.len() {
deint(&ml_cache[ml..ml + W], ml_stamp)
} else {
None
};
if let Some(prices) = vectorised {
for (k, slot) in buf.iter_mut().enumerate() {
*slot = c_base.wrapping_add(prices[k]);
}
} else {
for (k, slot) in buf.iter_mut().enumerate() {
*slot = priceset_next_cost(
profile,
stats,
ml_cache,
ml_stamp,
ml + k,
ll0_price,
off_price,
base_cost,
);
}
}
let base_next = pos + ml;
let mut bits = mask(&buf, &node_prices[base_next..base_next + W]);
while bits != 0 {
let k = bits.trailing_zeros() as usize;
bits &= bits - 1;
let next = base_next + k;
node_prices[next] = buf[k];
nodes[next] = HcOptimalNode {
off,
mlen: (ml + k) as u32,
litlen: 0,
reps,
};
if next > new_last {
new_last = next;
}
}
ml += W;
}
while ml <= max {
let next_cost = priceset_next_cost(
profile, stats, ml_cache, ml_stamp, ml, ll0_price, off_price, base_cost,
);
let next = pos + ml;
if next_cost < node_prices[next] {
node_prices[next] = next_cost;
nodes[next] = HcOptimalNode {
off,
mlen: ml as u32,
litlen: 0,
reps,
};
if next > new_last {
new_last = next;
}
}
ml += 1;
}
new_last
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
#[inline]
unsafe fn priceset_cached_prices8_avx2(cells: &[[u32; 2]], stamp: u32) -> Option<[u32; 8]> {
#[cfg(target_arch = "x86")]
use core::arch::x86::{
__m256i, _mm256_castsi256_ps, _mm256_cmpeq_epi32, _mm256_loadu_si256, _mm256_movemask_ps,
_mm256_permute4x64_epi64, _mm256_set1_epi32, _mm256_shuffle_epi32, _mm256_storeu_si256,
_mm256_unpackhi_epi64, _mm256_unpacklo_epi64,
};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
__m256i, _mm256_castsi256_ps, _mm256_cmpeq_epi32, _mm256_loadu_si256, _mm256_movemask_ps,
_mm256_permute4x64_epi64, _mm256_set1_epi32, _mm256_shuffle_epi32, _mm256_storeu_si256,
_mm256_unpackhi_epi64, _mm256_unpacklo_epi64,
};
debug_assert!(cells.len() >= 8);
let base = cells.as_ptr() as *const __m256i;
let v0 = unsafe { _mm256_loadu_si256(base) };
let v1 = unsafe { _mm256_loadu_si256(base.add(1)) };
let s0 = _mm256_shuffle_epi32(v0, 0xD8); let s1 = _mm256_shuffle_epi32(v1, 0xD8); let gens = _mm256_unpackhi_epi64(s0, s1);
let eq = _mm256_cmpeq_epi32(gens, _mm256_set1_epi32(stamp as i32));
if _mm256_movemask_ps(_mm256_castsi256_ps(eq)) as u8 != 0xFF {
return None;
}
let p_scrambled = _mm256_unpacklo_epi64(s0, s1);
let prices = _mm256_permute4x64_epi64(p_scrambled, 0xD8);
let mut out = [0u32; 8];
unsafe { _mm256_storeu_si256(out.as_mut_ptr() as *mut __m256i, prices) };
Some(out)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "avx2")]
#[inline]
#[allow(clippy::too_many_arguments)]
pub(crate) unsafe fn priceset_range_nonabort_avx2(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
priceset_range_vec::<8>(
node_prices,
nodes,
ml_cache,
ml_stamp,
profile,
stats,
pos,
start,
max,
ll0_price,
off_price,
base_cost,
off,
reps,
last_pos,
|cells, stamp| unsafe { priceset_cached_prices8_avx2(cells, stamp) },
|nc, np| unsafe { priceset_improved_mask8_avx2(nc, np) },
)
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn priceset_cached_prices4_neon(cells: &[[u32; 2]], stamp: u32) -> Option<[u32; 4]> {
use core::arch::aarch64::{vceqq_u32, vdupq_n_u32, vld2q_u32, vminvq_u32, vst1q_u32};
debug_assert!(cells.len() >= 4);
let pair = unsafe { vld2q_u32(cells.as_ptr() as *const u32) };
let eq = vceqq_u32(pair.1, vdupq_n_u32(stamp));
if vminvq_u32(eq) != u32::MAX {
return None;
}
let mut out = [0u32; 4];
unsafe { vst1q_u32(out.as_mut_ptr(), pair.0) };
Some(out)
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[target_feature(enable = "neon")]
#[inline]
unsafe fn priceset_improved_mask4_neon(next_cost: &[u32; 4], node_price: &[u32]) -> u8 {
use core::arch::aarch64::{vaddvq_u32, vandq_u32, vcltq_u32, vld1q_u32, vst1q_u32};
let nc = unsafe { vld1q_u32(next_cost.as_ptr()) };
let np = unsafe { vld1q_u32(node_price.as_ptr()) };
let lt = vcltq_u32(nc, np);
let weights: [u32; 4] = [1, 2, 4, 8];
let w = unsafe { vld1q_u32(weights.as_ptr()) };
let bits = vandq_u32(lt, w);
let _ = vst1q_u32; vaddvq_u32(bits) as u8
}
#[cfg(all(target_arch = "aarch64", target_endian = "little"))]
#[target_feature(enable = "neon")]
#[inline]
#[allow(clippy::too_many_arguments)]
pub(crate) unsafe fn priceset_range_nonabort_neon(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
priceset_range_vec::<4>(
node_prices,
nodes,
ml_cache,
ml_stamp,
profile,
stats,
pos,
start,
max,
ll0_price,
off_price,
base_cost,
off,
reps,
last_pos,
|cells, stamp| unsafe { priceset_cached_prices4_neon(cells, stamp) },
|nc, np| unsafe { priceset_improved_mask4_neon(nc, np) },
)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "sse4.2")]
#[inline]
unsafe fn priceset_cached_prices4_sse41(cells: &[[u32; 2]], stamp: u32) -> Option<[u32; 4]> {
#[cfg(target_arch = "x86")]
use core::arch::x86::{
__m128i, _mm_castsi128_ps, _mm_cmpeq_epi32, _mm_loadu_si128, _mm_movemask_ps,
_mm_set1_epi32, _mm_shuffle_epi32, _mm_storeu_si128, _mm_unpackhi_epi64,
_mm_unpacklo_epi64,
};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
__m128i, _mm_castsi128_ps, _mm_cmpeq_epi32, _mm_loadu_si128, _mm_movemask_ps,
_mm_set1_epi32, _mm_shuffle_epi32, _mm_storeu_si128, _mm_unpackhi_epi64,
_mm_unpacklo_epi64,
};
debug_assert!(cells.len() >= 4);
let base = cells.as_ptr() as *const __m128i;
let v0 = unsafe { _mm_loadu_si128(base) }; let v1 = unsafe { _mm_loadu_si128(base.add(1)) }; let s0 = _mm_shuffle_epi32(v0, 0xD8); let s1 = _mm_shuffle_epi32(v1, 0xD8); let gens = _mm_unpackhi_epi64(s0, s1); let eq = _mm_cmpeq_epi32(gens, _mm_set1_epi32(stamp as i32));
if _mm_movemask_ps(_mm_castsi128_ps(eq)) as u8 & 0x0F != 0x0F {
return None;
}
let prices = _mm_unpacklo_epi64(s0, s1); let mut out = [0u32; 4];
unsafe { _mm_storeu_si128(out.as_mut_ptr() as *mut __m128i, prices) };
Some(out)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "sse4.2")]
#[inline]
unsafe fn priceset_improved_mask4_sse41(next_cost: &[u32; 4], node_price: &[u32]) -> u8 {
#[cfg(target_arch = "x86")]
use core::arch::x86::{
__m128i, _mm_andnot_si128, _mm_castsi128_ps, _mm_cmpeq_epi32, _mm_loadu_si128,
_mm_min_epu32, _mm_movemask_ps,
};
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::{
__m128i, _mm_andnot_si128, _mm_castsi128_ps, _mm_cmpeq_epi32, _mm_loadu_si128,
_mm_min_epu32, _mm_movemask_ps,
};
let nc = unsafe { _mm_loadu_si128(next_cost.as_ptr() as *const __m128i) };
let np = unsafe { _mm_loadu_si128(node_price.as_ptr() as *const __m128i) };
let min = _mm_min_epu32(nc, np);
let le = _mm_cmpeq_epi32(min, nc);
let eq = _mm_cmpeq_epi32(nc, np);
let lt = _mm_andnot_si128(eq, le);
(_mm_movemask_ps(_mm_castsi128_ps(lt)) as u8) & 0x0F
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[target_feature(enable = "sse4.2")]
#[inline]
#[allow(clippy::too_many_arguments)]
pub(crate) unsafe fn priceset_range_nonabort_sse41(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
priceset_range_vec::<4>(
node_prices,
nodes,
ml_cache,
ml_stamp,
profile,
stats,
pos,
start,
max,
ll0_price,
off_price,
base_cost,
off,
reps,
last_pos,
|cells, stamp| unsafe { priceset_cached_prices4_sse41(cells, stamp) },
|nc, np| unsafe { priceset_improved_mask4_sse41(nc, np) },
)
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
#[inline]
unsafe fn priceset_cached_prices4_simd128(cells: &[[u32; 2]], stamp: u32) -> Option<[u32; 4]> {
use core::arch::wasm32::{
u32x4_all_true, u32x4_eq, u32x4_shuffle, u32x4_splat, v128, v128_load, v128_store,
};
debug_assert!(cells.len() >= 4);
let base = cells.as_ptr() as *const v128;
let v0 = unsafe { v128_load(base) }; let v1 = unsafe { v128_load(base.add(1)) }; let gens = u32x4_shuffle::<1, 3, 5, 7>(v0, v1); let eq = u32x4_eq(gens, u32x4_splat(stamp));
if !u32x4_all_true(eq) {
return None;
}
let prices = u32x4_shuffle::<0, 2, 4, 6>(v0, v1); let mut out = [0u32; 4];
unsafe { v128_store(out.as_mut_ptr() as *mut v128, prices) };
Some(out)
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
#[inline]
unsafe fn priceset_improved_mask4_simd128(next_cost: &[u32; 4], node_price: &[u32]) -> u8 {
use core::arch::wasm32::{u32x4_bitmask, u32x4_lt, v128, v128_load};
let nc = unsafe { v128_load(next_cost.as_ptr() as *const v128) };
let np = unsafe { v128_load(node_price.as_ptr() as *const v128) };
u32x4_bitmask(u32x4_lt(nc, np))
}
#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
#[target_feature(enable = "simd128")]
#[inline]
#[allow(clippy::too_many_arguments)]
pub(crate) unsafe fn priceset_range_nonabort_simd128(
node_prices: &mut [u32],
nodes: &mut [HcOptimalNode],
ml_cache: &mut [[u32; 2]],
ml_stamp: u32,
profile: HcOptimalCostProfile,
stats: &HcOptState,
pos: usize,
start: usize,
max: usize,
ll0_price: u32,
off_price: u32,
base_cost: u32,
off: u32,
reps: [u32; 3],
last_pos: usize,
) -> usize {
priceset_range_vec::<4>(
node_prices,
nodes,
ml_cache,
ml_stamp,
profile,
stats,
pos,
start,
max,
ll0_price,
off_price,
base_cost,
off,
reps,
last_pos,
|cells, stamp| unsafe { priceset_cached_prices4_simd128(cells, stamp) },
|nc, np| unsafe { priceset_improved_mask4_simd128(nc, np) },
)
}
#[cfg(test)]
mod tests;