lib/simd/src/block.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 pub fn broadcast(comptime D: type, comptime lane: usize, value: D.Vector) D.Vector {
  4     const lanes_per_block = comptime blockLanes(D);
  5     if (comptime lane >= lanes_per_block) @compileError("broadcast lane is outside a 128-bit block");
  6     const lanes: [D.lane_count]D.Lane = @bitCast(value);
  7     var result: [D.lane_count]D.Lane = undefined;
  8     inline for (0..D.lane_count) |index| {
  9         result[index] = lanes[(index / lanes_per_block) * lanes_per_block + lane];
 10     }
 11     return @bitCast(result);
 12 }
 13 
 14 pub fn interleaveLower(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 15     return interleaveHalf(D, false, a, b);
 16 }
 17 
 18 pub fn interleaveUpper(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 19     if (comptime blockLanes(D) < 2) @compileError("interleaveUpper requires at least two lanes per block");
 20     return interleaveHalf(D, true, a, b);
 21 }
 22 
 23 pub fn interleaveEven(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 24     return interleaveParity(D, 0, a, b);
 25 }
 26 
 27 pub fn interleaveOdd(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
 28     if (comptime blockLanes(D) < 2) @compileError("interleaveOdd requires at least two lanes per block");
 29     return interleaveParity(D, 1, a, b);
 30 }
 31 
 32 pub fn zipLower(comptime D: type, a: D.Vector, b: D.Vector) D.repartition(wideInteger(D.Lane)).Vector {
 33     return @bitCast(interleaveLower(D, a, b));
 34 }
 35 
 36 pub fn zipUpper(comptime D: type, a: D.Vector, b: D.Vector) D.repartition(wideInteger(D.Lane)).Vector {
 37     return @bitCast(interleaveUpper(D, a, b));
 38 }
 39 
 40 pub fn shiftLeftBytes(comptime D: type, comptime amount: usize, value: D.Vector) D.Vector {
 41     return shiftBytes(D, amount, true, value);
 42 }
 43 
 44 pub fn shiftRightBytes(comptime D: type, comptime amount: usize, value: D.Vector) D.Vector {
 45     return shiftBytes(D, amount, false, value);
 46 }
 47 
 48 pub fn shiftLeftLanes(comptime D: type, comptime amount: usize, value: D.Vector) D.Vector {
 49     return shiftLanes(D, amount, true, value);
 50 }
 51 
 52 pub fn shiftRightLanes(comptime D: type, comptime amount: usize, value: D.Vector) D.Vector {
 53     return shiftLanes(D, amount, false, value);
 54 }
 55 
 56 pub fn combineShiftRightBytes(
 57     comptime D: type,
 58     comptime amount: usize,
 59     high: D.Vector,
 60     low: D.Vector,
 61 ) D.Vector {
 62     const bytes_per_block = @min(D.byte_count, 16);
 63     if (comptime amount >= bytes_per_block) @compileError("combined byte shift must remain inside a block");
 64     const high_bytes: [D.byte_count]u8 = @bitCast(high);
 65     const low_bytes: [D.byte_count]u8 = @bitCast(low);
 66     var result: [D.byte_count]u8 = undefined;
 67     inline for (0..D.byte_count) |index| {
 68         const base = index / bytes_per_block * bytes_per_block;
 69         const source = index % bytes_per_block + amount;
 70         result[index] = if (source < bytes_per_block)
 71             low_bytes[base + source]
 72         else
 73             high_bytes[base + source - bytes_per_block];
 74     }
 75     return @bitCast(result);
 76 }
 77 
 78 pub fn combineShiftRightLanes(
 79     comptime D: type,
 80     comptime amount: usize,
 81     high: D.Vector,
 82     low: D.Vector,
 83 ) D.Vector {
 84     const lanes_per_block = comptime blockLanes(D);
 85     if (comptime amount >= lanes_per_block) @compileError("combined lane shift must remain inside a block");
 86     const high_lanes: [D.lane_count]D.Lane = @bitCast(high);
 87     const low_lanes: [D.lane_count]D.Lane = @bitCast(low);
 88     var result: [D.lane_count]D.Lane = undefined;
 89     inline for (0..D.lane_count) |index| {
 90         const base = index / lanes_per_block * lanes_per_block;
 91         const source = index % lanes_per_block + amount;
 92         result[index] = if (source < lanes_per_block)
 93             low_lanes[base + source]
 94         else
 95             high_lanes[base + source - lanes_per_block];
 96     }
 97     return @bitCast(result);
 98 }
 99 
