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 }