lib/simd/src/aligned.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const builtin = @import("builtin");
  3 
  4 pub const alignment: usize = 128;
  5 pub const native_vector_bytes: usize = std.simd.suggestVectorLength(u8) orelse 1;
  6 
  7 pub const Error = std.mem.Allocator.Error || error{
  8     AllocationSizeOverflow,
  9     DimensionOverflow,
 10     EmptyAllocation,
 11     IndexOutOfBounds,
 12     InvalidVectorBytes,
 13     MisalignedStorage,
 14     ShapeExpansion,
 15     StorageTooSmall,
 16     ZeroDimension,
 17 };
 18 
 19 const allocation_alignment: usize = switch (builtin.target.cpu.arch) {
 20     .riscv32, .riscv64 => if (std.Target.riscv.featureSetHas(
 21         builtin.target.cpu.features,
 22         .v,
 23     )) @max(alignment, 4096) else alignment,
 24     else => alignment,
 25 };
 26 const alias_bytes: usize = switch (builtin.target.cpu.arch) {
 27     .x86, .x86_64 => @max(allocation_alignment, 1024),
 28     else => allocation_alignment,
 29 };
 30 const alias_groups: usize = alias_bytes / allocation_alignment;
 31 
 32 var next_offset = std.atomic.Value(usize).init(0);
 33 
 34 pub fn isAligned(pointer: anytype) bool {
 35     return isAlignedTo(pointer, alignment);
 36 }
 37 
 38 pub fn isAlignedTo(pointer: anytype, byte_alignment: usize) bool {
 39     std.debug.assert(byte_alignment != 0);
 40     return @intFromPtr(pointer) % byte_alignment == 0;
 41 }
 42 
 43 pub fn isDescriptorAligned(comptime D: type, pointer: anytype) bool {
 44     const Child = switch (@typeInfo(@TypeOf(pointer))) {
 45         .pointer => |info| info.child,
 46         else => @compileError("descriptor alignment requires a pointer"),
 47     };
 48     return isAlignedTo(pointer, D.lane_count * @sizeOf(Child));
 49 }
 50 
 51 pub fn Allocation(comptime T: type) type {
 52     if (@sizeOf(T) == 0) @compileError("aligned allocations require nonzero-sized values");
 53     if (@alignOf(T) > allocation_alignment) {
 54         @compileError("value alignment exceeds the Highway allocation alignment");
 55     }
 56 
 57     return struct {
 58         allocation: []u8,
 59         values: []align(allocation_alignment) T,
 60 
 61         const Self = @This();
 62 
 63         pub fn init(allocator: std.mem.Allocator, count: usize) Error!Self {
 64             if (count == 0) return error.EmptyAllocation;
 65             const payload_bytes = std.math.mul(usize, count, @sizeOf(T)) catch
 66                 return error.AllocationSizeOverflow;
 67             if (payload_bytes >= std.math.maxInt(usize) / 2) {
 68                 return error.AllocationSizeOverflow;
 69             }
 70             const offset = nextAlignedOffset();
 71             const prefix_bytes = std.math.add(usize, alias_bytes, offset) catch
 72                 return error.AllocationSizeOverflow;
 73             const allocated_bytes = std.math.add(usize, prefix_bytes, payload_bytes) catch
 74                 return error.AllocationSizeOverflow;
 75             const allocation = try allocator.alloc(u8, allocated_bytes);
 76             errdefer allocator.free(allocation);
 77             const aligned_base = std.mem.alignBackward(
 78                 usize,
 79                 @intFromPtr(allocation.ptr) + alias_bytes,
 80                 alias_bytes,
 81             );
 82             const payload_address = aligned_base + offset;
 83             std.debug.assert(payload_address >= @intFromPtr(allocation.ptr));
 84             std.debug.assert(payload_address + payload_bytes <=
 85                 @intFromPtr(allocation.ptr) + allocation.len);
 86             std.debug.assert(payload_address % allocation_alignment == 0);
 87             const payload_offset = payload_address - @intFromPtr(allocation.ptr);
 88             const byte_pointer: [*]align(allocation_alignment) u8 =
 89                 @alignCast(allocation.ptr + payload_offset);
 90             const pointer: [*]align(allocation_alignment) T = @ptrCast(byte_pointer);
 91             return .{
 92                 .allocation = allocation,
 93                 .values = pointer[0..count],
 94             };
 95         }
 96 
 97         pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
 98             allocator.free(self.allocation);
 99             self.* = undefined;
100         }
101 
102         pub fn slice(self: *Self) []T {
103             return self.values;
104         }
105 
106         pub fn constSlice(self: *const Self) []const T {
107             return self.values;
108         }
109     };
110 }
111 
112 pub fn Vector(comptime T: type) type {
113     return struct {
114         storage: ?Allocation(T) = null,
115         len_value: usize = 0,
116 
117         const Self = @This();
118 
119         pub fn init(
120             allocator: std.mem.Allocator,
121             initial: []const T,
122         ) Error!Self {
123             var self = try initCapacity(allocator, initial.len);
124             if (initial.len != 0) {
125                 @memcpy(self.storage.?.values[0..initial.len], initial);
126                 self.len_value = initial.len;
127             }
128             return self;
129         }
130 
131         pub fn initCapacity(
132             allocator: std.mem.Allocator,
133             capacity_value: usize,
134         ) Error!Self {
135             if (capacity_value == 0) return .{};
136             return .{ .storage = try Allocation(T).init(allocator, capacity_value) };
137         }
138 
139         pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
140             if (self.storage) |*storage| storage.deinit(allocator);
141             self.* = .{};
142         }
143 
144         pub fn len(self: *const Self) usize {
145             return self.len_value;
146         }
147 
148         pub fn capacity(self: *const Self) usize {
149             return if (self.storage) |storage| storage.values.len else 0;
150         }
151 
152         pub fn items(self: *Self) []T {
153             if (self.storage) |*storage| return storage.values[0..self.len_value];
154             return @constCast((&[_]T{})[0..]);
155         }
156 
157         pub fn constItems(self: *const Self) []const T {
158             if (self.storage) |*storage| return storage.values[0..self.len_value];
159             return &.{};
160         }
161 
162         pub fn append(
163             self: *Self,
164             allocator: std.mem.Allocator,
165             value: T,
166         ) Error!void {
167             const required = std.math.add(usize, self.len_value, 1) catch
168                 return error.AllocationSizeOverflow;
169             try self.ensureTotalCapacity(allocator, required);
170             self.storage.?.values[self.len_value] = value;
171             self.len_value += 1;
172         }
173 
174         pub fn appendSlice(
175             self: *Self,
176             allocator: std.mem.Allocator,
177             values: []const T,
178         ) Error!void {
179             if (values.len == 0) return;
180             const required = std.math.add(usize, self.len_value, values.len) catch
181                 return error.AllocationSizeOverflow;
182             try self.ensureTotalCapacity(allocator, required);
183             @memcpy(self.storage.?.values[self.len_value..required], values);
184             self.len_value = required;
185         }
186 
187         pub fn pop(self: *Self) ?T {
188             if (self.len_value == 0) return null;
189             self.len_value -= 1;
190             return self.storage.?.values[self.len_value];
191         }
192 
193         pub fn clearRetainingCapacity(self: *Self) void {
194             self.len_value = 0;
195         }
196 
197         pub fn ensureTotalCapacity(
198             self: *Self,
199             allocator: std.mem.Allocator,
200             required: usize,
201         ) Error!void {
202             const current_capacity = self.capacity();
203             if (required <= current_capacity) return;
204             const grown = std.math.mul(usize, current_capacity, 2) catch required;
205             const new_capacity = @max(required, @max(@as(usize, 8), grown));
206             var replacement = try Allocation(T).init(allocator, new_capacity);
207             if (self.storage) |*storage| {
208                 @memcpy(replacement.values[0..self.len_value], storage.values[0..self.len_value]);
209                 storage.deinit(allocator);
210             }
211             self.storage = replacement;
212         }
213     };
214 }
215 
216 pub fn Layout(comptime axes: usize) type {
217     if (axes == 0) @compileError("aligned arrays require at least one axis");
218 
219     return struct {
220         shape_value: [axes]usize,
221         memory_shape_value: [axes]usize,
222         sizes: [axes + 1]usize,
223         memory_sizes: [axes + 1]usize,
224         vector_bytes: usize,
225 
226         const Self = @This();
227 
228         pub fn init(shape_value: [axes]usize) Error!Self {
229             return initFor(shape_value, native_vector_bytes);
230         }
231 
232         pub fn initFor(
233             shape_value: [axes]usize,
234             vector_bytes: usize,
235         ) Error!Self {
236             if (!std.math.isPowerOfTwo(vector_bytes)) return error.InvalidVectorBytes;
237             for (shape_value) |dimension| {
238                 if (dimension == 0) return error.ZeroDimension;
239             }
240             var memory_shape_value = shape_value;
241             memory_shape_value[axes - 1] = try roundUp(
242                 memory_shape_value[axes - 1],
243                 vector_bytes,
244             );
245             return .{
246                 .shape_value = shape_value,
247                 .memory_shape_value = memory_shape_value,
248                 .sizes = try computeSizes(axes, shape_value),
249                 .memory_sizes = try computeSizes(axes, memory_shape_value),
250                 .vector_bytes = vector_bytes,
251             };
252         }
253 
254         pub fn shape(self: *const Self) [axes]usize {
255             return self.shape_value;
256         }
257 
258         pub fn memoryShape(self: *const Self) [axes]usize {
259             return self.memory_shape_value;
260         }
261 
262         pub fn len(self: *const Self) usize {
263             return self.sizes[0];
264         }
265 
266         pub fn memoryLen(self: *const Self) usize {
267             return self.memory_sizes[0];
268         }
269 
270         pub fn memoryBytes(self: *const Self, comptime T: type) Error!usize {
271             return std.math.mul(usize, self.memoryLen(), @sizeOf(T)) catch
272                 error.AllocationSizeOverflow;
273         }
274 
275         pub fn rowLen(self: *const Self) usize {
276             return self.shape_value[axes - 1];
277         }
278 
279         pub fn rowOffset(
280             self: *const Self,
281             indices: [axes - 1]usize,
282         ) Error!usize {
283             var offset: usize = 0;
284             for (indices, 0..) |index, axis| {
285                 if (index >= self.shape_value[axis]) return error.IndexOutOfBounds;
286                 offset += self.memory_sizes[axis + 1] * index;
287             }
288             return offset;
289         }
290 
291         pub fn truncate(self: *Self, new_shape: [axes]usize) Error!void {
292             for (new_shape, self.shape_value) |new_dimension, old_dimension| {
293                 if (new_dimension > old_dimension) return error.ShapeExpansion;
294             }
295             self.shape_value = new_shape;
296             self.sizes = try computeSizes(axes, new_shape);
297         }
298     };
299 }
300 
301 pub fn View(comptime T: type, comptime axes: usize) type {
302     return struct {
303         layout: Layout(axes),
304         storage: []T,
305 
306         const Self = @This();
307 
308         pub fn init(storage: []T, shape_value: [axes]usize) Error!Self {
309             return initFor(storage, shape_value, native_vector_bytes);
310         }
311 
312         pub fn initFor(
313             storage: []T,
314             shape_value: [axes]usize,
315             vector_bytes: usize,
316         ) Error!Self {
317             if (!isAligned(storage.ptr)) return error.MisalignedStorage;
318             const layout = try Layout(axes).initFor(shape_value, vector_bytes);
319             if (storage.len < layout.memoryLen()) return error.StorageTooSmall;
320             return .{
321                 .layout = layout,
322                 .storage = storage[0..layout.memoryLen()],
323             };
324         }
325 
326         pub fn row(self: *Self, indices: [axes - 1]usize) Error![]T {
327             const offset = try self.layout.rowOffset(indices);
328             return self.storage[offset..][0..self.layout.rowLen()];
329         }
330 
331         pub fn constRow(
332             self: *const Self,
333             indices: [axes - 1]usize,
334         ) Error![]const T {
335             const offset = try self.layout.rowOffset(indices);
336             return self.storage[offset..][0..self.layout.rowLen()];
337         }
338 
339         pub fn truncate(self: *Self, new_shape: [axes]usize) Error!void {
340             try self.layout.truncate(new_shape);
341         }
342     };
343 }
344 
345 pub fn Array(comptime T: type, comptime axes: usize) type {
346     return struct {
347         layout: Layout(axes),
348         allocation: Allocation(T),
349 
350         const Self = @This();
351 
352         pub fn init(
353             allocator: std.mem.Allocator,
354             shape_value: [axes]usize,
355         ) Error!Self {
356             return initFor(allocator, shape_value, native_vector_bytes);
357         }
358 
359         pub fn initFor(
360             allocator: std.mem.Allocator,
361             shape_value: [axes]usize,
362             vector_bytes: usize,
363         ) Error!Self {
364             const layout = try Layout(axes).initFor(shape_value, vector_bytes);
365             _ = try layout.memoryBytes(T);
366             const allocation = try Allocation(T).init(allocator, layout.memoryLen());
367             @memset(allocation.values, std.mem.zeroes(T));
368             return .{
369                 .layout = layout,
370                 .allocation = allocation,
371             };
372         }
373 
374         pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
375             self.allocation.deinit(allocator);
376             self.* = undefined;
377         }
378 
379         pub fn row(self: *Self, indices: [axes - 1]usize) Error![]T {
380             const offset = try self.layout.rowOffset(indices);
381             return self.allocation.values[offset..][0..self.layout.rowLen()];
382         }
383 
384         pub fn constRow(
385             self: *const Self,
386             indices: [axes - 1]usize,
387         ) Error![]const T {
388             const offset = try self.layout.rowOffset(indices);
389             return self.allocation.values[offset..][0..self.layout.rowLen()];
390         }
391 
392         pub fn shape(self: *const Self) [axes]usize {
393             return self.layout.shape();
394         }
395 
396         pub fn memoryShape(self: *const Self) [axes]usize {
397             return self.layout.memoryShape();
398         }
399 
400         pub fn len(self: *const Self) usize {
401             return self.layout.len();
402         }
403 
404         pub fn memoryLen(self: *const Self) usize {
405             return self.layout.memoryLen();
406         }
407 
408         pub fn data(self: *Self) []T {
409             return self.allocation.values;
410         }
411 
412         pub fn constData(self: *const Self) []const T {
413             return self.allocation.values;
414         }
415 
416         pub fn truncate(self: *Self, new_shape: [axes]usize) Error!void {
417             try self.layout.truncate(new_shape);
418         }
419     };
420 }
421 
422 fn nextAlignedOffset() usize {
423     const ordinal = next_offset.fetchAdd(1, .monotonic);
424     var offset = allocation_alignment * (ordinal % alias_groups);
425     if (offset == 0) offset = allocation_alignment;
426     return offset;
427 }
428 
429 fn roundUp(value: usize, multiple: usize) Error!usize {
430     const adjusted = std.math.add(usize, value, multiple - 1) catch
431         return error.DimensionOverflow;
432     return adjusted & ~(multiple - 1);
433 }
434 
435 fn computeSizes(comptime axes: usize, shape_value: [axes]usize) Error![axes + 1]usize {
436     var sizes: [axes + 1]usize = undefined;
437     sizes[axes] = 1;
438     var axis = axes;
439     while (axis != 0) {
440         axis -= 1;
441         sizes[axis] = std.math.mul(usize, sizes[axis + 1], shape_value[axis]) catch
442             return error.DimensionOverflow;
443     }
444     return sizes;
445 }
446 
447 fn shiftCount(value: usize) usize {
448     return if (value <= 1) 0 else 1 + shiftCount(value / 2);
449 }
450 
451 fn checkArrayInitFailures(allocator: std.mem.Allocator) !void {
452     var array = try Array(f32, 3).init(allocator, .{ 3, 5, 7 });
453     array.deinit(allocator);
454 }
455 
456 test "Highway aligned allocation preserves alignment ownership and payload" {
457     var counting = std.testing.FailingAllocator.init(std.testing.allocator, .{});
458     var allocation = try Allocation(u8).init(counting.allocator(), 7777);
459     defer allocation.deinit(counting.allocator());
460     try std.testing.expectEqual(@as(usize, 1), counting.alloc_index);
461     try std.testing.expect(isAligned(allocation.values.ptr));
462     var digest: usize = 0;
463     for (allocation.values, 0..) |*value, index| {
464         value.* = @intCast(index & 0x7f);
465         if (index != 0) digest +%= @as(usize, value.*) * allocation.values[index - 1];
466     }
467     try std.testing.expect(digest != 0);
468 }
469 
470 test "Highway descriptor alignment uses active lanes and pointer element size" {
471     const D = @import("tag.zig").FixedTag(u32, 8);
472     var storage: [9]u32 align(32) = @splat(0);
473     try std.testing.expect(isDescriptorAligned(D, &storage[0]));
474     try std.testing.expect(!isDescriptorAligned(D, &storage[1]));
475 }
476 
477 test "Highway aligned allocation cycles x86 alias groups" {
478     var counts: [alias_groups]usize = @splat(0);
479     for (0..alias_groups) |_| {
480         var allocation = try Allocation(u8).init(std.testing.allocator, 1);
481         const group = (@intFromPtr(allocation.values.ptr) % alias_bytes) /
482             allocation_alignment;
483         counts[group] += 1;
484         allocation.deinit(std.testing.allocator);
485     }
486     if (comptime alias_groups == 1) {
487         try std.testing.expectEqual(@as(usize, 1), counts[0]);
488     } else {
489         try std.testing.expectEqual(@as(usize, 0), counts[0]);
490         try std.testing.expectEqual(@as(usize, 2), counts[1]);
491         for (counts[2..]) |count| try std.testing.expectEqual(@as(usize, 1), count);
492     }
493 }
494 
495 test "Highway typed allocation rejects every multiplication overflow" {
496     const maximum = std.math.maxInt(usize);
497     const most_significant = (maximum >> 1) + 1;
498     try std.testing.expectError(
499         error.AllocationSizeOverflow,
500         Allocation(u32).init(std.testing.allocator, maximum / 2),
501     );
502     try std.testing.expectError(
503         error.AllocationSizeOverflow,
504         Allocation(u32).init(std.testing.allocator, maximum / 3),
505     );
506     try std.testing.expectError(
507         error.AllocationSizeOverflow,
508         Allocation([5]u8).init(std.testing.allocator, maximum / 4),
509     );
510     try std.testing.expectError(
511         error.AllocationSizeOverflow,
512         Allocation(u16).init(std.testing.allocator, most_significant),
513     );
514     try std.testing.expectError(
515         error.AllocationSizeOverflow,
516         Allocation(f64).init(std.testing.allocator, most_significant + 1),
517     );
518     try std.testing.expectError(
519         error.AllocationSizeOverflow,
520         Allocation([10]u8).init(std.testing.allocator, most_significant / 4),
521     );
522     try std.testing.expectEqual(@as(usize, 0), shiftCount(1));
523     try std.testing.expectEqual(@as(usize, 1), shiftCount(2));
524     try std.testing.expectEqual(@as(usize, 3), shiftCount(8));
525 }
526 
527 test "Highway aligned arrays zero rows and retain padded geometry" {
528     var one = try Array(f32, 1).init(std.testing.allocator, .{4});
529     defer one.deinit(std.testing.allocator);
530     try std.testing.expectEqualSlices(f32, &@as([4]f32, @splat(0)), try one.constRow(.{}));
531     (try one.row(.{}))[2] = 3.4;
532     try std.testing.expectEqualSlices(f32, &.{ 0, 0, 3.4, 0 }, try one.constRow(.{}));
533 
534     var two = try Array(f32, 2).init(std.testing.allocator, .{ 2, 3 });
535     defer two.deinit(std.testing.allocator);
536     @memcpy(try two.row(.{0}), &[_]f32{ 1, 2, 3 });
537     @memcpy(try two.row(.{1}), &[_]f32{ 4, 5, 6 });
538     try std.testing.expectEqualSlices(f32, &.{ 1, 2, 3 }, try two.constRow(.{0}));
539     try std.testing.expectEqualSlices(f32, &.{ 4, 5, 6 }, try two.constRow(.{1}));
540     try std.testing.expectEqual(@as(usize, 6), two.len());
541     try std.testing.expectEqual([2]usize{ 2, 3 }, two.shape());
542     try std.testing.expectEqual([2]usize{ 2, native_vector_bytes }, two.memoryShape());
543 }
544 
545 test "pinned Highway aligned array oracle matches dispatched geometry" {
546     var array = try Array(f32, 2).initFor(std.testing.allocator, .{ 2, 3 }, 64);
547     defer array.deinit(std.testing.allocator);
548     try std.testing.expectEqual([2]usize{ 2, 3 }, array.shape());
549     try std.testing.expectEqual([2]usize{ 2, 64 }, array.memoryShape());
550     try std.testing.expectEqual(@as(usize, 6), array.len());
551     try std.testing.expectEqual(@as(usize, 128), array.memoryLen());
552     try std.testing.expect(isAligned((try array.row(.{0})).ptr));
553     try std.testing.expect(isAligned((try array.row(.{1})).ptr));
554     @memcpy(try array.row(.{0}), &[_]f32{ 1, 2, 3 });
555     @memcpy(try array.row(.{1}), &[_]f32{ 4, 5, 6 });
556     var digest: f64 = 0;
557     for (0..2) |row_index| {
558         for (try array.constRow(.{row_index})) |value| digest += value;
559     }
560     try array.truncate(.{ 1, 2 });
561     try std.testing.expectEqual(@as(f64, 21), digest);
562     try std.testing.expectEqual([2]usize{ 1, 2 }, array.shape());
563     try std.testing.expectEqual([2]usize{ 2, 64 }, array.memoryShape());
564     try std.testing.expectEqualSlices(f32, &.{ 1, 2 }, try array.constRow(.{0}));
565 }
566 
567 test "Highway aligned array rows retain native vector alignment" {
568     var array = try Array(f32, 4).init(std.testing.allocator, .{ 3, 3, 3, 3 });
569     defer array.deinit(std.testing.allocator);
570     for (0..3) |d0| {
571         for (0..3) |d1| {
572             for (0..3) |d2| {
573                 const row = try array.row(.{ d0, d1, d2 });
574                 try std.testing.expect(isAlignedTo(row.ptr, native_vector_bytes));
575             }
576         }
577     }
578 }
579 
580 test "Highway aligned array truncation preserves memory layout and values" {
581     var array = try Array(usize, 4).init(std.testing.allocator, .{ 8, 8, 8, 8 });
582     defer array.deinit(std.testing.allocator);
583     const memory_shape = array.memoryShape();
584     for (0..8) |d0| {
585         for (0..8) |d1| {
586             for (0..8) |d2| {
587                 const row = try array.row(.{ d0, d1, d2 });
588                 for (row, 0..) |*value, d3| {
589                     value.* = d0 * 8 * 8 * 8 + d1 * 8 * 8 + d2 * 8 + d3;
590                 }
591             }
592         }
593     }
594     try array.truncate(.{ 7, 7, 7, 7 });
595     try array.truncate(.{ 6, 5, 4, 3 });
596     try std.testing.expectEqual([4]usize{ 6, 5, 4, 3 }, array.shape());
597     try std.testing.expectEqual(memory_shape, array.memoryShape());
598     for (0..6) |d0| {
599         for (0..5) |d1| {
600             for (0..4) |d2| {
601                 const row = try array.constRow(.{ d0, d1, d2 });
602                 for (row, 0..) |value, d3| {
603                     try std.testing.expectEqual(
604                         d0 * 8 * 8 * 8 + d1 * 8 * 8 + d2 * 8 + d3,
605                         value,
606                     );
607                 }
608             }
609         }
610     }
611     try std.testing.expectError(error.ShapeExpansion, array.truncate(.{ 7, 5, 4, 3 }));
612 }
613 
614 test "Highway aligned vector growth preserves elements and capacity" {
615     var empty = try Vector(usize).initCapacity(std.testing.allocator, 0);
616     defer empty.deinit(std.testing.allocator);
617     try empty.appendSlice(std.testing.allocator, &.{});
618     try std.testing.expectEqual(@as(usize, 0), empty.len());
619     try std.testing.expectEqual(@as(usize, 0), empty.capacity());
620 
621     var vector = try Vector(usize).init(std.testing.allocator, &.{ 0, 1, 2, 3, 4 });
622     defer vector.deinit(std.testing.allocator);
623     try std.testing.expectEqual(@as(usize, 4), vector.pop().?);
624     try vector.appendSlice(std.testing.allocator, &.{ 4, 5 });
625     const initial_capacity = vector.capacity();
626     var value = vector.len();
627     while (value < initial_capacity + 10) : (value += 1) {
628         try vector.append(std.testing.allocator, value);
629     }
630     try std.testing.expect(vector.capacity() > initial_capacity);
631     for (vector.constItems(), 0..) |item, index| try std.testing.expectEqual(index, item);
632     vector.clearRetainingCapacity();
633     try std.testing.expectEqual(@as(usize, 0), vector.len());
634     try std.testing.expect(vector.capacity() > 0);
635 }
636 
637 test "aligned owners reject invalid geometry before mutation" {
638     try std.testing.expectError(
639         error.EmptyAllocation,
640         Allocation(u8).init(std.testing.allocator, 0),
641     );
642     try std.testing.expectError(
643         error.ZeroDimension,
644         Layout(2).initFor(.{ 2, 0 }, 16),
645     );
646     try std.testing.expectError(
647         error.InvalidVectorBytes,
648         Layout(2).initFor(.{ 2, 3 }, 3),
649     );
650     try std.testing.expectError(
651         error.DimensionOverflow,
652         Layout(1).initFor(.{std.math.maxInt(usize)}, 2),
653     );
654     try std.testing.expectError(
655         error.DimensionOverflow,
656         Layout(2).initFor(.{ std.math.maxInt(usize), 2 }, 1),
657     );
658     var storage: [260]u8 align(alignment) = undefined;
659     try std.testing.expectError(
660         error.MisalignedStorage,
661         View(u8, 2).initFor(storage[1..], .{ 2, 3 }, 4),
662     );
663     try std.testing.expectError(
664         error.StorageTooSmall,
665         View(u8, 2).initFor(storage[0..4], .{ 2, 3 }, 4),
666     );
667     var view = try View(u8, 2).initFor(&storage, .{ 2, 3 }, 4);
668     try std.testing.expectError(error.IndexOutOfBounds, view.row(.{2}));
669     try std.testing.expectError(error.ShapeExpansion, view.truncate(.{ 3, 3 }));
670 }
671 
672 test "aligned vector growth failure preserves the original owner" {
673     var failing = std.testing.FailingAllocator.init(
674         std.testing.allocator,
675         .{ .fail_index = 1 },
676     );
677     var vector = try Vector(u32).init(failing.allocator(), &.{ 1, 2, 3, 4, 5 });
678     defer vector.deinit(failing.allocator());
679     const original_pointer = vector.storage.?.values.ptr;
680     const original_capacity = vector.capacity();
681     try std.testing.expectError(error.OutOfMemory, vector.append(failing.allocator(), 6));
682     try std.testing.expectEqual(original_pointer, vector.storage.?.values.ptr);
683     try std.testing.expectEqual(original_capacity, vector.capacity());
684     try std.testing.expectEqualSlices(u32, &.{ 1, 2, 3, 4, 5 }, vector.constItems());
685 }
686 
687 test "Highway aligned array allocation failures are transactional" {
688     try std.testing.checkAllAllocationFailures(
689         std.testing.allocator,
690         checkArrayInitFailures,
691         .{},
692     );
693 }