Skip to content

Commit 68f2143

Browse files
authored
Merge pull request #279 from AdaWorldAPI/claude/lance-graph-java-panama-valhalla-sus9w8
simd: mask/facet primitives for the lance-graph-java membrane (W1a)
2 parents e0a7e1c + c54c239 commit 68f2143

8 files changed

Lines changed: 1280 additions & 1 deletion

File tree

src/simd.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,25 @@ pub use crate::hpc::bf16_tile_gemm::{
683683
#[cfg(target_arch = "x86_64")]
684684
pub use crate::simd_amx::{amx_report, cpu_model, CpuModel};
685685

686+
// Packed-bitmask predicates + mask algebra — the columnar-selection lane.
687+
// Slice-level siblings of `add_i8` / `dot_i8`, built on the lane-level
688+
// `U32x16::eq_bitmask` / `I32x16::gt_bitmask` methods. Surfaced here because
689+
// the W1a invariant is "all SIMD from `ndarray::simd`": a consumer that had to
690+
// reach into `ndarray::simd_int_ops` (or worse, write its own compare-and-pack
691+
// loop) would be a polyfill bypass. Bit order is normative and identical
692+
// across all of them — element `i` at bit `i % 64` of word `i / 64`, trailing
693+
// bits zero. See `src/simd_int_ops.rs` for the full statement.
694+
#[cfg(feature = "std")]
695+
pub use crate::simd_int_ops::{
696+
eq_u32_strided_to_mask, eq_u32_to_mask, gt_i32_to_mask, mask_and, mask_and_assign, mask_or, mask_or_assign,
697+
masked_sum_i32,
698+
};
699+
// The popcount that closes the loop on the masks above: `mask_count` in ABI
700+
// terms. Already public at `ndarray::bitwise::popcount_batch_u64`; re-exported
701+
// here so a mask producer and its reducer share one import path (the sibling
702+
// `popcount_raw` / `hamming_distance_raw` re-export is just above).
703+
pub use crate::bitwise::popcount_batch_u64;
704+
686705
// Elementwise slice ops — polyfill-dispatched (F32x16/F64x8 chunks + scalar tail).
687706
#[cfg(feature = "std")]
688707
pub use crate::simd_ops::{

src/simd_avx2.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1764,6 +1764,29 @@ impl U32x16 {
17641764
}
17651765
Self(out)
17661766
}
1767+
1768+
/// Lane-wise equality as a packed 16-bit bitmask.
1769+
///
1770+
/// Bit `i` of the result is set iff `self.lane(i) == other.lane(i)`. Bit
1771+
/// order is **LSB-first**: lane `0` occupies bit `0`. Same convention as
1772+
/// [`I32x16::cmpge_zero_mask`] and [`I32x16::gt_bitmask`].
1773+
///
1774+
/// Edge cases: equality is exact bitwise comparison over the full 32-bit
1775+
/// range, so `u32::MAX` and `0` behave like any other value — no
1776+
/// saturation, wrapping, or signedness question arises.
1777+
///
1778+
/// Plain index loop over the array polyfill; see [`I32x16::gt_bitmask`]
1779+
/// for why no intrinsic override is earned.
1780+
#[inline(always)]
1781+
pub fn eq_bitmask(self, other: Self) -> u16 {
1782+
let mut mask = 0u16;
1783+
for i in 0..16 {
1784+
if self.0[i] == other.0[i] {
1785+
mask |= 1 << i;
1786+
}
1787+
}
1788+
mask
1789+
}
17671790
}
17681791

