authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-02 15:55:21+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-04-02 15:55:21+02:00
log43d84420f3bca06be86397049c3b940358131f75
tree5423fcf89e712c4ec22af18586c48bf833abd336
parent557caecaaa78a1a434b72c38195b40a82fe42c74
parent4848c3a1ad2bfd956419351afb60928e290f2be1

Merge pull request 'std: add mem.absorbSentinel(), fix sentinel handling in Allocator interface' (#31611) from ifreund/zig:absorb-sent into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31611 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

2 files changed, 52 insertions(+), 5 deletions(-)

lib/std/mem.zig+48
......@@ -4709,6 +4709,54 @@ test "sliceAsBytes preserves pointer attributes" {
47094709 try testing.expectEqual(in.alignment, out.alignment);
47104710}
47114711
4712fn AbsorbSentinelReturnType(comptime Slice: type) type {
4713 const info = @typeInfo(Slice).pointer;
4714 assert(info.size == .slice);
4715 return @Pointer(.slice, .{
4716 .@"const" = info.is_const,
4717 .@"volatile" = info.is_volatile,
4718 .@"allowzero" = info.is_allowzero,
4719 .@"addrspace" = info.address_space,
4720 .@"align" = info.alignment,
4721 }, info.child, null);
4722}
4723
4724/// If the provided slice is not sentinel terminated, do nothing and return that slice.
4725/// If it is sentinel-terminated, return a non-sentinel-terminated slice with the
4726/// length increased by one to include the absorbed sentinel element.
4727pub fn absorbSentinel(slice: anytype) AbsorbSentinelReturnType(@TypeOf(slice)) {
4728 const info = @typeInfo(@TypeOf(slice)).pointer;
4729 comptime assert(info.size == .slice);
4730 if (info.sentinel_ptr == null) {
4731 return slice;
4732 } else {
4733 return slice.ptr[0 .. slice.len + 1];
4734 }
4735}
4736
4737test absorbSentinel {
4738 {
4739 var buffer: [3:0]u8 = .{ 1, 2, 3 };
4740 const foo: [:0]const u8 = &buffer;
4741 const bar: []const u8 = &buffer;
4742 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo)));
4743 try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar)));
4744 try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo));
4745 try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar));
4746 }
4747 {
4748 var buffer: [3:0]u8 = .{ 1, 2, 3 };
4749 const foo: [:0]u8 = &buffer;
4750 const bar: []u8 = &buffer;
4751 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo)));
4752 try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar)));
4753 var expected_foo = [_]u8{ 1, 2, 3, 0 };
4754 try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo));
4755 var expected_bar = [_]u8{ 1, 2, 3 };
4756 try testing.expectEqualSlices(u8, &expected_bar, absorbSentinel(bar));
4757 }
4758}
4759
47124760/// Round an address down to the next (or current) aligned address.
47134761/// Unlike `alignForward`, `alignment` can be any positive number, not just a power of 2.
47144762pub fn alignForwardAnyAlign(comptime T: type, addr: T, alignment: T) T {
lib/std/mem/Allocator.zig+4-5
......@@ -322,7 +322,7 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool {
322322 if (allocation.len == 0) {
323323 return false;
324324 }
325 const old_memory = mem.sliceAsBytes(allocation);
325 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
326326 // I would like to use saturating multiplication here, but LLVM cannot lower it
327327 // on WebAssembly: https://github.com/ziglang/zig/issues/9660
328328 //const new_len_bytes = new_len *| @sizeOf(T);
......@@ -368,7 +368,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allo
368368 new_memory.len = new_len;
369369 return new_memory;
370370 }
371 const old_memory = mem.sliceAsBytes(allocation);
371 const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation)));
372372 // I would like to use saturating multiplication here, but LLVM cannot lower it
373373 // on WebAssembly: https://github.com/ziglang/zig/issues/9660
374374 //const new_len_bytes = new_len *| @sizeOf(T);
......@@ -420,7 +420,7 @@ pub fn reallocAdvanced(
420420 return ptr;
421421 }
422422
423 const old_byte_slice = mem.sliceAsBytes(old_mem);
423 const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(old_mem)));
424424 const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return error.OutOfMemory;
425425 // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure
426426 if (self.rawRemap(old_byte_slice, .fromByteUnits(slice_info.alignment orelse @alignOf(T)), byte_count, return_address)) |p| {
......@@ -443,8 +443,7 @@ pub fn reallocAdvanced(
443443pub fn free(self: Allocator, memory: anytype) void {
444444 const slice_info = @typeInfo(@TypeOf(memory)).pointer;
445445 comptime assert(slice_info.size == .slice);
446 const mem_with_sent = memory[0 .. memory.len + @intFromBool(slice_info.sentinel() != null)];
447 const bytes: []u8 = @ptrCast(@constCast(mem_with_sent));
446 const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory)));
448447 if (bytes.len == 0) return;
449448 @memset(bytes, undefined);
450449 self.rawFree(bytes, .fromByteUnits(slice_info.alignment orelse @alignOf(slice_info.child)), @returnAddress());