authorgravatar for rb.lymn@gmail.comRobbie Lyman <rb.lymn@gmail.com> 2026-08-06 20:35:17-04:00
committergravatar for rb.lymn@gmail.comRobbie Lyman <rb.lymn@gmail.com> 2026-08-06 21:42:17-04:00
logb470382173c6be27f6cdd46c3adce0e59f2b3675
tree2d6f1fabf825201d1c97fe2f55192da5ed8bc868
parent009604d690aeaac6957d4646a815bc8762e84757

feat(mem): allow absorbSentinel on *[n]T


3 files changed, 107 insertions(+), 43 deletions(-)

lib/std/mem.zig+26-12
...@@ -9,6 +9,7 @@ const assert = debug.assert;...@@ -9,6 +9,7 @@ const assert = debug.assert;
9const math = std.math;9const math = std.math;
10const testing = std.testing;10const testing = std.testing;
11const Endian = std.lang.Endian;11const Endian = std.lang.Endian;
12const AbsorbSentinel = std.meta.AbsorbSentinel;
1213
13/// The standard library currently thoroughly depends on byte size14/// The standard library currently thoroughly depends on byte size
14/// being 8 bits. (see the use of u8 throughout allocation code as15/// being 8 bits. (see the use of u8 throughout allocation code as
...@@ -4738,22 +4739,28 @@ test "sliceAsBytes preserves pointer attributes" {...@@ -4738,22 +4739,28 @@ test "sliceAsBytes preserves pointer attributes" {
4738 try testing.expectEqual(in_attrs.@"align", out_attrs.@"align");4739 try testing.expectEqual(in_attrs.@"align", out_attrs.@"align");
4739}4740}
47404741
4741fn AbsorbSentinelReturnType(comptime Slice: type) type {
4742 const info = @typeInfo(Slice).pointer;
4743 assert(info.size == .slice);
4744 return @Pointer(.slice, info.attrs, info.child, null);
4745}
4746
4747/// If the provided slice is not sentinel terminated, do nothing and return that slice.4742/// If the provided slice is not sentinel terminated, do nothing and return that slice.
4748/// If it is sentinel-terminated, return a non-sentinel-terminated slice with the4743/// If it is sentinel-terminated, return a non-sentinel-terminated slice with the
4749/// length increased by one to include the absorbed sentinel element.4744/// length increased by one to include the absorbed sentinel element.
4750pub fn absorbSentinel(slice: anytype) AbsorbSentinelReturnType(@TypeOf(slice)) {4745pub fn absorbSentinel(slice: anytype) AbsorbSentinel(@TypeOf(slice)) {
4751 const info = @typeInfo(@TypeOf(slice)).pointer;4746 const info = @typeInfo(@TypeOf(slice)).pointer;
4752 comptime assert(info.size == .slice);4747 switch (info.size) {
4753 if (info.sentinel_ptr == null) {4748 .slice => {
4754 return slice;4749 if (info.sentinel_ptr == null) {
4755 } else {4750 return slice;
4756 return slice.ptr[0 .. slice.len + 1];4751 } else {
4752 return slice.ptr[0 .. slice.len + 1];
4753 }
4754 },
4755 .one => {
4756 const child_info = @typeInfo(info.child).array;
4757 if (child_info.sentinel_ptr == null) {
4758 return slice;
4759 } else {
4760 return slice[0 .. child_info.len + 1];
4761 }
4762 },
4763 else => unreachable,
4757 }4764 }
4758}4765}
47594766
...@@ -4762,21 +4769,28 @@ test absorbSentinel {...@@ -4762,21 +4769,28 @@ test absorbSentinel {
4762 var buffer: [3:0]u8 = .{ 1, 2, 3 };4769 var buffer: [3:0]u8 = .{ 1, 2, 3 };
4763 const foo: [:0]const u8 = &buffer;4770 const foo: [:0]const u8 = &buffer;
4764 const bar: []const u8 = &buffer;4771 const bar: []const u8 = &buffer;
4772 const baz: *const [3:0]u8 = &buffer;
4765 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo)));4773 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo)));
4766 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar)));4774 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar)));
4775 try testing.expectEqual(*const [4]u8, @TypeOf(absorbSentinel(baz)));
4767 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo));4776 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo));
4768 try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar));4777 try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar));
4778 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(baz));
4769 }4779 }
4770 {4780 {
4771 var buffer: [3:0]u8 = .{ 1, 2, 3 };4781 var buffer: [3:0]u8 = .{ 1, 2, 3 };
4772 const foo: [:0]u8 = &buffer;4782 const foo: [:0]u8 = &buffer;
4773 const bar: []u8 = &buffer;4783 const bar: []u8 = &buffer;
4784 const baz: *[3:0]u8 = &buffer;
4774 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo)));4785 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo)));
4775 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar)));4786 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar)));
4787 try testing.expectEqual(*[4]u8, @TypeOf(absorbSentinel(baz)));
4776 var expected_foo = [_]u8{ 1, 2, 3, 0 };4788 var expected_foo = [_]u8{ 1, 2, 3, 0 };
4777 try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo));4789 try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo));
4778 var expected_bar = [_]u8{ 1, 2, 3 };4790 var expected_bar = [_]u8{ 1, 2, 3 };
4779 try testing.expectEqualSlices(u8, &expected_bar, absorbSentinel(bar));4791 try testing.expectEqualSlices(u8, &expected_bar, absorbSentinel(bar));
4792 var expected_baz = [_]u8{ 1, 2, 3, 0 };
4793 try testing.expectEqualSlices(u8, &expected_baz, absorbSentinel(baz));
4780 }4794 }
4781}4795}
47824796
lib/std/mem/Allocator.zig+59-31
...@@ -9,6 +9,7 @@ const math = std.math;...@@ -9,6 +9,7 @@ const math = std.math;
9const mem = std.mem;9const mem = std.mem;
10const Alignment = std.mem.Alignment;10const Alignment = std.mem.Alignment;
11const Slice = std.meta.Slice;11const Slice = std.meta.Slice;
12const AbsorbSentinel = std.meta.AbsorbSentinel;
1213
13pub const Error = error{OutOfMemory};14pub const Error = error{OutOfMemory};
14pub const Log2Align = math.Log2Int(usize);15pub const Log2Align = math.Log2Int(usize);
...@@ -316,15 +317,16 @@ pub fn allocBytesAligned(...@@ -316,15 +317,16 @@ pub fn allocBytesAligned(
316///317///
317/// `new_len` may be zero, in which case the allocation is freed.318/// `new_len` may be zero, in which case the allocation is freed.
318pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {319pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
319 const SliceType = Slice(@TypeOf(allocation));320 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
320 const slice: SliceType = allocation; // coerce *[len]T to []T321 const T = if (slice_info.size != .slice) comptime T: {
321 const slice_info = @typeInfo(SliceType).pointer;322 assert(slice_info.size == .one);
322 const T = slice_info.child;323 break :T @typeInfo(slice_info.child).array.child;
324 } else slice_info.child;
323 if (new_len == 0) {325 if (new_len == 0) {
324 self.free(slice);326 self.free(allocation);
325 return true;327 return true;
326 }328 }
327 if (slice.len == 0) {329 if (allocation.len == 0) {
328 return false;330 return false;
329 }331 }
330 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));332 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
...@@ -356,25 +358,26 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {...@@ -356,25 +358,26 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
356/// `new_len` may be zero, in which case the allocation is freed.358/// `new_len` may be zero, in which case the allocation is freed.
357///359///
358/// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`.360/// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`.
359pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeOf(allocation)) {361pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(AbsorbSentinel(@TypeOf(allocation))) {
360 const SliceType = Slice(@TypeOf(allocation));362 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
361 const slice: SliceType = allocation; // coerce *[len]T to []T363 const T = if (slice_info.size != .slice) comptime T: {
362 const slice_info = @typeInfo(SliceType).pointer;364 assert(slice_info.size == .one);
363 const T = slice_info.child;365 break :T @typeInfo(slice_info.child).array.child;
366 } else slice_info.child;
364367
365 if (new_len == 0) {368 if (new_len == 0) {
366 self.free(slice);369 self.free(allocation);
367 return slice[0..0];370 return allocation[0..0];
368 }371 }
369 if (slice.len == 0) {372 if (allocation.len == 0) {
370 return null;373 return null;
371 }374 }
372 if (@sizeOf(T) == 0) {375 if (@sizeOf(T) == 0) {
373 var new_memory = slice;376 var new_memory = allocation;
374 new_memory.len = new_len;377 new_memory.len = new_len;
375 return new_memory;378 return new_memory;
376 }379 }
377 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice)));380 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
378 // I would like to use saturating multiplication here, but LLVM cannot lower it381 // I would like to use saturating multiplication here, but LLVM cannot lower it
379 // on WebAssembly: https://github.com/ziglang/zig/issues/9660382 // on WebAssembly: https://github.com/ziglang/zig/issues/9660
380 //const new_len_bytes = new_len *| @sizeOf(T);383 //const new_len_bytes = new_len *| @sizeOf(T);
...@@ -402,7 +405,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeO...@@ -402,7 +405,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeO
402/// do the realloc more efficiently than the caller405/// do the realloc more efficiently than the caller
403/// * `resize` which returns `false` when the `Allocator` implementation cannot406/// * `resize` which returns `false` when the `Allocator` implementation cannot
404/// change the size without relocating the allocation.407/// change the size without relocating the allocation.
405pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!Slice(@TypeOf(old_mem)) {408pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) {
406 return self.reallocAdvanced(old_mem, new_n, @returnAddress());409 return self.reallocAdvanced(old_mem, new_n, @returnAddress());
407}410}
408411
...@@ -411,24 +414,24 @@ pub fn reallocAdvanced(...@@ -411,24 +414,24 @@ pub fn reallocAdvanced(
411 old_mem: anytype,414 old_mem: anytype,
412 new_n: usize,415 new_n: usize,
413 return_address: usize,416 return_address: usize,
414) Error!Slice(@TypeOf(old_mem)) {417) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) {
415 const SliceType = Slice(@TypeOf(old_mem));418 const slice_info = @typeInfo(@TypeOf(old_mem)).pointer;
416 const slice: SliceType = old_mem; // coerce *[len]T to []T419 const T = if (slice_info.size != .slice) comptime T: {
417 const slice_info = @typeInfo(SliceType).pointer;420 assert(slice_info.size == .one);
418 comptime assert(slice_info.size == .slice);421 break :T @typeInfo(slice_info.child).array.child;
419 const T = slice_info.child;422 } else slice_info.child;
420 if (slice.len == 0) {423 if (old_mem.len == 0) {
421 return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address);424 return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address);
422 }425 }
423 if (new_n == 0) {426 if (new_n == 0) {
424 self.free(slice);427 self.free(old_mem);
425 const alignment = slice_info.attrs.@"align" orelse @alignOf(T);428 const alignment = slice_info.attrs.@"align" orelse @alignOf(T);
426 const addr = comptime std.mem.alignBackward(usize, math.maxInt(usize), alignment);429 const addr = comptime std.mem.alignBackward(usize, math.maxInt(usize), alignment);
427 const ptr: *align(alignment) [0]T = @ptrFromInt(addr);430 const ptr: *align(alignment) [0]T = @ptrFromInt(addr);
428 return ptr;431 return ptr;
429 }432 }
430433
431 const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice)));434 const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(old_mem)));
432 const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return error.OutOfMemory;435 const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return error.OutOfMemory;
433 // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure436 // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure
434 if (self.rawRemap(old_byte_slice, .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(T)), byte_count, return_address)) |p| {437 if (self.rawRemap(old_byte_slice, .fromByteUnits(slice_info.attrs.@"align" orelse @alignOf(T)), byte_count, return_address)) |p| {
...@@ -451,10 +454,8 @@ pub fn reallocAdvanced(...@@ -451,10 +454,8 @@ pub fn reallocAdvanced(
451pub fn free(self: Allocator, memory: anytype) void {454pub fn free(self: Allocator, memory: anytype) void {
452 const slice_info = @typeInfo(@TypeOf(memory)).pointer;455 const slice_info = @typeInfo(@TypeOf(memory)).pointer;
453 if (slice_info.size != .slice) {456 if (slice_info.size != .slice) {
454 const slice: Slice(@TypeOf(memory)) = memory; // coerce *[len]T to []T457 assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array);
455 return free(self, slice);
456 }458 }
457 comptime assert(slice_info.size == .slice);
458 const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory)));459 const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory)));
459 if (bytes.len == 0) return;460 if (bytes.len == 0) return;
460 @memset(bytes, undefined);461 @memset(bytes, undefined);
...@@ -600,6 +601,33 @@ test failing {...@@ -600,6 +601,33 @@ test failing {
600601
601test "free single-pointer to array" {602test "free single-pointer to array" {
602 const allocator = std.testing.allocator;603 const allocator = std.testing.allocator;
603 const bytes = allocator.alloc(u32, 128) catch return error.SkipZigTest;604 {
604 allocator.free(bytes.ptr[0..128]);605 const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest;
606 slice[127] = 0;
607 const ptr = slice[0..127 :0];
608 allocator.free(ptr);
609 }
610 {
611 const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest;
612 slice[127] = 0;
613 const ptr = slice[0..127 :0];
614 if (allocator.resize(ptr, 16)) {
615 allocator.free(ptr[0..16]);
616 } else allocator.free(ptr);
617 }
618 {
619 const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest;
620 slice[127] = 0;
621 const ptr = slice[0..127 :0];
622 if (allocator.remap(ptr, 16)) |new| {
623 allocator.free(new);
624 } else allocator.free(ptr);
625 }
626 {
627 const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest;
628 slice[127] = 0;
629 const ptr = slice[0..127 :0];
630 const new = allocator.realloc(ptr, 16) catch return error.SkipZigTest;
631 allocator.free(new);
632 }
605}633}
lib/std/meta.zig+22
...@@ -1091,6 +1091,28 @@ pub fn Slice(comptime Pointer: type) type {...@@ -1091,6 +1091,28 @@ pub fn Slice(comptime Pointer: type) type {
1091 }1091 }
1092}1092}
10931093
1094/// Given a pointer type, removes the sentinel if present, returning an
1095/// equivalent pointer type with no sentinel
1096pub fn AbsorbSentinel(comptime Pointer: type) type {
1097 const info = @typeInfo(Pointer).pointer;
1098 switch (info.size) {
1099 .slice => return @Pointer(.slice, info.attrs, info.child, null),
1100 .one => {
1101 const child_info = @typeInfo(info.child).array;
1102 if (child_info.sentinel_ptr == null) {
1103 return Pointer;
1104 } else {
1105 return @Pointer(.one, info.attrs, [child_info.len + 1]child_info.child, null);
1106 }
1107 },
1108 else => unreachable,
1109 }
1110}
1111
1094test Slice {1112test Slice {
1095 try testing.expectEqual([]i32, Slice(*[10]i32));1113 try testing.expectEqual([]i32, Slice(*[10]i32));
1096}1114}
1115
1116test AbsorbSentinel {
1117 try testing.expectEqual(*[5]u32, AbsorbSentinel(*[4:0]u32));
1118}