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 }