Skip to content

Commit ea79386

Browse files
committed
simd(W1a-#9): express masking as whole-register ops, not lane traversal
Removes every lane index from the portable backends. andnot becomes `self & !other`; ternlog composes from the BitAnd/BitOr/Not these types already carry. No `for i in 0..N`, no per-lane helper functions -- masking is a projection over the whole register, and a loop expressed it as a traversal even though LLVM undid it. The two portable backends now carry byte-identical bodies, which is the polyfill's point: one source, one geometry, per-arch lowering selected by the compile-time dispatch in simd.rs. Scope note: ternlog is bitwise, so it has no lane semantics at all -- it applies its truth table independently at every bit position. Every reading of a 12-byte cell (6x2, 4x3, 3x4, 24xi4) is therefore masked by the IDENTICAL mask; the operation never sees, and never imposes, a carving. That is why this crate ships the node only: no fold, no composition structure, no state. Composition and interning belong to the consumer. Codegen re-measured after the rewrite, unchanged: v4: vpternlogq $0x80 / $0x40, vandnps -- one instruction each v3: 2 x [vmovaps ymm; vandps; vandps; vmovaps], aligned Net -49 lines. Suite 2207 passed / 0 failed, fmt clean, clippy baseline unchanged (3 pre-existing warnings in untouched files).
1 parent ba2db74 commit ea79386

2 files changed

Lines changed: 172 additions & 221 deletions

File tree

src/simd_avx2.rs

Lines changed: 72 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -3501,96 +3501,31 @@ mod f16_precision_tests {
35013501
}
35023502
}
35033503

3504-
// ── W1a-#9: U64x8 / U32x16 :: andnot + ternlog (AVX2 backend) ───────────────
3504+
// ── W1a-#9: U64x8 / U32x16 :: andnot + ternlog (portable backend) ───────────
35053505
//
3506-
// Written in the same idiom as this file's `avx2_int_type!` bitwise operators:
3507-
// an element-wise loop over the `#[repr(align(64))]` backing array, no
3508-
// intrinsics and no `unsafe`. Under this crate's x86-64-v3 baseline LLVM
3509-
// auto-vectorises these to `vpand`/`vpandn`/`vpor` on `ymm` — the alignment
3510-
// attribute is what lets it emit aligned moves. The same source lowers to
3511-
// `vandq_u64`/`vbicq_u64` on NEON and `v128_and`/`v128_andnot` on wasm, which
3512-
// is why one portable body serves all three of those profiles.
3513-
3514-
/// Evaluate a ternlog truth table for one lane. `imm` is a compile-time
3515-
/// constant at every call site, so only the minterms the table selects survive
3516-
/// const-folding — `AND3` (0x80) reduces to two ANDs.
3517-
#[inline(always)]
3518-
const fn ternlog_lane_u64_avx2(a: u64, b: u64, c: u64, imm: i32) -> u64 {
3519-
let mut r = 0u64;
3520-
if imm & 0x01 != 0 {
3521-
r |= !a & !b & !c;
3522-
}
3523-
if imm & 0x02 != 0 {
3524-
r |= !a & !b & c;
3525-
}
3526-
if imm & 0x04 != 0 {
3527-
r |= !a & b & !c;
3528-
}
3529-
if imm & 0x08 != 0 {
3530-
r |= !a & b & c;
3531-
}
3532-
if imm & 0x10 != 0 {
3533-
r |= a & !b & !c;
3534-
}
3535-
if imm & 0x20 != 0 {
3536-
r |= a & !b & c;
3537-
}
3538-
if imm & 0x40 != 0 {
3539-
r |= a & b & !c;
3540-
}
3541-
if imm & 0x80 != 0 {
3542-
r |= a & b & c;
3543-
}
3544-
r
3545-
}
3546-
3547-
#[inline(always)]
3548-
const fn ternlog_lane_u32_avx2(a: u32, b: u32, c: u32, imm: i32) -> u32 {
3549-
let mut r = 0u32;
3550-
if imm & 0x01 != 0 {
3551-
r |= !a & !b & !c;
3552-
}
3553-
if imm & 0x02 != 0 {
3554-
r |= !a & !b & c;
3555-
}
3556-
if imm & 0x04 != 0 {
3557-
r |= !a & b & !c;
3558-
}
3559-
if imm & 0x08 != 0 {
3560-
r |= !a & b & c;
3561-
}
3562-
if imm & 0x10 != 0 {
3563-
r |= a & !b & !c;
3564-
}
3565-
if imm & 0x20 != 0 {
3566-
r |= a & !b & c;
3567-
}
3568-
if imm & 0x40 != 0 {
3569-
r |= a & b & !c;
3570-
}
3571-
if imm & 0x80 != 0 {
3572-
r |= a & b & c;
3573-
}
3574-
r
3575-
}
3506+
// Masked projection, never traversal. The geometry is fixed and identical on
3507+
// every architecture, so these are whole-register operations composed from the
3508+
// `BitAnd` / `BitOr` / `Not` this type already carries — there is no lane
3509+
// index anywhere below. LLVM lowers the same source to `vpand`/`vpandn` on
3510+
// ymm (v3), `vandq_u64`/`vbicq_u64` on NEON, and `v128_and`/`v128_andnot` on
3511+
// wasm; the `repr(align(64))` backing is what earns the aligned moves.
3512+
//
3513+
// `IMM` is a const generic, so each `if IMM & bit` folds at compile time and
3514+
// only the minterms the truth table names survive. `AND3` (0x80) reduces to
3515+
// two ANDs of the whole register.
35763516

