lib/simd/src/arithmetic.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub fn add(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
4 return if (comptime isInteger(D.Lane)) a +% b else a + b;
5 }
6
7 pub fn sub(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
8 return if (comptime isInteger(D.Lane)) a -% b else a - b;
9 }
10
11 pub fn mul(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
12 return if (comptime isInteger(D.Lane)) a *% b else a * b;
13 }
14
15 pub fn addSub(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
16 var result: D.Vector = undefined;
17 inline for (0..D.lane_count) |index| {
18 result[index] = if (index & 1 == 0)
19 subLane(D.Lane, a[index], b[index])
20 else
21 addLane(D.Lane, a[index], b[index]);
22 }
23 return result;
24 }
25
26 pub fn neg(comptime D: type, value: D.Vector) D.Vector {
27 return switch (@typeInfo(D.Lane)) {
28 .int => |info| if (info.signedness == .signed)
29 @as(D.Vector, @splat(0)) -% value
30 else
31 @compileError("neg requires signed integer or floating-point lanes"),
32 .float => -value,
33 else => unreachable,
34 };
35 }
36
37 pub fn saturatedNeg(comptime D: type, value: D.Vector) D.Vector {
38 requireSignedInteger(D.Lane, "saturatedNeg");
39 var result: D.Vector = undefined;
40 inline for (0..D.lane_count) |index| {
41 result[index] = if (value[index] == std.math.minInt(D.Lane))
42 std.math.maxInt(D.Lane)
43 else
44 -value[index];
45 }
46 return result;
47 }
48
49 pub fn abs(comptime D: type, value: D.Vector) D.Vector {
50 return switch (@typeInfo(D.Lane)) {
51 .int => |info| if (info.signedness == .signed)
52 absInteger(D, value)
53 else
54 @compileError("abs requires signed integer or floating-point lanes"),
55 .float => @abs(value),
56 else => unreachable,
57 };
58 }
59
60 pub fn saturatedAbs(comptime D: type, value: D.Vector) D.Vector {
61 requireSignedInteger(D.Lane, "saturatedAbs");
62 var result: D.Vector = undefined;
63 inline for (0..D.lane_count) |index| {
64 result[index] = if (value[index] == std.math.minInt(D.Lane))
65 std.math.maxInt(D.Lane)
66 else if (value[index] < 0)
67 -value[index]
68 else
69 value[index];
70 }
71 return result;
72 }
73
74 pub fn absDiff(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
75 if (comptime @typeInfo(D.Lane) == .float) return @abs(a - b);
76 if (comptime @typeInfo(D.Lane) != .int) @compileError("absDiff requires numeric lanes");
77 const U = @Int(.unsigned, @bitSizeOf(D.Lane));
78 var result: D.Vector = undefined;
79 inline for (0..D.lane_count) |index| {
80 const a_bits: U = @bitCast(a[index]);
81 const b_bits: U = @bitCast(b[index]);
82 const difference = if (a[index] >= b[index]) a_bits -% b_bits else b_bits -% a_bits;
83 result[index] = @bitCast(difference);
84 }
85 return result;
86 }
87
88 pub fn saturatedAdd(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
89 requireInteger(D.Lane, "saturatedAdd");
90 var result: D.Vector = undefined;
91 inline for (0..D.lane_count) |index| {
92 result[index] = saturatedAddLane(D.Lane, a[index], b[index]);
93 }
94 return result;
95 }
96
97 pub fn saturatedSub(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
98 requireInteger(D.Lane, "saturatedSub");
99 var result: D.Vector = undefined;
100 inline for (0..D.lane_count) |index| {
101 result[index] = saturatedSubLane(D.Lane, a[index], b[index]);
102 }
103 return result;
104 }
105
106 pub fn div(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
107 if (comptime @typeInfo(D.Lane) == .float) return a / b;
108 requireInteger(D.Lane, "div");
109 var result: D.Vector = undefined;
110 inline for (0..D.lane_count) |index| {
111 result[index] = safeDivLane(D.Lane, a[index], b[index]);
112 }
113 return result;
114 }
115
116 pub fn mod(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
117 requireInteger(D.Lane, "mod");
118 var result: D.Vector = undefined;
119 inline for (0..D.lane_count) |index| {
120 result[index] = safeModLane(D.Lane, a[index], b[index]);
121 }
122 return result;
123 }
124
125 pub fn min(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
126 return @select(D.Lane, a < b, a, b);
127 }
128
129 pub fn max(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
130 return @select(D.Lane, a > b, a, b);
131 }
132
133 pub fn minNumber(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
134 if (comptime @typeInfo(D.Lane) != .float) return min(D, a, b);
135 const a_nan = a != a;
136 const b_nan = b != b;
137 return @select(
138 D.Lane,
139 a_nan,
140 b,
141 @select(D.Lane, b_nan, a, min(D, a, b)),
142 );
143 }
144
145 pub fn maxNumber(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
146 if (comptime @typeInfo(D.Lane) != .float) return max(D, a, b);
147 const a_nan = a != a;
148 const b_nan = b != b;
149 return @select(
150 D.Lane,
151 a_nan,
152 b,
153 @select(D.Lane, b_nan, a, max(D, a, b)),
154 );
155 }
156
157 pub fn minMagnitude(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
158 return magnitudeChoice(D, a, b, false);
159 }
160
161 pub fn maxMagnitude(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
162 return magnitudeChoice(D, a, b, true);
163 }
164
165 pub fn min128(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
166 return choose128(D, a, b, false, false);
167 }
168
169 pub fn max128(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
170 return choose128(D, a, b, true, false);
171 }
172
173 pub fn min128Upper(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
174 return choose128(D, a, b, false, true);
175 }
176
177 pub fn max128Upper(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
178 return choose128(D, a, b, true, true);
179 }
180
181 pub fn clamp(
182 comptime D: type,
183 value: D.Vector,
184 lower: D.Vector,
185 upper: D.Vector,
186 ) D.Vector {
187 return max(D, lower, min(D, value, upper));
188 }
189
190 pub fn mulAdd(
191 comptime D: type,
192 multiplicand: D.Vector,
193 multiplier: D.Vector,
194 addend: D.Vector,
195 ) D.Vector {
196 if (comptime isInteger(D.Lane)) {
197 return add(D, mul(D, multiplicand, multiplier), addend);
198 }
199 return @mulAdd(D.Vector, multiplicand, multiplier, addend);
200 }
201
202 pub fn negMulAdd(
203 comptime D: type,
204 multiplicand: D.Vector,
205 multiplier: D.Vector,
206 addend: D.Vector,
207 ) D.Vector {
208 if (comptime isInteger(D.Lane)) {
209 return add(D, mul(D, neg(D, multiplicand), multiplier), addend);
210 }
211 return @mulAdd(D.Vector, -multiplicand, multiplier, addend);
212 }
213
214 pub fn mulSub(
215 comptime D: type,
216 multiplicand: D.Vector,
217 multiplier: D.Vector,
218 subtrahend: D.Vector,
219 ) D.Vector {
220 if (comptime isInteger(D.Lane)) {
221 return sub(D, mul(D, multiplicand, multiplier), subtrahend);
222 }
223 return @mulAdd(D.Vector, multiplicand, multiplier, -subtrahend);
224 }
225
226 pub fn negMulSub(
227 comptime D: type,
228 multiplicand: D.Vector,
229 multiplier: D.Vector,
230 subtrahend: D.Vector,
231 ) D.Vector {
232 if (comptime isInteger(D.Lane)) {
233 return sub(D, mul(D, neg(D, multiplicand), multiplier), subtrahend);
234 }
235 return @mulAdd(D.Vector, -multiplicand, multiplier, -subtrahend);
236 }
237
238 pub fn mulAddSub(
239 comptime D: type,
240 multiplicand: D.Vector,
241 multiplier: D.Vector,
242 addend: D.Vector,
243 ) D.Vector {
244 return alternate(D, mulSub(D, multiplicand, multiplier, addend), mulAdd(D, multiplicand, multiplier, addend));
245 }
246
247 pub fn mulSubAdd(
248 comptime D: type,
249 multiplicand: D.Vector,
250 multiplier: D.Vector,
251 addend: D.Vector,
252 ) D.Vector {
253 return alternate(D, mulAdd(D, multiplicand, multiplier, addend), mulSub(D, multiplicand, multiplier, addend));
254 }
255
256 pub fn maskedMinOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
257 return maskedOr(D, no, mask, min(D, a, b));
258 }
259
260 pub fn maskedMaxOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
261 return maskedOr(D, no, mask, max(D, a, b));
262 }
263
264 pub fn maskedAddOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
265 return maskedOr(D, no, mask, add(D, a, b));
266 }
267
268 pub fn maskedSubOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
269 return maskedOr(D, no, mask, sub(D, a, b));
270 }
271
272 pub fn maskedMulOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
273 return maskedOr(D, no, mask, mul(D, a, b));
274 }
275
276 pub fn maskedDivOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
277 return maskedOr(D, no, mask, div(D, a, b));
278 }
279
280 pub fn maskedModOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
281 return maskedOr(D, no, mask, mod(D, a, b));
282 }
283
284 pub fn maskedSatAddOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
285 return maskedOr(D, no, mask, saturatedAdd(D, a, b));
286 }
287
288 pub fn maskedSatSubOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
289 return maskedOr(D, no, mask, saturatedSub(D, a, b));
290 }
291
292 pub fn maskedAbsOr(comptime D: type, no: D.Vector, mask: D.Mask, value: D.Vector) D.Vector {
293 return maskedOr(D, no, mask, abs(D, value));
294 }
295
296 pub fn maskedMulAddOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
297 return maskedOr(D, no, mask, mulAdd(D, a, b, c));
298 }
299
300 pub fn maskedMulSubOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
301 return maskedOr(D, no, mask, mulSub(D, a, b, c));
302 }
303
304 pub fn maskedNegMulAddOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
305 return maskedOr(D, no, mask, negMulAdd(D, a, b, c));
306 }
307
308 pub fn maskedNegMulSubOr(comptime D: type, no: D.Vector, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
309 return maskedOr(D, no, mask, negMulSub(D, a, b, c));
310 }
311
312 pub fn maskedAbs(comptime D: type, mask: D.Mask, value: D.Vector) D.Vector {
313 return maskedZero(D, mask, abs(D, value));
314 }
315
316 pub fn maskedMax(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
317 return maskedZero(D, mask, max(D, a, b));
318 }
319
320 pub fn maskedAdd(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
321 return maskedZero(D, mask, add(D, a, b));
322 }
323
324 pub fn maskedSub(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
325 return maskedZero(D, mask, sub(D, a, b));
326 }
327
328 pub fn maskedMul(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
329 return maskedZero(D, mask, mul(D, a, b));
330 }
331
332 pub fn maskedDiv(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
333 return maskedZero(D, mask, div(D, a, b));
334 }
335
336 pub fn maskedSaturatedAdd(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
337 return maskedZero(D, mask, saturatedAdd(D, a, b));
338 }
339
340 pub fn maskedSaturatedSub(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector) D.Vector {
341 return maskedZero(D, mask, saturatedSub(D, a, b));
342 }
343
344 pub fn maskedMulAdd(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
345 return maskedZero(D, mask, mulAdd(D, a, b, c));
346 }
347
348 pub fn maskedMulSub(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
349 return maskedZero(D, mask, mulSub(D, a, b, c));
350 }
351
352 pub fn maskedNegMulAdd(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
353 return maskedZero(D, mask, negMulAdd(D, a, b, c));
354 }
355
356 pub fn maskedNegMulSub(comptime D: type, mask: D.Mask, a: D.Vector, b: D.Vector, c: D.Vector) D.Vector {
357 return maskedZero(D, mask, negMulSub(D, a, b, c));
358 }
359
360 pub fn pairwiseAdd(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
361 if (D.lane_count < 2) @compileError("pairwiseAdd requires at least two lanes");
362 var result: D.Vector = undefined;
363 inline for (0..D.lane_count / 2) |pair| {
364 result[pair * 2] = addLane(D.Lane, a[pair * 2], a[pair * 2 + 1]);
365 result[pair * 2 + 1] = addLane(D.Lane, b[pair * 2], b[pair * 2 + 1]);
366 }
367 return result;
368 }
369
370 pub fn pairwiseSub(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
371 if (D.lane_count < 2) @compileError("pairwiseSub requires at least two lanes");
372 var result: D.Vector = undefined;
373 inline for (0..D.lane_count / 2) |pair| {
374 result[pair * 2] = subLane(D.Lane, a[pair * 2 + 1], a[pair * 2]);
375 result[pair * 2 + 1] = subLane(D.Lane, b[pair * 2 + 1], b[pair * 2]);
376 }
377 return result;
378 }
379
380 pub fn pairwiseAdd128(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
381 return pairwise128(D, false, a, b);
382 }
383
384 pub fn pairwiseSub128(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
385 return pairwise128(D, true, a, b);
386 }
387
388 fn pairwise128(comptime D: type, comptime subtract: bool, a: D.Vector, b: D.Vector) D.Vector {
389 if (D.byte_count < 16) @compileError("128-bit pairwise operations require at least one full block");
390 const lanes_per_block = 16 / @sizeOf(D.Lane);
391 if (lanes_per_block < 2) @compileError("pairwise operations require at least two lanes per block");
392 var result: D.Vector = undefined;
393 inline for (0..D.lane_count / lanes_per_block) |block| {
394 inline for (0..lanes_per_block / 2) |pair| {
395 const source = block * lanes_per_block + pair * 2;
396 const destination = block * lanes_per_block + pair;
397 result[destination] = if (subtract)
398 subLane(D.Lane, a[source + 1], a[source])
399 else
400 addLane(D.Lane, a[source], a[source + 1]);
401 result[destination + lanes_per_block / 2] = if (subtract)
402 subLane(D.Lane, b[source + 1], b[source])
403 else
404 addLane(D.Lane, b[source], b[source + 1]);
405 }
406 }
407 return result;
408 }
409
410 fn addLane(comptime T: type, a: T, b: T) T {
411 return if (@typeInfo(T) == .int) a +% b else a + b;
412 }
413
414 fn subLane(comptime T: type, a: T, b: T) T {
415 return if (@typeInfo(T) == .int) a -% b else a - b;
416 }
417
418 fn isInteger(comptime T: type) bool {
419 return @typeInfo(T) == .int;
420 }
421
422 fn absInteger(comptime D: type, value: D.Vector) D.Vector {
423 var result: D.Vector = undefined;
424 inline for (0..D.lane_count) |index| {
425 result[index] = if (value[index] < 0) 0 -% value[index] else value[index];
426 }
427 return result;
428 }
429
430 fn saturatedAddLane(comptime T: type, a: T, b: T) T {
431 return if (@typeInfo(T).int.signedness == .signed) blk: {
432 const sum = @as(i128, a) + @as(i128, b);
433 break :blk @intCast(std.math.clamp(
434 sum,
435 @as(i128, std.math.minInt(T)),
436 @as(i128, std.math.maxInt(T)),
437 ));
438 } else blk: {
439 const sum = @as(u128, a) + @as(u128, b);
440 break :blk @intCast(@min(sum, @as(u128, std.math.maxInt(T))));
441 };
442 }
443
444 fn saturatedSubLane(comptime T: type, a: T, b: T) T {
445 return if (@typeInfo(T).int.signedness == .signed) blk: {
446 const difference = @as(i128, a) - @as(i128, b);
447 break :blk @intCast(std.math.clamp(
448 difference,
449 @as(i128, std.math.minInt(T)),
450 @as(i128, std.math.maxInt(T)),
451 ));
452 } else if (a < b) 0 else a - b;
453 }
454
455 fn safeDivLane(comptime T: type, a: T, b: T) T {
456 if (b == 0) return 0;
457 if (@typeInfo(T).int.signedness == .signed and
458 a == std.math.minInt(T) and b == -1)
459 {
460 return 0;
461 }
462 return @divTrunc(a, b);
463 }
464
465 fn safeModLane(comptime T: type, a: T, b: T) T {
466 if (b == 0) return 0;
467 if (@typeInfo(T).int.signedness == .signed and
468 a == std.math.minInt(T) and b == -1)
469 {
470 return 0;
471 }
472 return @rem(a, b);
473 }
474
475 fn magnitudeChoice(
476 comptime D: type,
477 a: D.Vector,
478 b: D.Vector,
479 comptime choose_maximum: bool,
480 ) D.Vector {
481 var result: D.Vector = undefined;
482 inline for (0..D.lane_count) |index| {
483 const a_smaller = magnitudeLess(D.Lane, a[index], b[index]);
484 result[index] = if (choose_maximum)
485 (if (a_smaller) b[index] else a[index])
486 else
487 (if (a_smaller) a[index] else b[index]);
488 }
489 return result;
490 }
491
492 fn magnitudeLess(comptime T: type, a: T, b: T) bool {
493 return switch (@typeInfo(T)) {
494 .float => blk: {
495 const magnitude_a = @abs(a);
496 const magnitude_b = @abs(b);
497 break :blk magnitude_a < magnitude_b or
498 (magnitude_a == magnitude_b and a < b);
499 },
500 .int => |info| if (info.signedness == .unsigned)
501 a < b
502 else blk: {
503 const U = @Int(.unsigned, @bitSizeOf(T));
504 const a_bits: U = @bitCast(a);
505 const b_bits: U = @bitCast(b);
506 const magnitude_a = if (a < 0) 0 -% a_bits else a_bits;
507 const magnitude_b = if (b < 0) 0 -% b_bits else b_bits;
508 break :blk magnitude_a < magnitude_b or
509 (magnitude_a == magnitude_b and a < b);
510 },
511 else => unreachable,
512 };
513 }
514
515 fn choose128(
516 comptime D: type,
517 a: D.Vector,
518 b: D.Vector,
519 comptime choose_maximum: bool,
520 comptime upper_only: bool,
521 ) D.Vector {
522 if (D.Lane != u64 or D.lane_count < 2 or D.lane_count & 1 != 0) {
523 @compileError("128-bit min/max requires an even number of u64 lanes");
524 }
525 var result: D.Vector = undefined;
526 inline for (0..D.lane_count / 2) |pair| {
527 const low = pair * 2;
528 const high = low + 1;
529 const a_less = if (upper_only)
530 a[high] < b[high]
531 else
532 a[high] < b[high] or (a[high] == b[high] and a[low] < b[low]);
533 const a_greater = if (upper_only)
534 a[high] > b[high]
535 else
536 a[high] > b[high] or (a[high] == b[high] and a[low] > b[low]);
537 const take_a = if (choose_maximum) a_greater else a_less;
538 result[low] = if (take_a) a[low] else b[low];
539 result[high] = if (take_a) a[high] else b[high];
540 }
541 return result;
542 }
543
544 fn alternate(comptime D: type, even: D.Vector, odd: D.Vector) D.Vector {
545 var result: D.Vector = undefined;
546 inline for (0..D.lane_count) |index| {
547 result[index] = if (index & 1 == 0) even[index] else odd[index];
548 }
549 return result;
550 }
551
552 fn maskedOr(comptime D: type, no: D.Vector, mask: D.Mask, yes: D.Vector) D.Vector {
553 return @select(D.Lane, mask, yes, no);
554 }
555
556 fn maskedZero(comptime D: type, mask: D.Mask, yes: D.Vector) D.Vector {
557 return maskedOr(D, @as(D.Vector, @splat(0)), mask, yes);
558 }
559
560 fn requireInteger(comptime T: type, comptime operation: []const u8) void {
561 if (@typeInfo(T) != .int) @compileError(operation ++ " requires integer lanes");
562 }
563
564 fn requireSignedInteger(comptime T: type, comptime operation: []const u8) void {
565 if (@typeInfo(T) != .int or @typeInfo(T).int.signedness != .signed) {
566 @compileError(operation ++ " requires signed integer lanes");
567 }
568 }
569
570 test "integer arithmetic wraps lane by lane" {
571 const simd = @import("root.zig");
572 const D = simd.FixedTag(u8, 4);
573 const a: D.Vector = .{ 255, 0, 200, 3 };
574 const b: D.Vector = .{ 1, 1, 2, 100 };
575 try std.testing.expect(@reduce(.And, add(D, a, b) ==
576 @as(D.Vector, .{ 0, 1, 202, 103 })));
577 try std.testing.expect(@reduce(.And, sub(D, a, b) ==
578 @as(D.Vector, .{ 254, 255, 198, 159 })));
579 try std.testing.expect(@reduce(.And, mul(D, a, b) ==
580 @as(D.Vector, .{ 255, 0, 144, 44 })));
581 }
582
583 test "floating arithmetic and clamp retain lane semantics" {
584 const simd = @import("root.zig");
585 const D = simd.FixedTag(f32, 4);
586 const a: D.Vector = .{ -2, 1, 4, 8 };
587 const b: D.Vector = @splat(2);
588 try std.testing.expect(@reduce(.And, add(D, a, b) ==
589 @as(D.Vector, .{ 0, 3, 6, 10 })));
590 try std.testing.expect(@reduce(.And, clamp(D, a, @splat(0), @splat(5)) ==
591 @as(D.Vector, .{ 0, 1, 4, 5 })));
592 }
593
594 test "Highway pairwise arithmetic interleaves source folds" {
595 const simd = @import("root.zig");
596 const D = simd.FixedTag(i16, 8);
597 const a: D.Vector = .{ 1, 2, 3, 4, 5, 6, 7, 8 };
598 const b: D.Vector = .{ 10, 20, 30, 40, 50, 60, 70, 80 };
599 try std.testing.expect(@reduce(.And, pairwiseAdd(D, a, b) == @as(D.Vector, .{
600 3, 30, 7, 70, 11, 110, 15, 150,
601 })));
602 try std.testing.expect(@reduce(.And, pairwiseSub(D, a, b) == @as(D.Vector, .{
603 1, 10, 1, 10, 1, 10, 1, 10,
604 })));
605 }
606
607 test "Highway 128-bit pairwise arithmetic packs each source half per block" {
608 const simd = @import("root.zig");
609 const D = simd.FixedTag(u32, 8);
610 const a: D.Vector = .{ 1, 2, 3, 4, 5, 6, 7, 8 };
611 const b: D.Vector = .{ 10, 20, 30, 40, 50, 60, 70, 80 };
612 try std.testing.expect(@reduce(.And, pairwiseAdd128(D, a, b) == @as(D.Vector, .{
613 3, 7, 30, 70, 11, 15, 110, 150,
614 })));
615 try std.testing.expect(@reduce(.And, pairwiseSub128(D, a, b) == @as(D.Vector, .{
616 1, 1, 10, 10, 1, 1, 10, 10,
617 })));
618 }
619
620 fn verifyPairwiseLaneType(comptime T: type) !void {
621 const simd = @import("root.zig");
622 const D = simd.FixedTag(T, @max(2, 16 / @sizeOf(T)));
623 const value: D.Vector = @splat(0);
624 try std.testing.expect(@reduce(.And, pairwiseAdd(D, value, value) == value));
625 try std.testing.expect(@reduce(.And, pairwiseSub(D, value, value) == value));
626 try std.testing.expect(@reduce(.And, pairwiseAdd128(D, value, value) == value));
627 try std.testing.expect(@reduce(.And, pairwiseSub128(D, value, value) == value));
628 }
629
630 test "Highway pairwise arithmetic instantiates every lane type" {
631 inline for (.{ u8, i8, u16, i16, u32, i32, u64, i64, f16, f32, f64 }) |T| {
632 try verifyPairwiseLaneType(T);
633 }
634 }
635
636 test "Highway absolute saturating division and alternating arithmetic retain edge semantics" {
637 const simd = @import("root.zig");
638 const D = simd.FixedTag(i16, 8);
639 const a: D.Vector = .{ std.math.minInt(i16), -300, -7, -1, 0, 7, 300, std.math.maxInt(i16) };
640 const b: D.Vector = .{ -1, 1000, 3, 2, 0, -3, 1000, 1 };
641 try std.testing.expect(@reduce(.And, abs(D, a) == @as(D.Vector, .{
642 std.math.minInt(i16), 300, 7, 1, 0, 7, 300, std.math.maxInt(i16),
643 })));
644 try std.testing.expect(@reduce(.And, saturatedAbs(D, a) == @as(D.Vector, .{
645 std.math.maxInt(i16), 300, 7, 1, 0, 7, 300, std.math.maxInt(i16),
646 })));
647 try std.testing.expect(@reduce(.And, saturatedNeg(D, a) == @as(D.Vector, .{
648 std.math.maxInt(i16), 300, 7, 1, 0, -7, -300, -std.math.maxInt(i16),
649 })));
650 try std.testing.expect(@reduce(.And, saturatedAdd(D, a, b) == @as(D.Vector, .{
651 std.math.minInt(i16), 700, -4, 1, 0, 4, 1300, std.math.maxInt(i16),
652 })));
653 try std.testing.expect(@reduce(.And, saturatedSub(D, a, b) == @as(D.Vector, .{
654 -32767, -1300, -10, -3, 0, 10, -700, 32766,
655 })));
656 try std.testing.expect(@reduce(.And, div(D, a, b) == @as(D.Vector, .{
657 0, 0, -2, 0, 0, -2, 0, 32767,
658 })));
659 try std.testing.expect(@reduce(.And, mod(D, a, b) == @as(D.Vector, .{
660 0, -300, -1, -1, 0, 1, 300, 0,
661 })));
662 try std.testing.expect(@reduce(.And, addSub(D, a, b) == @as(D.Vector, .{
663 -32767, 700, -10, 1, 0, 4, -700, -32768,
664 })));
665 }
666
667 test "Highway absolute difference and saturation instantiate every integer lane" {
668 const simd = @import("root.zig");
669 inline for (.{ u8, i8, u16, i16, u32, i32, u64, i64 }) |T| {
670 const D = simd.FixedTag(T, 4);
671 const zero: D.Vector = @splat(0);
672 const one: D.Vector = @splat(1);
673 _ = absDiff(D, zero, one);
674 _ = saturatedAdd(D, zero, one);
675 _ = saturatedSub(D, zero, one);
676 _ = div(D, one, one);
677 _ = mod(D, one, one);
678 _ = maskedSaturatedAdd(D, @as(D.Mask, @splat(true)), zero, one);
679 _ = maskedSaturatedSub(D, @as(D.Mask, @splat(true)), zero, one);
680 }
681 }
682
683 test "Highway number and magnitude extrema handle NaN ties and signed minima" {
684 const simd = @import("root.zig");
685 const F = simd.FixedTag(f32, 4);
686 const nan_value = std.math.nan(f32);
687 const a: F.Vector = .{ nan_value, 3, -4, -2 };
688 const b: F.Vector = .{ 7, nan_value, 2, 2 };
689 const minimum = minNumber(F, a, b);
690 const maximum = maxNumber(F, a, b);
691 try std.testing.expectEqual(@as(f32, 7), minimum[0]);
692 try std.testing.expectEqual(@as(f32, 3), minimum[1]);
693 try std.testing.expectEqual(@as(f32, -4), minimum[2]);
694 try std.testing.expectEqual(@as(f32, -2), minimum[3]);
695 try std.testing.expectEqual(@as(f32, 7), maximum[0]);
696 try std.testing.expectEqual(@as(f32, 3), maximum[1]);
697 const I = simd.FixedTag(i32, 4);
698 const x: I.Vector = .{ std.math.minInt(i32), -7, -3, 5 };
699 const y: I.Vector = .{ std.math.maxInt(i32), 6, 3, -5 };
700 try std.testing.expect(@reduce(.And, minMagnitude(I, x, y) == @as(I.Vector, .{
701 std.math.maxInt(i32), 6, -3, -5,
702 })));
703 try std.testing.expect(@reduce(.And, maxMagnitude(I, x, y) == @as(I.Vector, .{
704 std.math.minInt(i32), -7, 3, 5,
705 })));
706 }
707
708 test "Highway 128-bit extrema compare complete pairs or upper keys" {
709 const simd = @import("root.zig");
710 const D = simd.FixedTag(u64, 8);
711 const a: D.Vector = .{ 9, 1, 7, 4, 10, 6, 20, 8 };
712 const b: D.Vector = .{ 10, 1, 8, 3, 11, 6, 19, 8 };
713 try std.testing.expect(@reduce(.And, min128(D, a, b) == @as(D.Vector, .{
714 9, 1, 8, 3, 10, 6, 19, 8,
715 })));
716 try std.testing.expect(@reduce(.And, max128(D, a, b) == @as(D.Vector, .{
717 10, 1, 7, 4, 11, 6, 20, 8,
718 })));
719 try std.testing.expect(@reduce(.And, min128Upper(D, a, b) == @as(D.Vector, .{
720 10, 1, 8, 3, 11, 6, 19, 8,
721 })));
722 try std.testing.expect(@reduce(.And, max128Upper(D, a, b) == @as(D.Vector, .{
723 10, 1, 7, 4, 11, 6, 19, 8,
724 })));
725 }
726
727 test "Highway fused and masked arithmetic alternate and merge exact lanes" {
728 const simd = @import("root.zig");
729 const D = simd.FixedTag(f32, 4);
730 const a: D.Vector = .{ 2, 3, 4, 5 };
731 const b: D.Vector = @splat(2);
732 const c: D.Vector = @splat(1);
733 try std.testing.expect(@reduce(.And, mulAddSub(D, a, b, c) == @as(D.Vector, .{ 3, 7, 7, 11 })));
734 try std.testing.expect(@reduce(.And, mulSubAdd(D, a, b, c) == @as(D.Vector, .{ 5, 5, 9, 9 })));
735 const mask: D.Mask = .{ true, false, true, false };
736 try std.testing.expect(@reduce(.And, maskedMulAddOr(D, @splat(9), mask, a, b, c) ==
737 @as(D.Vector, .{ 5, 9, 9, 9 })));
738 try std.testing.expect(@reduce(.And, maskedNegMulSub(D, mask, a, b, c) ==
739 @as(D.Vector, .{ -5, 0, -9, 0 })));
740 }