lib/simd/src/convert.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const bfloat = @import("bfloat.zig");
3 const shift = @import("shift.zig");
4 const tag = @import("tag.zig");
5
6 pub fn convertTo(comptime D: type, value: anytype) D.Vector {
7 const V = @TypeOf(value);
8 const From = sourceLane(V);
9 validateLaneCount(D, V);
10 if (@bitSizeOf(D.Lane) != @bitSizeOf(From)) {
11 @compileError("convertTo requires equal source and destination lane widths");
12 }
13 if ((@typeInfo(From) == .float) == (@typeInfo(D.Lane) == .float)) {
14 @compileError("convertTo requires one floating-point and one integer lane type");
15 }
16 return numericCast(D, value);
17 }
18
19 pub fn convertInRangeTo(comptime D: type, value: anytype) D.Vector {
20 return convertTo(D, value);
21 }
22
23 pub fn maskedConvertTo(
24 comptime D: type,
25 mask: D.Mask,
26 value: anytype,
27 ) D.Vector {
28 return @select(D.Lane, mask, convertTo(D, value), @as(D.Vector, @splat(0)));
29 }
30
31 pub fn nearestInt(comptime D: type, value: anytype) D.Vector {
32 validateSameWidthFloatToSigned(D, @TypeOf(value));
33 return roundEvenToInt(D, value);
34 }
35
36 pub fn ceilInt(comptime D: type, value: anytype) D.Vector {
37 validateSameWidthFloatToSigned(D, @TypeOf(value));
38 return floatToInt(D, @ceil(value));
39 }
40
41 pub fn floorInt(comptime D: type, value: anytype) D.Vector {
42 validateSameWidthFloatToSigned(D, @TypeOf(value));
43 return floatToInt(D, @floor(value));
44 }
45
46 pub fn demoteToNearestInt(comptime D: type, value: anytype) D.Vector {
47 const V = @TypeOf(value);
48 const From = sourceLane(V);
49 validateLaneCount(D, V);
50 if (@typeInfo(D.Lane) != .int or
51 @typeInfo(D.Lane).int.signedness != .signed or
52 @typeInfo(From) != .float or
53 @bitSizeOf(D.Lane) >= @bitSizeOf(From))
54 {
55 @compileError("demoteToNearestInt requires narrower signed integer destinations");
56 }
57 return roundEvenToInt(D, value);
58 }
59
60 pub fn promoteTo(comptime D: type, value: anytype) D.Vector {
61 const V = @TypeOf(value);
62 const From = sourceLane(V);
63 validateLaneCount(D, V);
64 if (comptime D.Lane == f32 and From == u16) return bfloat.promoteF32(D, value);
65 if (@bitSizeOf(D.Lane) <= @bitSizeOf(From)) {
66 @compileError("promoteTo requires a wider destination lane");
67 }
68 if (@typeInfo(From) == .int and @typeInfo(D.Lane) == .int and
69 @typeInfo(From).int.signedness == .signed and
70 @typeInfo(D.Lane).int.signedness == .unsigned)
71 {
72 @compileError("promoteTo does not convert signed integers to unsigned integers");
73 }
74 return numericCast(D, value);
75 }
76
77 pub fn promoteInRangeTo(comptime D: type, value: anytype) D.Vector {
78 return promoteTo(D, value);
79 }
80
81 pub fn promoteLowerTo(comptime D: type, value: anytype) D.Vector {
82 return promoteSelected(D, value, .lower, false);
83 }
84
85 pub fn promoteUpperTo(comptime D: type, value: anytype) D.Vector {
86 return promoteSelected(D, value, .upper, false);
87 }
88
89 pub fn promoteEvenTo(comptime D: type, value: anytype) D.Vector {
90 return promoteSelected(D, value, .even, false);
91 }
92
93 pub fn promoteOddTo(comptime D: type, value: anytype) D.Vector {
94 return promoteSelected(D, value, .odd, false);
95 }
96
97 pub fn promoteInRangeLowerTo(comptime D: type, value: anytype) D.Vector {
98 return promoteSelected(D, value, .lower, true);
99 }
100
101 pub fn promoteInRangeUpperTo(comptime D: type, value: anytype) D.Vector {
102 return promoteSelected(D, value, .upper, true);
103 }
104
105 pub fn promoteInRangeEvenTo(comptime D: type, value: anytype) D.Vector {
106 return promoteSelected(D, value, .even, true);
107 }
108
109 pub fn promoteInRangeOddTo(comptime D: type, value: anytype) D.Vector {
110 return promoteSelected(D, value, .odd, true);
111 }
112
113 pub fn demoteTo(comptime D: type, value: anytype) D.Vector {
114 const V = @TypeOf(value);
115 const From = sourceLane(V);
116 validateLaneCount(D, V);
117 if (comptime isBFloatTag(D) and From == f32) return bfloat.demoteF32(D, value);
118 if (@bitSizeOf(D.Lane) >= @bitSizeOf(From)) {
119 @compileError("demoteTo requires a narrower destination lane");
120 }
121 return numericCast(D, value);
122 }
123
124 pub fn demoteInRangeTo(comptime D: type, value: anytype) D.Vector {
125 return demoteTo(D, value);
126 }
127
128 pub fn truncateTo(comptime D: type, value: anytype) D.Vector {
129 const V = @TypeOf(value);
130 const From = sourceLane(V);
131 validateLaneCount(D, V);
132 if (@typeInfo(D.Lane) != .int or @typeInfo(From) != .int or
133 @typeInfo(D.Lane).int.signedness != .unsigned or
134 @typeInfo(From).int.signedness != .unsigned or
135 @bitSizeOf(D.Lane) >= @bitSizeOf(From))
136 {
137 @compileError("truncateTo requires narrower unsigned integer destinations");
138 }
139 return @truncate(value);
140 }
141
142 pub fn u8FromU32(comptime D: type, value: anytype) D.Vector {
143 const V = @TypeOf(value);
144 validateLaneCount(D, V);
145 if (D.Lane != u8 or sourceLane(V) != u32) {
146 @compileError("u8FromU32 requires u32 sources and a u8 destination");
147 }
148 std.debug.assert(@reduce(.And, value < @as(V, @splat(256))));
149 return @truncate(value);
150 }
151
152 pub fn orderedDemote2To(
153 comptime D: type,
154 a: anytype,
155 b: @TypeOf(a),
156 ) D.Vector {
157 validatePair(D, @TypeOf(a));
158 const H = D.half();
159 return combinePair(D, demoteTo(H, a), demoteTo(H, b));
160 }
161
162 pub fn reorderDemote2To(
163 comptime D: type,
164 a: anytype,
165 b: @TypeOf(a),
166 ) D.Vector {
167 return orderedDemote2To(D, a, b);
168 }
169
170 pub fn orderedTruncate2To(
171 comptime D: type,
172 a: anytype,
173 b: @TypeOf(a),
174 ) D.Vector {
175 validatePair(D, @TypeOf(a));
176 const H = D.half();
177 return combinePair(D, truncateTo(H, a), truncateTo(H, b));
178 }
179
180 pub fn shiftRightAndDemoteTo(
181 comptime D: type,
182 comptime amount: usize,
183 value: anytype,
184 ) D.Vector {
185 const S = sourceDescriptor(@TypeOf(value));
186 return demoteTo(D, shift.shiftRight(S, amount, value));
187 }
188
189 pub fn roundingShiftRightAndDemoteTo(
190 comptime D: type,
191 comptime amount: usize,
192 value: anytype,
193 ) D.Vector {
194 const S = sourceDescriptor(@TypeOf(value));
195 return demoteTo(D, shift.roundingShiftRight(S, amount, value));
196 }
197
198 pub fn reorderShiftRightAndDemote2To(
199 comptime D: type,
200 comptime amount: usize,
201 a: anytype,
202 b: @TypeOf(a),
203 ) D.Vector {
204 const S = sourceDescriptor(@TypeOf(a));
205 return reorderDemote2To(
206 D,
207 shift.shiftRight(S, amount, a),
208 shift.shiftRight(S, amount, b),
209 );
210 }
211
212 pub fn reorderRoundingShiftRightAndDemote2To(
213 comptime D: type,
214 comptime amount: usize,
215 a: anytype,
216 b: @TypeOf(a),
217 ) D.Vector {
218 const S = sourceDescriptor(@TypeOf(a));
219 return reorderDemote2To(
220 D,
221 shift.roundingShiftRight(S, amount, a),
222 shift.roundingShiftRight(S, amount, b),
223 );
224 }
225
226 pub fn orderedShiftRightAndDemote2To(
227 comptime D: type,
228 comptime amount: usize,
229 a: anytype,
230 b: @TypeOf(a),
231 ) D.Vector {
232 const S = sourceDescriptor(@TypeOf(a));
233 return orderedDemote2To(
234 D,
235 shift.shiftRight(S, amount, a),
236 shift.shiftRight(S, amount, b),
237 );
238 }
239
240 pub fn orderedRoundingShiftRightAndDemote2To(
241 comptime D: type,
242 comptime amount: usize,
243 a: anytype,
244 b: @TypeOf(a),
245 ) D.Vector {
246 const S = sourceDescriptor(@TypeOf(a));
247 return orderedDemote2To(
248 D,
249 shift.roundingShiftRight(S, amount, a),
250 shift.roundingShiftRight(S, amount, b),
251 );
252 }
253
254 pub fn promoteMaskTo(
255 comptime DTo: type,
256 comptime DFrom: type,
257 mask: DFrom.Mask,
258 ) DTo.Mask {
259 if (DTo.lane_count != DFrom.lane_count or @sizeOf(DTo.Lane) <= @sizeOf(DFrom.Lane)) {
260 @compileError("promoteMaskTo requires equal lane counts and wider destination lanes");
261 }
262 return mask;
263 }
264
265 pub fn demoteMaskTo(
266 comptime DTo: type,
267 comptime DFrom: type,
268 mask: DFrom.Mask,
269 ) DTo.Mask {
270 if (DTo.lane_count != DFrom.lane_count or @sizeOf(DTo.Lane) >= @sizeOf(DFrom.Lane)) {
271 @compileError("demoteMaskTo requires equal lane counts and narrower destination lanes");
272 }
273 return mask;
274 }
275
276 pub fn orderedDemote2MasksTo(
277 comptime DTo: type,
278 comptime DFrom: type,
279 a: DFrom.Mask,
280 b: DFrom.Mask,
281 ) DTo.Mask {
282 if (DTo.lane_count != DFrom.lane_count * 2 or @sizeOf(DTo.Lane) >= @sizeOf(DFrom.Lane)) {
283 @compileError("orderedDemote2MasksTo requires two source masks and narrower destination lanes");
284 }
285 var result: DTo.Mask = undefined;
286 inline for (0..DFrom.lane_count) |index| {
287 result[index] = a[index];
288 result[DFrom.lane_count + index] = b[index];
289 }
290 return result;
291 }
292
293 const Selection = enum { lower, upper, even, odd };
294
295 fn promoteSelected(
296 comptime D: type,
297 value: anytype,
298 comptime selection: Selection,
299 comptime in_range: bool,
300 ) D.Vector {
301 const V = @TypeOf(value);
302 const From = sourceLane(V);
303 if (comptime sourceLaneCount(V) != D.lane_count * 2) {
304 @compileError("selected promotion requires twice the destination lane count");
305 }
306 if (comptime D.Lane == f32 and From == u16) {
307 return switch (selection) {
308 .lower => bfloat.promoteLowerF32(D, value),
309 .upper => bfloat.promoteUpperF32(D, value),
310 .even => bfloat.promoteEvenF32(D, value),
311 .odd => bfloat.promoteOddF32(D, value),
312 };
313 }
314 var selected: @Vector(D.lane_count, From) = undefined;
315 inline for (0..D.lane_count) |index| {
316 const source_index = switch (selection) {
317 .lower => index,
318 .upper => D.lane_count + index,
319 .even => index * 2,
320 .odd => index * 2 + 1,
321 };
322 selected[index] = value[source_index];
323 }
324 return if (in_range) promoteInRangeTo(D, selected) else promoteTo(D, selected);
325 }
326
327 fn numericCast(comptime D: type, value: anytype) D.Vector {
328 const From = sourceLane(@TypeOf(value));
329 const from_info = @typeInfo(From);
330 const to_info = @typeInfo(D.Lane);
331 if (from_info == .float and to_info == .int) return floatToInt(D, value);
332 if (from_info == .int and to_info == .int) return saturatingIntToInt(D, value);
333 if (from_info == .int and to_info == .float) return @floatFromInt(value);
334 if (from_info == .float and to_info == .float) return @floatCast(value);
335 @compileError("numeric conversion requires integer or floating-point lanes");
336 }
337
338 fn floatToInt(comptime D: type, value: anytype) D.Vector {
339 const V = @TypeOf(value);
340 const From = sourceLane(V);
341 const FromUnsigned = @Int(.unsigned, @bitSizeOf(From));
342 const FromBits = @Vector(D.lane_count, FromUnsigned);
343 const bits: FromBits = @bitCast(value);
344 const sign_bit: FromBits = @splat(@as(FromUnsigned, 1) << (@bitSizeOf(From) - 1));
345 const negative = bits & sign_bit != @as(FromBits, @splat(0));
346 const nan = value != value;
347 const destination_bits: i32 = @intCast(@bitSizeOf(D.Lane));
348 const sign_bits: i32 = if (@typeInfo(D.Lane).int.signedness == .signed) 1 else 0;
349 const upper = std.math.ldexp(@as(From, 1), destination_bits - sign_bits);
350 const high = (value >= @as(V, @splat(upper))) | (nan & ~negative);
351 const low = if (@typeInfo(D.Lane).int.signedness == .signed)
352 (value <= @as(V, @splat(-upper))) | (nan & negative)
353 else
354 (value <= @as(V, @splat(0))) | (nan & negative);
355 const safe = @select(From, high | low, @as(V, @splat(0)), value);
356 const converted: D.Vector = @intFromFloat(safe);
357 const maximum: D.Vector = @splat(std.math.maxInt(D.Lane));
358 const minimum: D.Vector = if (@typeInfo(D.Lane).int.signedness == .signed)
359 @splat(std.math.minInt(D.Lane))
360 else
361 @splat(0);
362 return @select(D.Lane, low, minimum, @select(D.Lane, high, maximum, converted));
363 }
364
365 fn saturatingIntToInt(comptime D: type, value: anytype) D.Vector {
366 const V = @TypeOf(value);
367 const From = sourceLane(V);
368 const from_signed = @typeInfo(From).int.signedness == .signed;
369 const to_signed = @typeInfo(D.Lane).int.signedness == .signed;
370 if (@bitSizeOf(D.Lane) >= @bitSizeOf(From)) return @intCast(value);
371 var clamped = value;
372 if (from_signed) {
373 if (to_signed) {
374 const minimum: V = @splat(@as(From, @intCast(std.math.minInt(D.Lane))));
375 clamped = @select(From, clamped < minimum, minimum, clamped);
376 } else {
377 const zero: V = @splat(0);
378 clamped = @select(From, clamped < zero, zero, clamped);
379 }
380 }
381 const maximum: V = @splat(@as(From, @intCast(std.math.maxInt(D.Lane))));
382 clamped = @select(From, clamped > maximum, maximum, clamped);
383 return @intCast(clamped);
384 }
385
386 fn roundEvenToInt(comptime D: type, value: anytype) D.Vector {
387 const V = @TypeOf(value);
388 const From = sourceLane(V);
389 const rounded_float = @round(value);
390 const rounded = floatToInt(D, rounded_float);
391 const fraction = @abs(value - @trunc(value));
392 const tie = fraction == @as(V, @splat(@as(From, 0.5)));
393 const odd = rounded & @as(D.Vector, @splat(1)) != @as(D.Vector, @splat(0));
394 const FromUnsigned = @Int(.unsigned, @bitSizeOf(From));
395 const FromBits = @Vector(D.lane_count, FromUnsigned);
396 const bits: FromBits = @bitCast(value);
397 const sign_bit: FromBits = @splat(@as(FromUnsigned, 1) << (@bitSizeOf(From) - 1));
398 const negative = bits & sign_bit != @as(FromBits, @splat(0));
399 const adjustment: D.Vector = @select(
400 D.Lane,
401 negative,
402 @as(D.Vector, @splat(1)),
403 @as(D.Vector, @splat(-1)),
404 );
405 const destination_bits: i32 = @intCast(@bitSizeOf(D.Lane));
406 const upper = std.math.ldexp(@as(From, 1), destination_bits - 1);
407 const in_range = (rounded_float >= @as(V, @splat(-upper))) &
408 (rounded_float < @as(V, @splat(upper)));
409 return @select(D.Lane, tie & odd & in_range, rounded +% adjustment, rounded);
410 }
411
412 fn combinePair(comptime D: type, low: D.half().Vector, high: D.half().Vector) D.Vector {
413 var result: D.Vector = undefined;
414 inline for (0..D.lane_count / 2) |index| {
415 result[index] = low[index];
416 result[D.lane_count / 2 + index] = high[index];
417 }
418 return result;
419 }
420
421 fn validatePair(comptime D: type, comptime V: type) void {
422 if (D.lane_count < 2 or comptime sourceLaneCount(V) * 2 != D.lane_count) {
423 @compileError("two-vector conversion requires each source to fill one destination half");
424 }
425 }
426
427 fn validateSameWidthFloatToSigned(comptime D: type, comptime V: type) void {
428 const From = sourceLane(V);
429 validateLaneCount(D, V);
430 if (@typeInfo(From) != .float or @typeInfo(D.Lane) != .int or
431 @typeInfo(D.Lane).int.signedness != .signed or
432 @bitSizeOf(D.Lane) != @bitSizeOf(From))
433 {
434 @compileError("integer rounding requires same-width float and signed integer lanes");
435 }
436 }
437
438 fn validateLaneCount(comptime D: type, comptime V: type) void {
439 if (comptime sourceLaneCount(V) != D.lane_count) {
440 @compileError("numeric conversion requires equal source and destination lane counts");
441 }
442 }
443
444 fn sourceDescriptor(comptime V: type) type {
445 return tag.FixedTag(sourceLane(V), sourceLaneCount(V));
446 }
447
448 fn sourceLane(comptime V: type) type {
449 return switch (@typeInfo(V)) {
450 .vector => |info| info.child,
451 else => @compileError("numeric conversion requires a vector source"),
452 };
453 }
454
455 fn sourceLaneCount(comptime V: type) usize {
456 return switch (@typeInfo(V)) {
457 .vector => |info| info.len,
458 else => @compileError("numeric conversion requires a vector source"),
459 };
460 }
461
462 fn isBFloatTag(comptime D: type) bool {
463 if (!@hasDecl(D, "is_bfloat16")) return false;
464 return D.is_bfloat16;
465 }
466
467 test "Highway convert saturates floating point by destination range and NaN sign" {
468 const simd = @import("root.zig");
469 const F = simd.FixedTag(f32, 8);
470 const I = simd.FixedTag(i32, 8);
471 const U = simd.FixedTag(u32, 8);
472 const B = simd.FixedTag(u32, 8);
473 const value: F.Vector = @bitCast(@as(B.Vector, .{
474 0xc060_0000,
475 0xbf00_0000,
476 0,
477 0x409c_cccd,
478 0x4f00_0000,
479 0xcf00_0000,
480 0x7fc0_1234,
481 0xffc0_1234,
482 }));
483 try std.testing.expect(@reduce(.And, convertTo(I, value) == @as(I.Vector, .{
484 -3,
485 0,
486 0,
487 4,
488 std.math.maxInt(i32),
489 std.math.minInt(i32),
490 std.math.maxInt(i32),
491 std.math.minInt(i32),
492 })));
493 try std.testing.expect(@reduce(.And, convertTo(U, value) == @as(U.Vector, .{
494 0,
495 0,
496 0,
497 4,
498 2_147_483_648,
499 0,
500 std.math.maxInt(u32),
501 0,
502 })));
503 }
504
505 test "Highway integer conversions widen and demote with saturation" {
506 const simd = @import("root.zig");
507 const S = simd.FixedTag(i32, 8);
508 const U = simd.FixedTag(u32, 8);
509 const I16 = simd.FixedTag(i16, 8);
510 const U16 = simd.FixedTag(u16, 8);
511 const I64 = simd.FixedTag(i64, 8);
512 const value: S.Vector = .{
513 std.math.minInt(i32), -65_536, -1, 0, 1, 65_535, 65_536, std.math.maxInt(i32),
514 };
515 try std.testing.expect(@reduce(.And, demoteTo(I16, value) == @as(I16.Vector, .{
516 -32_768, -32_768, -1, 0, 1, 32_767, 32_767, 32_767,
517 })));
518 try std.testing.expect(@reduce(.And, demoteTo(U16, value) == @as(U16.Vector, .{
519 0, 0, 0, 0, 1, 65_535, 65_535, 65_535,
520 })));
521 const unsigned: U.Vector = .{ 0, 1, 32_767, 32_768, 65_535, 65_536, 0x8000_0000, 0xffff_ffff };
522 try std.testing.expect(@reduce(.And, demoteTo(I16, unsigned) == @as(I16.Vector, .{
523 0, 1, 32_767, 32_767, 32_767, 32_767, 32_767, 32_767,
524 })));
525 try std.testing.expect(@reduce(.And, promoteTo(I64, unsigned) ==
526 @as(I64.Vector, .{ 0, 1, 32_767, 32_768, 65_535, 65_536, 0x8000_0000, 0xffff_ffff })));
527 }
528
529 test "Highway promotion selects exact lower upper even and odd lanes" {
530 const simd = @import("root.zig");
531 const D = simd.FixedTag(u16, 4);
532 const S = simd.FixedTag(u8, 8);
533 const value: S.Vector = .{ 0, 1, 2, 3, 4, 5, 6, 7 };
534 try std.testing.expect(@reduce(.And, promoteLowerTo(D, value) == @as(D.Vector, .{ 0, 1, 2, 3 })));
535 try std.testing.expect(@reduce(.And, promoteUpperTo(D, value) == @as(D.Vector, .{ 4, 5, 6, 7 })));
536 try std.testing.expect(@reduce(.And, promoteEvenTo(D, value) == @as(D.Vector, .{ 0, 2, 4, 6 })));
537 try std.testing.expect(@reduce(.And, promoteOddTo(D, value) == @as(D.Vector, .{ 1, 3, 5, 7 })));
538 }
539
540 test "Highway float widening narrowing and integer rounding preserve semantics" {
541 const simd = @import("root.zig");
542 const F16 = simd.FixedTag(f16, 8);
543 const F32 = simd.FixedTag(f32, 8);
544 const F64 = simd.FixedTag(f64, 8);
545 const I32 = simd.FixedTag(i32, 8);
546 const I16 = simd.FixedTag(i16, 8);
547 const halves: F16.Vector = .{ -4, -1.5, -0.5, 0, 0.5, 1.5, 2.5, 65_504 };
548 try std.testing.expect(@reduce(.And, demoteTo(F16, promoteTo(F32, halves)) == halves));
549 const doubles: F64.Vector = .{ -1.0e300, -3.5, -0.0, 0, 3.5, 65_504, 65_520, 1.0e300 };
550 const narrowed = demoteTo(F16, doubles);
551 try std.testing.expect(std.math.isInf(narrowed[0]) and std.math.signbit(narrowed[0]));
552 try std.testing.expect(std.math.isInf(narrowed[7]) and !std.math.signbit(narrowed[7]));
553 try std.testing.expect(@reduce(.And, nearestInt(I32, @as(F32.Vector, .{
554 -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5,
555 })) == @as(I32.Vector, .{ -4, -2, -2, 0, 0, 2, 2, 4 })));
556 try std.testing.expect(@reduce(.And, demoteToNearestInt(I16, doubles) == @as(I16.Vector, .{
557 std.math.minInt(i16), -4, 0, 0, 4, std.math.maxInt(i16), std.math.maxInt(i16), std.math.maxInt(i16),
558 })));
559 const boundaries: F64.Vector = .{
560 -32_769.5, -32_768.5, -32_767.5, -32_766.5,
561 32_766.5, 32_767.5, 32_768.5, 32_769.5,
562 };
563 try std.testing.expect(@reduce(.And, demoteToNearestInt(I16, boundaries) == @as(I16.Vector, .{
564 -32_768, -32_768, -32_768, -32_766, 32_766, 32_767, 32_767, 32_767,
565 })));
566 }
567
568 test "Highway truncation ordered packing and shifted demotion retain lane order" {
569 const simd = @import("root.zig");
570 const D = simd.FixedTag(u8, 8);
571 const H = simd.FixedTag(u16, 4);
572 const a: H.Vector = .{ 0x0102, 0x03ff, 0x0400, 0xffff };
573 const b: H.Vector = .{ 0x1005, 0x2006, 0x3007, 0x4008 };
574 try std.testing.expect(@reduce(.And, orderedTruncate2To(D, a, b) ==
575 @as(D.Vector, .{ 2, 255, 0, 255, 5, 6, 7, 8 })));
576 try std.testing.expect(@reduce(.And, orderedDemote2To(D, a, b) ==
577 @as(D.Vector, .{ 255, 255, 255, 255, 255, 255, 255, 255 })));
578 try std.testing.expect(@reduce(.And, orderedShiftRightAndDemote2To(D, 8, a, b) ==
579 @as(D.Vector, .{ 1, 3, 4, 255, 16, 32, 48, 64 })));
580 try std.testing.expect(@reduce(.And, orderedRoundingShiftRightAndDemote2To(D, 8, a, b) ==
581 @as(D.Vector, .{ 1, 4, 4, 255, 16, 32, 48, 64 })));
582 }
583
584 test "Highway conversion entry points instantiate supported lane families" {
585 const simd = @import("root.zig");
586 const integer_types = .{ i8, u8, i16, u16, i32, u32, i64, u64 };
587 inline for (integer_types) |From| {
588 inline for (integer_types) |To| {
589 const DFrom = simd.FixedTag(From, 4);
590 const DTo = simd.FixedTag(To, 4);
591 const value: DFrom.Vector = @splat(0);
592 if (comptime @bitSizeOf(To) < @bitSizeOf(From)) {
593 _ = demoteTo(DTo, value);
594 }
595 if (comptime @bitSizeOf(To) > @bitSizeOf(From) and
596 (@typeInfo(From).int.signedness == .unsigned or
597 @typeInfo(To).int.signedness == .signed))
598 {
599 _ = promoteTo(DTo, value);
600 }
601 }
602 }
603 inline for (.{ .{ f16, f32 }, .{ f16, f64 }, .{ f32, f64 } }) |types| {
604 const Narrow = types[0];
605 const Wide = types[1];
606 const DN = simd.FixedTag(Narrow, 4);
607 const DW = simd.FixedTag(Wide, 4);
608 const value: DN.Vector = @splat(1.5);
609 try std.testing.expect(@reduce(.And, demoteTo(DN, promoteTo(DW, value)) == value));
610 }
611 inline for (.{ f16, f32, f64 }) |Float| {
612 const Signed = @Int(.signed, @bitSizeOf(Float));
613 const Unsigned = @Int(.unsigned, @bitSizeOf(Float));
614 const DF = simd.FixedTag(Float, 4);
615 const DI = simd.FixedTag(Signed, 4);
616 const DU = simd.FixedTag(Unsigned, 4);
617 const floats: DF.Vector = @splat(1.5);
618 const signed: DI.Vector = @splat(1);
619 const unsigned: DU.Vector = @splat(1);
620 _ = convertTo(DI, floats);
621 _ = convertTo(DU, floats);
622 _ = convertTo(DF, signed);
623 _ = convertTo(DF, unsigned);
624 _ = convertInRangeTo(DI, floats);
625 _ = nearestInt(DI, floats);
626 _ = ceilInt(DI, floats);
627 _ = floorInt(DI, floats);
628 _ = maskedConvertTo(DI, @as(DI.Mask, @splat(true)), floats);
629 _ = maskedConvertTo(DF, @as(DF.Mask, @splat(true)), signed);
630 }
631 const unsigned_types = .{ u8, u16, u32, u64 };
632 inline for (unsigned_types) |From| {
633 inline for (unsigned_types) |To| {
634 if (comptime @bitSizeOf(To) < @bitSizeOf(From)) {
635 const DFrom = simd.FixedTag(From, 4);
636 const DTo = simd.FixedTag(To, 4);
637 _ = truncateTo(DTo, @as(DFrom.Vector, @splat(0)));
638 }
639 }
640 }
641 const F32 = simd.FixedTag(f32, 4);
642 const I32 = simd.FixedTag(i32, 4);
643 const U32 = simd.FixedTag(u32, 4);
644 const F64 = simd.FixedTag(f64, 4);
645 const I64 = simd.FixedTag(i64, 4);
646 const U64 = simd.FixedTag(u64, 4);
647 const floats: F32.Vector = .{ -1, 0, 1, 2 };
648 _ = convertInRangeTo(I32, floats);
649 _ = maskedConvertTo(I32, @as(I32.Mask, .{ true, false, true, false }), floats);
650 _ = ceilInt(I32, floats);
651 _ = floorInt(I32, floats);
652 _ = promoteInRangeTo(I64, floats);
653 _ = promoteInRangeTo(U64, floats);
654 _ = promoteTo(F64, @as(I32.Vector, @splat(1)));
655 _ = promoteTo(F64, @as(U32.Vector, @splat(1)));
656 _ = demoteTo(F32, @as(I64.Vector, @splat(1)));
657 _ = demoteTo(F32, @as(U64.Vector, @splat(1)));
658 _ = demoteInRangeTo(I32, @as(F64.Vector, @splat(1)));
659 _ = demoteInRangeTo(U32, @as(F64.Vector, @splat(1)));
660 _ = demoteToNearestInt(I32, @as(F64.Vector, @splat(1)));
661 _ = u8FromU32(simd.FixedTag(u8, 4), @as(U32.Vector, .{ 0, 1, 254, 255 }));
662 _ = reorderDemote2To(simd.FixedTag(i16, 8), @as(I32.Vector, @splat(0)), @as(I32.Vector, @splat(1)));
663 _ = shiftRightAndDemoteTo(simd.FixedTag(i16, 4), 1, @as(I32.Vector, @splat(1)));
664 _ = roundingShiftRightAndDemoteTo(simd.FixedTag(i16, 4), 1, @as(I32.Vector, @splat(1)));
665 _ = reorderShiftRightAndDemote2To(simd.FixedTag(i16, 8), 1, @as(I32.Vector, @splat(1)), @as(I32.Vector, @splat(2)));
666 _ = reorderRoundingShiftRightAndDemote2To(simd.FixedTag(i16, 8), 1, @as(I32.Vector, @splat(1)), @as(I32.Vector, @splat(2)));
667 _ = promoteInRangeLowerTo(I64, @as(simd.FixedTag(f32, 8).Vector, @splat(1)));
668 _ = promoteInRangeUpperTo(U64, @as(simd.FixedTag(f32, 8).Vector, @splat(1)));
669 _ = promoteInRangeEvenTo(I64, @as(simd.FixedTag(f32, 8).Vector, @splat(1)));
670 _ = promoteInRangeOddTo(U64, @as(simd.FixedTag(f32, 8).Vector, @splat(1)));
671 }
672
673 test "Highway mask promotion demotion and ordered packing preserve truth values" {
674 const simd = @import("root.zig");
675 const B = simd.FixedTag(u8, 4);
676 const W = simd.FixedTag(i32, 4);
677 const N = simd.FixedTag(u16, 8);
678 const a: B.Mask = .{ true, false, false, true };
679 const b: B.Mask = .{ false, true, true, false };
680 try std.testing.expect(@reduce(.And, promoteMaskTo(W, B, a) == a));
681 try std.testing.expect(@reduce(.And, demoteMaskTo(B, W, a) == a));
682 try std.testing.expect(@reduce(.And, orderedDemote2MasksTo(N, W, a, b) ==
683 @as(N.Mask, .{ true, false, false, true, false, true, true, false })));
684 }