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;
99const math = std.math;
1010const testing = std.testing;
1111const Endian = std.lang.Endian;
12const AbsorbSentinel = std.meta.AbsorbSentinel;
1213
1314/// The standard library currently thoroughly depends on byte size
1415/// being 8 bits. (see the use of u8 throughout allocation code as
......@@ -4738,22 +4739,28 @@ test "sliceAsBytes preserves pointer attributes" {
47384739 try testing.expectEqual(in_attrs.@"align", out_attrs.@"align");
47394740}
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
47474742/// If the provided slice is not sentinel terminated, do nothing and return that slice.
47484743/// If it is sentinel-terminated, return a non-sentinel-terminated slice with the
47494744/// 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)) {
47514746 const info = @typeInfo(@TypeOf(slice)).pointer;
4752 comptime assert(info.size == .slice);
4753 if (info.sentinel_ptr == null) {
4754 return slice;
4755 } else {
4756 return slice.ptr[0 .. slice.len + 1];
4747 switch (info.size) {
4748 .slice => {
4749 if (info.sentinel_ptr == null) {
4750 return slice;
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,
47574764 }
47584765}
47594766
......@@ -4762,21 +4769,28 @@ test absorbSentinel {
47624769 var buffer: [3:0]u8 = .{ 1, 2, 3 };
47634770 const foo: [:0]const u8 = &buffer;
47644771 const bar: []const u8 = &buffer;
4772 const baz: *const [3:0]u8 = &buffer;
47654773 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo)));
47664774 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar)));
4775 try testing.expectEqual(*const [4]u8, @TypeOf(absorbSentinel(baz)));
47674776 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo));
47684777 try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar));
4778 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(baz));
47694779 }
47704780 {
47714781 var buffer: [3:0]u8 = .{ 1, 2, 3 };
47724782 const foo: [:0]u8 = &buffer;
47734783 const bar: []u8 = &buffer;
4784 const baz: *[3:0]u8 = &buffer;
47744785 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo)));
47754786 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar)));
4787 try testing.expectEqual(*[4]u8, @TypeOf(absorbSentinel(baz)));
47764788 var expected_foo = [_]u8{ 1, 2, 3, 0 };
47774789 try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo));
47784790 var expected_bar = [_]u8{ 1, 2, 3 };
47794791 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));
47804794 }
47814795}
47824796
lib/std/mem/Allocator.zig+59-31
......@@ -9,6 +9,7 @@ const math = std.math;
99const mem = std.mem;
1010const Alignment = std.mem.Alignment;
1111const Slice = std.meta.Slice;
12const AbsorbSentinel = std.meta.AbsorbSentinel;
1213
1314pub const Error = error{OutOfMemory};
1415pub const Log2Align = math.Log2Int(usize);
......@@ -316,15 +317,16 @@ pub fn allocBytesAligned(
316317///
317318/// `new_len` may be zero, in which case the allocation is freed.
318319pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
319 const SliceType = Slice(@TypeOf(allocation));
320 const slice: SliceType = allocation; // coerce *[len]T to []T
321 const slice_info = @typeInfo(SliceType).pointer;
322 const T = slice_info.child;
320 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
321 const T = if (slice_info.size != .slice) comptime T: {
322 assert(slice_info.size == .one);
323 break :T @typeInfo(slice_info.child).array.child;
324 } else slice_info.child;
323325 if (new_len == 0) {
324 self.free(slice);
326 self.free(allocation);
325327 return true;
326328 }
327 if (slice.len == 0) {
329 if (allocation.len == 0) {
328330 return false;
329331 }
330332 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
......@@ -356,25 +358,26 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
356358/// `new_len` may be zero, in which case the allocation is freed.
357359///
358360/// 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)) {
360 const SliceType = Slice(@TypeOf(allocation));
361 const slice: SliceType = allocation; // coerce *[len]T to []T
362 const slice_info = @typeInfo(SliceType).pointer;
363 const T = slice_info.child;
361pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(AbsorbSentinel(@TypeOf(allocation))) {
362 const slice_info = @typeInfo(@TypeOf(allocation)).pointer;
363 const T = if (slice_info.size != .slice) comptime T: {
364 assert(slice_info.size == .one);
365 break :T @typeInfo(slice_info.child).array.child;
366 } else slice_info.child;
364367
365368 if (new_len == 0) {
366 self.free(slice);
367 return slice[0..0];
369 self.free(allocation);
370 return allocation[0..0];
368371 }
369 if (slice.len == 0) {
372 if (allocation.len == 0) {
370373 return null;
371374 }
372375 if (@sizeOf(T) == 0) {
373 var new_memory = slice;
376 var new_memory = allocation;
374377 new_memory.len = new_len;
375378 return new_memory;
376379 }
377 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice)));
380 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
378381 // I would like to use saturating multiplication here, but LLVM cannot lower it
379382 // on WebAssembly: https://github.com/ziglang/zig/issues/9660
380383 //const new_len_bytes = new_len *| @sizeOf(T);
......@@ -402,7 +405,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeO
402405/// do the realloc more efficiently than the caller
403406/// * `resize` which returns `false` when the `Allocator` implementation cannot
404407/// 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))) {
406409 return self.reallocAdvanced(old_mem, new_n, @returnAddress());
407410}
408411
......@@ -411,24 +414,24 @@ pub fn reallocAdvanced(
411414 old_mem: anytype,
412415 new_n: usize,
413416 return_address: usize,
414) Error!Slice(@TypeOf(old_mem)) {
415 const SliceType = Slice(@TypeOf(old_mem));
416 const slice: SliceType = old_mem; // coerce *[len]T to []T
417 const slice_info = @typeInfo(SliceType).pointer;
418 comptime assert(slice_info.size == .slice);
419 const T = slice_info.child;
420 if (slice.len == 0) {
417) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) {
418 const slice_info = @typeInfo(@TypeOf(old_mem)).pointer;
419 const T = if (slice_info.size != .slice) comptime T: {
420 assert(slice_info.size == .one);
421 break :T @typeInfo(slice_info.child).array.child;
422 } else slice_info.child;
423 if (old_mem.len == 0) {
421424 return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address);
422425 }
423426 if (new_n == 0) {
424 self.free(slice);
427 self.free(old_mem);
425428 const alignment = slice_info.attrs.@"align" orelse @alignOf(T);
426429 const addr = comptime std.mem.alignBackward(usize, math.maxInt(usize), alignment);
427430 const ptr: *align(alignment) [0]T = @ptrFromInt(addr);
428431 return ptr;
429432 }
430433
431 const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice)));
434 const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(old_mem)));
432435 const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return error.OutOfMemory;
433436 // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure
434437 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(
451454pub fn free(self: Allocator, memory: anytype) void {
452455 const slice_info = @typeInfo(@TypeOf(memory)).pointer;
453456 if (slice_info.size != .slice) {
454 const slice: Slice(@TypeOf(memory)) = memory; // coerce *[len]T to []T
455 return free(self, slice);
457 assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array);
456458 }
457 comptime assert(slice_info.size == .slice);
458459 const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory)));
459460 if (bytes.len == 0) return;
460461 @memset(bytes, undefined);
......@@ -600,6 +601,33 @@ test failing {
600601
601602test "free single-pointer to array" {
602603 const allocator = std.testing.allocator;
603 const bytes = allocator.alloc(u32, 128) catch return error.SkipZigTest;
604 allocator.free(bytes.ptr[0..128]);
604 {
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 }
605633}
lib/std/meta.zig+22
......@@ -1091,6 +1091,28 @@ pub fn Slice(comptime Pointer: type) type {
10911091 }
10921092}
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
10941112test Slice {
10951113 try testing.expectEqual([]i32, Slice(*[10]i32));
10961114}
1115
1116test AbsorbSentinel {
1117 try testing.expectEqual(*[5]u32, AbsorbSentinel(*[4:0]u32));
1118}