100 pub fn per4LaneBlockShuffle(
101     comptime D: type,
102     comptime index3: usize,
103     comptime index2: usize,
104     comptime index1: usize,
105     comptime index0: usize,
106     value: D.Vector,
107 ) D.Vector {
108     inline for (.{ index0, index1, index2, index3 }) |index| {
109         if (comptime index >= 4) @compileError("per-four-lane shuffle index must be below four");
110     }
111     const group = @min(D.lane_count, 4);
112     const indices = [4]usize{ index0, index1, index2, index3 };
113     const lanes: [D.lane_count]D.Lane = @bitCast(value);
114     var result: [D.lane_count]D.Lane = undefined;
115     inline for (0..D.lane_count) |lane| {
116         const source = indices[lane % group];
117         result[lane] = if (source < group) lanes[lane / group * group + source] else lanes[lane];
118     }
119     return @bitCast(result);
120 }
121 
122 pub fn shuffle1032(comptime D: type, value: D.Vector) D.Vector {
123     validate32BitLanes(D);
124     return per4LaneBlockShuffle(D, 1, 0, 3, 2, value);
125 }
126 
127 pub fn shuffle0321(comptime D: type, value: D.Vector) D.Vector {
128     validate32BitLanes(D);
129     return per4LaneBlockShuffle(D, 0, 3, 2, 1, value);
130 }
131 
132 pub fn shuffle2103(comptime D: type, value: D.Vector) D.Vector {
133     validate32BitLanes(D);
134     return per4LaneBlockShuffle(D, 2, 1, 0, 3, value);
135 }
136 
137 pub fn shuffle2301(comptime D: type, value: D.Vector) D.Vector {
138     validate32BitLanes(D);
139     return per4LaneBlockShuffle(D, 2, 3, 0, 1, value);
140 }
141 
142 pub fn shuffle01(comptime D: type, value: D.Vector) D.Vector {
143     if (comptime @bitSizeOf(D.Lane) != 64) @compileError("shuffle01 requires 64-bit lanes");
144     const lanes: [D.lane_count]D.Lane = value;
145     var result: [D.lane_count]D.Lane = undefined;
146     inline for (0..D.lane_count) |index| result[index] = lanes[index ^ 1];
147     return result;
148 }
149 
150 pub fn shuffle0123(comptime D: type, value: D.Vector) D.Vector {
151     validate32BitLanes(D);
152     return per4LaneBlockShuffle(D, 0, 1, 2, 3, value);
153 }
154 
155 pub fn blocks(comptime D: type) usize {
156     return @max(1, D.byte_count / 16);
157 }
158 
159 pub fn extractBlock(
160     comptime D: type,
161     comptime block_index: usize,
162     value: D.Vector,
163 ) @Vector(blockLanes(D), D.Lane) {
164     const block_count = comptime blocks(D);
165     if (comptime block_index >= block_count) @compileError("block index is outside the vector");
166     const lanes_per_block = comptime blockLanes(D);
167     const lanes: [D.lane_count]D.Lane = @bitCast(value);
168     var result: [lanes_per_block]D.Lane = undefined;
169     inline for (0..lanes_per_block) |lane| {
170         result[lane] = lanes[block_index * lanes_per_block + lane];
171     }
172     return @bitCast(result);
173 }
174 
175 pub fn insertBlock(
176     comptime D: type,
177     comptime block_index: usize,
178     value: D.Vector,
179     inserted: @Vector(blockLanes(D), D.Lane),
180 ) D.Vector {
181     const block_count = comptime blocks(D);
182     if (comptime block_index >= block_count) @compileError("block index is outside the vector");
183     const lanes_per_block = comptime blockLanes(D);
184     var result: [D.lane_count]D.Lane = @bitCast(value);
185     const inserted_lanes: [lanes_per_block]D.Lane = @bitCast(inserted);
186     inline for (0..lanes_per_block) |lane| {
187         result[block_index * lanes_per_block + lane] = inserted_lanes[lane];
188     }
189     return @bitCast(result);
190 }
191 
192 pub fn broadcastBlock(comptime D: type, comptime block_index: usize, value: D.Vector) D.Vector {
193     const block_count = comptime blocks(D);
194     if (comptime block_index >= block_count) @compileError("block index is outside the vector");
195     if (block_count == 1) return value;
196     const lanes_per_block = comptime blockLanes(D);
197     const lanes: [D.lane_count]D.Lane = @bitCast(value);
198     var result: [D.lane_count]D.Lane = undefined;
199     inline for (0..D.lane_count) |lane| {
200         result[lane] = lanes[block_index * lanes_per_block + lane % lanes_per_block];
201     }
202     return @bitCast(result);
203 }
204 
205 pub fn oddEvenBlocks(comptime D: type, odd: D.Vector, even: D.Vector) D.Vector {
206     const block_count = comptime blocks(D);
207     if (block_count == 1) return even;
208     const lanes_per_block = comptime blockLanes(D);
209     const odd_lanes: [D.lane_count]D.Lane = @bitCast(odd);
210     const even_lanes: [D.lane_count]D.Lane = @bitCast(even);
211     var result: [D.lane_count]D.Lane = undefined;
212     inline for (0..D.lane_count) |lane| {
213         result[lane] = if ((lane / lanes_per_block) & 1 == 0) even_lanes[lane] else odd_lanes[lane];
214     }
215     return @bitCast(result);
216 }
217 
218 pub fn swapAdjacentBlocks(comptime D: type, value: D.Vector) D.Vector {
219     validateMultipleBlocks(D);
220     const lanes_per_block = comptime blockLanes(D);
221     const lanes: [D.lane_count]D.Lane = @bitCast(value);
222     var result: [D.lane_count]D.Lane = undefined;
223     inline for (0..D.lane_count) |lane| {
224         const block_index = lane / lanes_per_block;
225         result[lane] = lanes[(block_index ^ 1) * lanes_per_block + lane % lanes_per_block];
226     }
227     return @bitCast(result);
228 }
229 
230 pub fn interleaveEvenBlocks(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
231     return interleaveBlockParity(D, 0, a, b);
232 }
233 
234 pub fn interleaveOddBlocks(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
235     return interleaveBlockParity(D, 1, a, b);
236 }
237 
238 pub fn interleaveLowerBlocks(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
239     return interleaveBlockHalf(D, false, a, b);
240 }
241 
242 pub fn interleaveUpperBlocks(comptime D: type, a: D.Vector, b: D.Vector) D.Vector {
243     return interleaveBlockHalf(D, true, a, b);
244 }
245 
246 fn interleaveHalf(comptime D: type, comptime upper: bool, a: D.Vector, b: D.Vector) D.Vector {
247     const lanes_per_block = comptime blockLanes(D);
248     if (lanes_per_block == 1) return a;
249     const a_lanes: [D.lane_count]D.Lane = @bitCast(a);
250     const b_lanes: [D.lane_count]D.Lane = @bitCast(b);
251     var result: [D.lane_count]D.Lane = undefined;
252     inline for (0..D.lane_count) |index| {
253         const base = index / lanes_per_block * lanes_per_block;
254         const within = index % lanes_per_block;
255         const source = base + within / 2 + if (upper) lanes_per_block / 2 else 0;
256         result[index] = if (within & 1 == 0) a_lanes[source] else b_lanes[source];
257     }
258     return @bitCast(result);
259 }
260 
261 fn interleaveBlockParity(
262     comptime D: type,
263     comptime parity: usize,
264     a: D.Vector,
265     b: D.Vector,
266 ) D.Vector {
267     validateMultipleBlocks(D);
268     const lanes_per_block = comptime blockLanes(D);
269     const a_lanes: [D.lane_count]D.Lane = @bitCast(a);
270     const b_lanes: [D.lane_count]D.Lane = @bitCast(b);
271     var result: [D.lane_count]D.Lane = undefined;
272     inline for (0..D.lane_count) |lane| {
273         const output_block = lane / lanes_per_block;
274         const source_block = (output_block / 2) * 2 + parity;
275         const source_lane = source_block * lanes_per_block + lane % lanes_per_block;
276         result[lane] = if (output_block & 1 == 0) a_lanes[source_lane] else b_lanes[source_lane];
277     }
278     return @bitCast(result);
279 }
280 
281 fn interleaveBlockHalf(
282     comptime D: type,
283     comptime upper: bool,
284     a: D.Vector,
285     b: D.Vector,
286 ) D.Vector {
287     validateMultipleBlocks(D);
288     const block_count = comptime blocks(D);
289     const lanes_per_block = comptime blockLanes(D);
290     const a_lanes: [D.lane_count]D.Lane = @bitCast(a);
291     const b_lanes: [D.lane_count]D.Lane = @bitCast(b);
292     var result: [D.lane_count]D.Lane = undefined;
293     inline for (0..D.lane_count) |lane| {
294         const output_block = lane / lanes_per_block;
295         const source_block = output_block / 2 + if (upper) block_count / 2 else 0;
296         const source_lane = source_block * lanes_per_block + lane % lanes_per_block;
297         result[lane] = if (output_block & 1 == 0) a_lanes[source_lane] else b_lanes[source_lane];
298     }
299     return @bitCast(result);
300 }
301 
302 fn interleaveParity(
303     comptime D: type,
304     comptime parity: usize,
305     a: D.Vector,
306     b: D.Vector,
307 ) D.Vector {
308     const lanes_per_block = comptime blockLanes(D);
309     const a_lanes: [D.lane_count]D.Lane = @bitCast(a);
310     const b_lanes: [D.lane_count]D.Lane = @bitCast(b);
311     var result: [D.lane_count]D.Lane = undefined;
312     inline for (0..D.lane_count) |index| {
313         const base = index / lanes_per_block * lanes_per_block;
314         const within = index % lanes_per_block;
315         const source = base + (within / 2) * 2 + parity;
316         result[index] = if (within & 1 == 0) a_lanes[source] else b_lanes[source];
317     }
318     return @bitCast(result);
319 }
320 
321 fn shiftBytes(comptime D: type, comptime amount: usize, comptime left: bool, value: D.Vector) D.Vector {
322     const bytes_per_block = @min(D.byte_count, 16);
323     if (comptime amount > bytes_per_block) @compileError("byte shift exceeds a block");
324     const bytes: [D.byte_count]u8 = @bitCast(value);
325     var result: [D.byte_count]u8 = @splat(0);
326     inline for (0..D.byte_count) |index| {
327         const within = index % bytes_per_block;
328         if (left) {
329             if (within >= amount) result[index] = bytes[index - amount];
330         } else if (within + amount < bytes_per_block) {
331             result[index] = bytes[index + amount];
332         }
333     }
334     return @bitCast(result);
335 }
336 
337 fn shiftLanes(comptime D: type, comptime amount: usize, comptime left: bool, value: D.Vector) D.Vector {
338     const lanes_per_block = comptime blockLanes(D);
339     if (comptime amount > lanes_per_block) @compileError("lane shift exceeds a block");
340     const lanes: [D.lane_count]D.Lane = @bitCast(value);
341     var result = @as([D.lane_count]D.Lane, @splat(0));
342     inline for (0..D.lane_count) |index| {
343         const within = index % lanes_per_block;
344         if (left) {
345             if (within >= amount) result[index] = lanes[index - amount];
346         } else if (within + amount < lanes_per_block) {
347             result[index] = lanes[index + amount];
348         }
349     }
350     return @bitCast(result);
351 }
352 
353 fn blockLanes(comptime D: type) comptime_int {
354     return @min(D.lane_count, 16 / @sizeOf(D.Lane));
355 }
356 
357 fn validateMultipleBlocks(comptime D: type) void {
358     if (comptime blocks(D) < 2) @compileError("operation requires at least two 128-bit blocks");
359 }
360 
361 fn validate32BitLanes(comptime D: type) void {
362     if (comptime @bitSizeOf(D.Lane) != 32) @compileError("shuffle requires 32-bit lanes");
363 }
364 
365 fn wideInteger(comptime T: type) type {
366     return switch (T) {
367         i8 => i16,
368         u8 => u16,
369         i16 => i32,
370         u16 => u32,
371         i32 => i64,
372         u32 => u64,
373         else => @compileError("zip requires 8-, 16-, or 32-bit integer lanes"),
374     };
375 }
376 
377 test "Highway block broadcast and interleave restart at 128-bit boundaries" {
378     const simd = @import("root.zig");
379     const D = simd.FixedTag(u32, 8);
380     const a: D.Vector = .{ 0, 2, 4, 6, 8, 10, 12, 14 };
381     const b: D.Vector = .{ 1, 3, 5, 7, 9, 11, 13, 15 };
382     try std.testing.expect(@reduce(.And, broadcast(D, 2, a) == @as(D.Vector, .{ 4, 4, 4, 4, 12, 12, 12, 12 })));
383     try std.testing.expect(@reduce(.And, interleaveLower(D, a, b) == @as(D.Vector, .{ 0, 1, 2, 3, 8, 9, 10, 11 })));
384     try std.testing.expect(@reduce(.And, interleaveUpper(D, a, b) == @as(D.Vector, .{ 4, 5, 6, 7, 12, 13, 14, 15 })));
385     try std.testing.expect(@reduce(.And, interleaveEven(D, a, b) == @as(D.Vector, .{ 0, 1, 4, 5, 8, 9, 12, 13 })));
386     try std.testing.expect(@reduce(.And, interleaveOdd(D, a, b) == @as(D.Vector, .{ 2, 3, 6, 7, 10, 11, 14, 15 })));
387 }
388 
389 test "Highway zip retains interleaved bits in wide lanes" {
390     const simd = @import("root.zig");
391     const D = simd.FixedTag(u16, 8);
392     const a: D.Vector = .{ 0, 2, 4, 6, 8, 10, 12, 14 };
393     const b: D.Vector = .{ 1, 3, 5, 7, 9, 11, 13, 15 };
394     const W = D.repartition(u32);
395     try std.testing.expect(@reduce(.And, zipLower(D, a, b) == @as(W.Vector, .{
396         0x0001_0000, 0x0003_0002, 0x0005_0004, 0x0007_0006,
397     })));
398     try std.testing.expect(@reduce(.And, zipUpper(D, a, b) == @as(W.Vector, .{
399         0x0009_0008, 0x000b_000a, 0x000d_000c, 0x000f_000e,
400     })));
401 }
402 
403 test "Highway block shifts zero fill and combined shifts concatenate low then high" {
404     const simd = @import("root.zig");
405     const D = simd.FixedTag(u32, 8);
406     const low: D.Vector = .{ 0, 1, 2, 3, 4, 5, 6, 7 };
407     const high: D.Vector = .{ 10, 11, 12, 13, 14, 15, 16, 17 };
408     try std.testing.expect(@reduce(.And, shiftLeftLanes(D, 1, low) == @as(D.Vector, .{ 0, 0, 1, 2, 0, 4, 5, 6 })));
409     try std.testing.expect(@reduce(.And, shiftRightLanes(D, 1, low) == @as(D.Vector, .{ 1, 2, 3, 0, 5, 6, 7, 0 })));
410     try std.testing.expect(@reduce(.And, combineShiftRightLanes(D, 2, high, low) == @as(D.Vector, .{ 2, 3, 10, 11, 6, 7, 14, 15 })));
411 
412     const B = simd.FixedTag(u8, 32);
413     const bytes = simd.iota(B, 1);
414     try std.testing.expectEqual(@as(u8, 0), shiftLeftBytes(B, 1, bytes)[0]);
415     try std.testing.expectEqual(@as(u8, 1), shiftLeftBytes(B, 1, bytes)[1]);
416     try std.testing.expectEqual(@as(u8, 0), shiftLeftBytes(B, 1, bytes)[16]);
417     try std.testing.expectEqual(@as(u8, 2), shiftRightBytes(B, 1, bytes)[0]);
418     try std.testing.expectEqual(@as(u8, 0), shiftRightBytes(B, 1, bytes)[15]);
419 }
420 
421 test "Highway per-four-lane shuffle repeats its pattern" {
422     const simd = @import("root.zig");
423     const D = simd.FixedTag(i32, 8);
424     const value: D.Vector = .{ 0, 1, 2, 3, 4, 5, 6, 7 };
425     try std.testing.expect(@reduce(.And, per4LaneBlockShuffle(D, 0, 1, 2, 3, value) ==
426         @as(D.Vector, .{ 3, 2, 1, 0, 7, 6, 5, 4 })));
427     try std.testing.expect(@reduce(.And, shuffle1032(D, value) == @as(D.Vector, .{ 2, 3, 0, 1, 6, 7, 4, 5 })));
428     try std.testing.expect(@reduce(.And, shuffle0321(D, value) == @as(D.Vector, .{ 1, 2, 3, 0, 5, 6, 7, 4 })));
429     try std.testing.expect(@reduce(.And, shuffle2103(D, value) == @as(D.Vector, .{ 3, 0, 1, 2, 7, 4, 5, 6 })));
430     try std.testing.expect(@reduce(.And, shuffle2301(D, value) == @as(D.Vector, .{ 1, 0, 3, 2, 5, 4, 7, 6 })));
431     try std.testing.expect(@reduce(.And, shuffle0123(D, value) == @as(D.Vector, .{ 3, 2, 1, 0, 7, 6, 5, 4 })));
432     const D64 = simd.FixedTag(u64, 4);
433     try std.testing.expect(@reduce(.And, shuffle01(D64, .{ 0, 1, 2, 3 }) ==
434         @as(D64.Vector, .{ 1, 0, 3, 2 })));
435 }
436 
437 test "Highway block extraction insertion broadcast and selection retain block boundaries" {
438     const simd = @import("root.zig");
439     const D = simd.FixedTag(u32, 16);
440     const value: D.Vector = .{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
441     const replacement: @Vector(4, u32) = .{ 90, 91, 92, 93 };
442     try std.testing.expectEqual(@as(usize, 4), blocks(D));
443     try std.testing.expect(@reduce(.And, extractBlock(D, 2, value) == replacement - @as(@Vector(4, u32), @splat(82))));
444     try std.testing.expect(@reduce(.And, insertBlock(D, 1, value, replacement) == @as(D.Vector, .{
445         0, 1, 2, 3, 90, 91, 92, 93, 8, 9, 10, 11, 12, 13, 14, 15,
446     })));
447     try std.testing.expect(@reduce(.And, broadcastBlock(D, 2, value) == @as(D.Vector, .{
448         8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11, 8, 9, 10, 11,
449     })));
450     try std.testing.expect(@reduce(.And, swapAdjacentBlocks(D, value) == @as(D.Vector, .{
451         4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11,
452     })));
453 }
454 
455 test "Highway block interleaves select even odd lower and upper source blocks" {
456     const simd = @import("root.zig");
457     const D = simd.FixedTag(u32, 16);
458     const a: D.Vector = .{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
459     const b = a + @as(D.Vector, @splat(100));
460     try std.testing.expect(@reduce(.And, oddEvenBlocks(D, b, a) == @as(D.Vector, .{
461         0, 1, 2, 3, 104, 105, 106, 107, 8, 9, 10, 11, 112, 113, 114, 115,
462     })));
463     try std.testing.expect(@reduce(.And, interleaveEvenBlocks(D, a, b) == @as(D.Vector, .{
464         0, 1, 2, 3, 100, 101, 102, 103, 8, 9, 10, 11, 108, 109, 110, 111,
465     })));
466     try std.testing.expect(@reduce(.And, interleaveOddBlocks(D, a, b) == @as(D.Vector, .{
467         4, 5, 6, 7, 104, 105, 106, 107, 12, 13, 14, 15, 112, 113, 114, 115,
468     })));
469     try std.testing.expect(@reduce(.And, interleaveLowerBlocks(D, a, b) == @as(D.Vector, .{
470         0, 1, 2, 3, 100, 101, 102, 103, 4, 5, 6, 7, 104, 105, 106, 107,
471     })));
472     try std.testing.expect(@reduce(.And, interleaveUpperBlocks(D, a, b) == @as(D.Vector, .{
473         8, 9, 10, 11, 108, 109, 110, 111, 12, 13, 14, 15, 112, 113, 114, 115,
474     })));
475 }
476 
477 fn verifyBlockLaneType(comptime T: type) !void {
478     const simd = @import("root.zig");
479     const D = simd.FixedTag(T, 32 / @sizeOf(T));
480     const B = @Vector(16 / @sizeOf(T), T);
481     const value: D.Vector = @splat(0);
482     const block_value: B = @splat(0);
483     try std.testing.expect(@reduce(.And, broadcast(D, 0, value) == value));
484     try std.testing.expect(@reduce(.And, interleaveLower(D, value, value) == value));
485     try std.testing.expect(@reduce(.And, interleaveUpper(D, value, value) == value));
486     try std.testing.expect(@reduce(.And, interleaveEven(D, value, value) == value));
487     try std.testing.expect(@reduce(.And, interleaveOdd(D, value, value) == value));
488     try std.testing.expect(@reduce(.And, shiftLeftLanes(D, 1, value) == value));
489     try std.testing.expect(@reduce(.And, shiftRightLanes(D, 1, value) == value));
490     try std.testing.expect(@reduce(.And, combineShiftRightLanes(D, 1, value, value) == value));
491     if (comptime @typeInfo(T) == .int) {
492         try std.testing.expect(@reduce(.And, shiftLeftBytes(D, 1, value) == value));
493         try std.testing.expect(@reduce(.And, shiftRightBytes(D, 1, value) == value));
494         try std.testing.expect(@reduce(.And, combineShiftRightBytes(D, 1, value, value) == value));
495     }
496     try std.testing.expect(@reduce(.And, insertBlock(D, 1, value, block_value) == value));
497     try std.testing.expect(@reduce(.And, extractBlock(D, 1, value) == block_value));
498     try std.testing.expect(@reduce(.And, broadcastBlock(D, 1, value) == value));
499     try std.testing.expect(@reduce(.And, oddEvenBlocks(D, value, value) == value));
500     try std.testing.expect(@reduce(.And, swapAdjacentBlocks(D, value) == value));
501     try std.testing.expect(@reduce(.And, interleaveEvenBlocks(D, value, value) == value));
502     try std.testing.expect(@reduce(.And, interleaveOddBlocks(D, value, value) == value));
503     try std.testing.expect(@reduce(.And, interleaveLowerBlocks(D, value, value) == value));
504     try std.testing.expect(@reduce(.And, interleaveUpperBlocks(D, value, value) == value));
505 }
506 
507 test "Highway block geometry instantiates every lane type" {
508     inline for (.{ u8, i8, u16, i16, u32, i32, u64, i64, f16, f32, f64 }) |T| {
509         try verifyBlockLaneType(T);
510     }
511     inline for (.{ u8, i8, u16, i16, u32, i32 }) |T| {
512         const simd = @import("root.zig");
513         const D = simd.FixedTag(T, 16 / @sizeOf(T));
514         const value: D.Vector = @splat(0);
515         try std.testing.expect(@reduce(.And, zipLower(D, value, value) ==
516             @as(D.repartition(wideInteger(T)).Vector, @splat(0))));
517         try std.testing.expect(@reduce(.And, zipUpper(D, value, value) ==
518             @as(D.repartition(wideInteger(T)).Vector, @splat(0))));
519     }
520 }