@@ -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
35773517impl 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
36143572impl 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