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 }