lib/simd/src/logical.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 pub fn bitNot(comptime D: type, value: D.Vector) D.Vector {
  4     const UVector = unsignedVector(D);
  5     const bits: UVector = @bitCast(value);
  6     return @bitCast(~bits);
  7 }
  8 
  9 pub fn bitAnd(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 10     const UVector = unsignedVector(D);
 11     const a_bits: UVector = @bitCast(a);
 12     const b_bits: UVector = @bitCast(b);
 13     return @bitCast(a_bits & b_bits);
 14 }
 15 
 16 pub fn andNot(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 17     return bitAnd(D, bitNot(D, a), b);
 18 }
 19 
 20 pub fn bitOr(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 21     const UVector = unsignedVector(D);
 22     const a_bits: UVector = @bitCast(a);
 23     const b_bits: UVector = @bitCast(b);
 24     return @bitCast(a_bits | b_bits);
 25 }
 26 
 27 pub fn bitXor(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 28     const UVector = unsignedVector(D);
 29     const a_bits: UVector = @bitCast(a);
 30     const b_bits: UVector = @bitCast(b);
 31     return @bitCast(a_bits ^ b_bits);
 32 }
 33 
 34 pub fn or3(comptime D: type, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
 35     return bitOr(D, a, bitOr(D, b, c));
 36 }
 37 
 38 pub fn xor3(comptime D: type, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
 39     return bitXor(D, a, bitXor(D, b, c));
 40 }
 41 
 42 pub fn orAnd(comptime D: type, o: D.Vector, a: D.Vector, b: D.Vector) D.Vector {
 43     return bitOr(D, o, bitAnd(D, a, b));
 44 }
 45 
 46 pub fn xorAndNot(comptime D: type, x: D.Vector, a: D.Vector, b: D.Vector) D.Vector {
 47     return bitXor(D, x, andNot(D, a, b));
 48 }
 49 
 50 pub fn andXor(comptime D: type, a: D.Vector, x: D.Vector, y: D.Vector) D.Vector {
 51     return bitAnd(D, a, bitXor(D, x, y));
 52 }
 53 
 54 pub fn maskedOrOr(
 55     comptime D: type,
 56     inactive: D.Vector,
 57     mask: D.Mask,
 58     a: D.Vector,
 59     b: D.Vector,
 60 ) D.Vector {
 61     return @select(D.Lane, mask, bitOr(D, a, b), inactive);
 62 }
 63 
 64 pub fn maskedOr(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
 65     return maskedOrOr(D, @splat(0), mask, a, b);
 66 }
 67 
 68 pub fn maskedXorOr(
 69     comptime D: type,
 70     inactive: D.Vector,
 71     mask: D.Mask,
 72     a: D.Vector,
 73     b: D.Vector,
 74 ) D.Vector {
 75     return @select(D.Lane, mask, bitXor(D, a, b), inactive);
 76 }
 77 
 78 pub fn maskedXor(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
 79     return maskedXorOr(D, @splat(0), mask, a, b);
 80 }
 81 
 82 pub fn testBit(comptime D: type, value: D.Vector, bit: D.Vector) D.Mask {
 83     if (comptime @typeInfo(D.Lane) != .int) {
 84         @compileError("testBit requires integer lanes");
 85     }
 86     return bitAnd(D, value, bit) == bit;
 87 }
 88 
 89 pub fn allBitsZero(comptime D: type, value: D.Vector) bool {
 90     const UVector = unsignedVector(D);
 91     const bits: UVector = @bitCast(value);
 92     return !@reduce(.Or, bits != @as(UVector, @splat(0)));
 93 }
 94 
 95 pub fn allBitsOne(comptime D: type, value: D.Vector) bool {
 96     const UVector = unsignedVector(D);
 97     const bits: UVector = @bitCast(value);
 98     return !@reduce(.Or, bits != @as(UVector, @splat(~@as(unsignedLane(D.Lane), 0))));
 99 }
100 
101 fn unsignedVector(comptime D: type) type {
102     return @Vector(D.lane_count, unsignedLane(D.Lane));
103 }
104 
105 fn unsignedLane(comptime T: type) type {
106     return @Int(.unsigned, @bitSizeOf(T));
107 }
108 
109 test "logical operations match Highway truth tables" {
110     const simd = @import("root.zig");
111     const D = simd.FixedTag(u32, 4);
112     const zero: D.Vector = @splat(0);
113     const value: D.Vector = .{ 0, 1, 2, 3 };
114     try std.testing.expect(@reduce(.And, bitAnd(D, value, value) == value));
115     try std.testing.expect(@reduce(.And, bitXor(D, value, value) == zero));
116     try std.testing.expect(@reduce(.And, bitOr(D, value, zero) == value));
117     try std.testing.expect(allBitsZero(D, bitAnd(D, value, bitNot(D, value))));
118     try std.testing.expect(allBitsOne(D, bitOr(D, value, bitNot(D, value))));
119 }
120 
121 test "logical operations preserve floating-point bit patterns" {
122     const simd = @import("root.zig");
123     const D = simd.FixedTag(f32, 4);
124     const U = simd.FixedTag(u32, 4);
125     const value: D.Vector = @bitCast(@as(U.Vector, .{ 0, 1, 0x8000_0000, 0x7f80_0000 }));
126     const twice = bitXor(D, value, value);
127     try std.testing.expect(allBitsZero(D, twice));
128     const inverted: U.Vector = @bitCast(bitNot(D, value));
129     try std.testing.expectEqual(@as(u32, 0xffff_ffff), inverted[0]);
130     try std.testing.expectEqual(@as(u32, 0x7fff_ffff), inverted[2]);
131 }
132 
133 test "Highway ternary and masked logical operations match scalar bit formulas" {
134     const simd = @import("root.zig");
135     const D = simd.FixedTag(u8, 4);
136     const a: D.Vector = .{ 0x0f, 0xf0, 0xaa, 0x55 };
137     const b: D.Vector = .{ 0x33, 0x33, 0xcc, 0xcc };
138     const c: D.Vector = .{ 0x55, 0xaa, 0x0f, 0xf0 };
139     try std.testing.expect(@reduce(.And, xorAndNot(D, a, b, c) == (a ^ (~b & c))));
140     try std.testing.expect(@reduce(.And, andXor(D, a, b, c) == (a & (b ^ c))));
141     const mask: D.Mask = .{ true, false, false, true };
142     try std.testing.expect(@reduce(.And, maskedOrOr(D, @splat(7), mask, a, b) ==
143         @as(D.Vector, .{ 0x3f, 7, 7, 0xdd })));
144     try std.testing.expect(@reduce(.And, maskedXor(D, mask, a, b) ==
145         @as(D.Vector, .{ 0x3c, 0, 0, 0x99 })));
146 }