17691792
// 256-bit int lanes — scalar polyfills filling the gap surfaced by the
@@ -2307,6 +2330,34 @@ impl I32x16 {
23072330
}
23082331
mask
23092332
}
2333+
2334+
/// Lane-wise **signed** greater-than as a packed 16-bit bitmask.
2335+
///
2336+
/// Bit `i` of the result is set iff `self.lane(i) > other.lane(i)` under
2337+
/// two's-complement signed ordering. Bit order is **LSB-first**: lane `0`
2338+
/// occupies bit `0`. Same convention as [`Self::cmpge_zero_mask`].
2339+
///
2340+
/// Edge cases (all exact; no saturation, wrapping, or clamping):
2341+
/// * `i32::MIN` as the threshold is set for every lane strictly greater
2342+
/// than it, and clear for lanes equal to `i32::MIN`.
2343+
/// * `i32::MAX` as the threshold yields `0` — no `i32` exceeds it.
2344+
/// * Comparison is signed, *not* bit-pattern: `-1 > 0` is `false`.
2345+
///
2346+
/// Plain index loop over the array polyfill — the codegen oracle
2347+
/// (`.claude/knowledge/simd-codegen-oracle/`) measured that LLVM lowers
2348+
/// compare-and-pack-to-bitmask shapes of exactly this form to packed
2349+
/// compares plus a `vmovmsk`-class extraction, so no `unsafe` and no
2350+
/// `core::arch` intrinsic override is earned here.
2351+
#[inline(always)]
2352+
pub fn gt_bitmask(self, other: Self) -> u16 {
2353+
let mut mask = 0u16;
2354+
for i in 0..16 {
2355+
if self.0[i] > other.0[i] {
2356+
mask |= 1 << i;
2357+
}
2358+
}
2359+
mask
2360+
}
23102361
}
23112362
impl Mul for I32x16 {
23122363
type Output = Self;

src/simd_avx512.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,32 @@ impl I32x16 {
943943
unsafe { _mm512_cmpge_epi32_mask(self.0, _mm512_setzero_si512()) }
944944
}
945945

946+
/// Lane-wise **signed** greater-than as a packed 16-bit bitmask.
947+
///
948+
/// Bit `i` of the result is set iff `self.lane(i) > other.lane(i)` under
949+
/// two's-complement signed ordering. Bit order is **LSB-first**: lane `0`
950+
/// occupies bit `0`. Sibling of [`Self::cmpge_zero_mask`], which uses the
951+
/// same convention.
952+
///
953+
/// Edge cases (all exact, no saturation or clamping anywhere):
954+
/// * `i32::MIN > i32::MIN` → `false`; nothing is greater than `i32::MIN`
955+
/// except strictly larger values, so `x.gt_bitmask(splat(i32::MIN))` is
956+
/// set for every lane except those equal to `i32::MIN`.
957+
/// * `i32::MAX` as the threshold yields `0` — no `i32` exceeds it.
958+
/// * Negative operands compare as signed, *not* as bit patterns:
959+
/// `-1 > 0` is `false` even though `0xFFFF_FFFF > 0` unsigned.
960+
///
961+
/// AVX-512 lowers this to a single `VPCMPGTD` into a `__mmask16`, which
962+
/// *is* a `u16` — the packed bitmask is the hardware's native result, so
963+
/// there is no extraction step to elide.
964+
#[inline(always)]
965+
pub fn gt_bitmask(self, other: Self) -> u16 {
966+
// SAFETY: `Self` wraps a native `__m512i` and this impl block is
967+
// compiled only under the `avx512f` dispatch arm, the same guarantee
968+
// every other method on this type relies on.
969+
unsafe { _mm512_cmpgt_epi32_mask(self.0, other.0) }
970+
}
971+
946972
#[inline(always)]
947973
pub fn simd_min(self, other: Self) -> Self {
948974
Self(unsafe { _mm512_min_epi32(self.0, other.0) })
@@ -1592,6 +1618,27 @@ impl U32x16 {
15921618
unsafe { _mm512_reduce_add_epi32(self.0) as u32 }
15931619
}
15941620

1621+
/// Lane-wise equality as a packed 16-bit bitmask.
1622+
///
1623+
/// Bit `i` of the result is set iff `self.lane(i) == other.lane(i)`. Bit
1624+
/// order is **LSB-first**: lane `0` occupies bit `0`. Same convention as
1625+
/// [`I32x16::cmpge_zero_mask`] and [`I32x16::gt_bitmask`].
1626+
///
1627+
/// Edge cases: equality is exact bitwise comparison over the full 32-bit
1628+
/// range, so `u32::MAX` and `0` behave like any other value and there is
1629+
/// no saturation, wrapping, or signedness question to resolve — an `i32`
1630+
/// lane pattern compares identically if reinterpreted.
1631+
///
1632+
/// AVX-512 lowers this to a single `VPCMPEQD` into a `__mmask16`, which
1633+
/// *is* a `u16` — the packed bitmask is the hardware's native result.
1634+
#[inline(always)]
1635+
pub fn eq_bitmask(self, other: Self) -> u16 {
1636+
// SAFETY: `Self` wraps a native `__m512i` and this impl block is
1637+
// compiled only under the `avx512f` dispatch arm, the same guarantee
1638+
// every other method on this type relies on.
1639+
unsafe { _mm512_cmpeq_epu32_mask(self.0, other.0) }
1640+
}
1641+
15951642
/// Lane-wise left-rotate by `n` bits — the ARX rotate (matches
15961643
/// `u32::rotate_left`), the third ChaCha20/BLAKE-family primitive alongside
15971644
/// `Add` + `BitXor`. Single `VPROLVD` (AVX-512F variable rotate). The rotate

0 commit comments

Comments
 (0)