35773517
impl U64x8 {
3578-
/// Set difference: `self & !other`, lane-wise.
3518+
/// Set difference: `self & !other`.
35793519
///
35803520
/// **Argument order differs from the raw Intel intrinsic.**
3581-
/// `_mm256_andnot_si256(a, b)` computes `!a & b`; this method computes
3582-
/// `self & !other` — "self minus other". Every backend implements this
3583-
/// same direction.
3521+
/// `_mm*_andnot_si*(a, b)` computes `!a & b`; this computes
3522+
/// `self & !other` — "self minus other". Every backend, same direction.
35843523
///
35853524
/// Total function: no saturation, no overflow, no UB. `x.andnot(x)` is
35863525
/// zero; `x.andnot(U64x8::splat(0))` is `x`.
35873526
#[inline(always)]
35883527
pub fn andnot(self, other: Self) -> Self {
3589-
let mut o = [0u64; 8];
3590-
for i in 0..8 {
3591-
o[i] = self.0[i] & !other.0[i];
3592-
}
3593-
Self(o)
3528+
self & !other
35943529
}
35953530

35963531
/// Any 3-input boolean function of `self`, `b` and `c`, selected by the
@@ -3599,36 +3534,77 @@ impl U64x8 {
35993534
/// Per bit position: `index = (self << 2) | (b << 1) | c`, result bit =
36003535
/// `(IMM >> index) & 1` — Intel's VPTERNLOG convention, matched exactly by
36013536
/// every backend. `IMM` is `i32` to mirror the intrinsic's signature; only
3602-
/// `0..=255` is legal and the AVX-512 backend rejects wider values at
3603-
/// compile time. Within that domain: total function, no lane interaction.
3537+
/// `0..=255` is legal, enforced at compile time on the AVX-512 backend by
3538+
/// the intrinsic's own static assert. Within that domain: total function,
3539+
/// no lane interaction.
36043540
#[inline(always)]
36053541
pub fn ternlog<const IMM: i32>(self, b: Self, c: Self) -> Self {
3606-
let mut o = [0u64; 8];
3607-
for i in 0..8 {
3608-
o[i] = ternlog_lane_u64_avx2(self.0[i], b.0[i], c.0[i], IMM);
3542+
let (a, z) = (self, Self::splat(0));
3543+
let mut r = z;
3544+
if IMM & 0x01 != 0 {
3545+
r = r | !a & !b & !c;
36093546
}
3610-
Self(o)
3547+
if IMM & 0x02 != 0 {
3548+
r = r | !a & !b & c;
3549+
}
3550+
if IMM & 0x04 != 0 {
3551+
r = r | !a & b & !c;
3552+
}
3553+
if IMM & 0x08 != 0 {
3554+
r = r | !a & b & c;
3555+
}
3556+
if IMM & 0x10 != 0 {
3557+
r = r | a & !b & !c;
3558+
}
3559+
if IMM & 0x20 != 0 {
3560+
r = r | a & !b & c;
3561+
}
3562+
if IMM & 0x40 != 0 {
3563+
r = r | a & b & !c;
3564+
}
3565+
if IMM & 0x80 != 0 {
3566+
r = r | a & b & c;
3567+
}
3568+
r
36113569
}
36123570
}
36133571

36143572
impl U32x16 {
3615-
/// Set difference: `self & !other`, lane-wise. See [`U64x8::andnot`].
3573+
/// Set difference: `self & !other`. See [`U64x8::andnot`].
36163574
#[inline(always)]
36173575
pub fn andnot(self, other: Self) -> Self {
3618-
let mut o = [0u32; 16];
3619-
for i in 0..16 {
3620-
o[i] = self.0[i] & !other.0[i];
3621-
}
3622-
Self(o)
3576+
self & !other
36233577
}
36243578

36253579
/// Any 3-input boolean function, 32-bit lanes. See [`U64x8::ternlog`].
36263580
#[inline(always)]
36273581
pub fn ternlog<const IMM: i32>(self, b: Self, c: Self) -> Self {
3628-
let mut o = [0u32; 16];
3629-
for i in 0..16 {
3630-
o[i] = ternlog_lane_u32_avx2(self.0[i], b.0[i], c.0[i], IMM);
3582+
let (a, z) = (self, Self::splat(0));
3583+
let mut r = z;
3584+
if IMM & 0x01 != 0 {
3585+
r = r | !a & !b & !c;
36313586
}
3632-
Self(o)
3587+
if IMM & 0x02 != 0 {
3588+
r = r | !a & !b & c;
3589+
}
3590+
if IMM & 0x04 != 0 {
3591+
r = r | !a & b & !c;
3592+
}
3593+
if IMM & 0x08 != 0 {
3594+
r = r | !a & b & c;
3595+
}
3596+
if IMM & 0x10 != 0 {
3597+
r = r | a & !b & !c;
3598+
}
3599+
if IMM & 0x20 != 0 {
3600+
r = r | a & !b & c;
3601+
}
3602+
if IMM & 0x40 != 0 {
3603+
r = r | a & b & !c;
3604+
}
3605+
if IMM & 0x80 != 0 {
3606+
r = r | a & b & c;
3607+
}
3608+
r
36333609
}
36343610
}

0 commit comments

Comments
 (0)