From b470382173c6be27f6cdd46c3adce0e59f2b3675 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Thu, 6 Aug 2026 20:35:17 -0400 Subject: [PATCH] feat(mem): allow absorbSentinel on *[n]T --- lib/std/mem.zig | 38 +++++++++++------ lib/std/mem/Allocator.zig | 90 +++++++++++++++++++++++++-------------- lib/std/meta.zig | 22 ++++++++++ 3 files changed, 107 insertions(+), 43 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 55b9019c4f4d95cfce0f271de52d12a856f9aea8..f6b2af68f914e5d31da6fc89f3702567d99fc1ac 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -9,6 +9,7 @@ const assert = debug.assert; const math = std.math; const testing = std.testing; const Endian = std.lang.Endian; +const AbsorbSentinel = std.meta.AbsorbSentinel; /// The standard library currently thoroughly depends on byte size /// being 8 bits. (see the use of u8 throughout allocation code as @@ -4738,22 +4739,28 @@ test "sliceAsBytes preserves pointer attributes" { try testing.expectEqual(in_attrs.@"align", out_attrs.@"align"); } -fn AbsorbSentinelReturnType(comptime Slice: type) type { - const info = @typeInfo(Slice).pointer; - assert(info.size == .slice); - return @Pointer(.slice, info.attrs, info.child, null); -} - /// If the provided slice is not sentinel terminated, do nothing and return that slice. /// If it is sentinel-terminated, return a non-sentinel-terminated slice with the /// length increased by one to include the absorbed sentinel element. -pub fn absorbSentinel(slice: anytype) AbsorbSentinelReturnType(@TypeOf(slice)) { +pub fn absorbSentinel(slice: anytype) AbsorbSentinel(@TypeOf(slice)) { const info = @typeInfo(@TypeOf(slice)).pointer; - comptime assert(info.size == .slice); - if (info.sentinel_ptr == null) { - return slice; - } else { - return slice.ptr[0 .. slice.len + 1]; + switch (info.size) { + .slice => { + if (info.sentinel_ptr == null) { + return slice; + } else { + return slice.ptr[0 .. slice.len + 1]; + } + }, + .one => { + const child_info = @typeInfo(info.child).array; + if (child_info.sentinel_ptr == null) { + return slice; + } else { + return slice[0 .. child_info.len + 1]; + } + }, + else => unreachable, } } @@ -4762,21 +4769,28 @@ test absorbSentinel { var buffer: [3:0]u8 = .{ 1, 2, 3 }; const foo: [:0]const u8 = &buffer; const bar: []const u8 = &buffer; + const baz: *const [3:0]u8 = &buffer; try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(foo))); try testing.expectEqual([]const u8, @TypeOf(absorbSentinel(bar))); + try testing.expectEqual(*const [4]u8, @TypeOf(absorbSentinel(baz))); try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(foo)); try testing.expectEqualSlices(u8, &.{ 1, 2, 3 }, absorbSentinel(bar)); + try testing.expectEqualSlices(u8, &.{ 1, 2, 3, 0 }, absorbSentinel(baz)); } { var buffer: [3:0]u8 = .{ 1, 2, 3 }; const foo: [:0]u8 = &buffer; const bar: []u8 = &buffer; + const baz: *[3:0]u8 = &buffer; try testing.expectEqual([]u8, @TypeOf(absorbSentinel(foo))); try testing.expectEqual([]u8, @TypeOf(absorbSentinel(bar))); + try testing.expectEqual(*[4]u8, @TypeOf(absorbSentinel(baz))); var expected_foo = [_]u8{ 1, 2, 3, 0 }; try testing.expectEqualSlices(u8, &expected_foo, absorbSentinel(foo)); var expected_bar = [_]u8{ 1, 2, 3 }; try testing.expectEqualSlices(u8, &expected_bar, absorbSentinel(bar)); + var expected_baz = [_]u8{ 1, 2, 3, 0 }; + try testing.expectEqualSlices(u8, &expected_baz, absorbSentinel(baz)); } } diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 08bfe689ff4eca5f1b3cb434757371223cfc8916..f16944bb7a934071acb8c89808f1560c2e08df9c 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -9,6 +9,7 @@ const math = std.math; const mem = std.mem; const Alignment = std.mem.Alignment; const Slice = std.meta.Slice; +const AbsorbSentinel = std.meta.AbsorbSentinel; pub const Error = error{OutOfMemory}; pub const Log2Align = math.Log2Int(usize); @@ -316,15 +317,16 @@ pub fn allocBytesAligned( /// /// `new_len` may be zero, in which case the allocation is freed. pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { - const SliceType = Slice(@TypeOf(allocation)); - const slice: SliceType = allocation; // coerce *[len]T to []T - const slice_info = @typeInfo(SliceType).pointer; - const T = slice_info.child; + const slice_info = @typeInfo(@TypeOf(allocation)).pointer; + const T = if (slice_info.size != .slice) comptime T: { + assert(slice_info.size == .one); + break :T @typeInfo(slice_info.child).array.child; + } else slice_info.child; if (new_len == 0) { - self.free(slice); + self.free(allocation); return true; } - if (slice.len == 0) { + if (allocation.len == 0) { return false; } const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation))); @@ -356,25 +358,26 @@ pub fn resize(self: Allocator, allocation: anytype, new_len: usize) bool { /// `new_len` may be zero, in which case the allocation is freed. /// /// If the allocation's elements' type is zero bytes sized, `allocation.len` is set to `new_len`. -pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeOf(allocation)) { - const SliceType = Slice(@TypeOf(allocation)); - const slice: SliceType = allocation; // coerce *[len]T to []T - const slice_info = @typeInfo(SliceType).pointer; - const T = slice_info.child; +pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(AbsorbSentinel(@TypeOf(allocation))) { + const slice_info = @typeInfo(@TypeOf(allocation)).pointer; + const T = if (slice_info.size != .slice) comptime T: { + assert(slice_info.size == .one); + break :T @typeInfo(slice_info.child).array.child; + } else slice_info.child; if (new_len == 0) { - self.free(slice); - return slice[0..0]; + self.free(allocation); + return allocation[0..0]; } - if (slice.len == 0) { + if (allocation.len == 0) { return null; } if (@sizeOf(T) == 0) { - var new_memory = slice; + var new_memory = allocation; new_memory.len = new_len; return new_memory; } - const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice))); + const old_memory: []u8 = @ptrCast(@constCast(mem.absorbSentinel(allocation))); // I would like to use saturating multiplication here, but LLVM cannot lower it // on WebAssembly: https://github.com/ziglang/zig/issues/9660 //const new_len_bytes = new_len *| @sizeOf(T); @@ -402,7 +405,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(@TypeO /// do the realloc more efficiently than the caller /// * `resize` which returns `false` when the `Allocator` implementation cannot /// change the size without relocating the allocation. -pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!Slice(@TypeOf(old_mem)) { +pub fn realloc(self: Allocator, old_mem: anytype, new_n: usize) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) { return self.reallocAdvanced(old_mem, new_n, @returnAddress()); } @@ -411,24 +414,24 @@ pub fn reallocAdvanced( old_mem: anytype, new_n: usize, return_address: usize, -) Error!Slice(@TypeOf(old_mem)) { - const SliceType = Slice(@TypeOf(old_mem)); - const slice: SliceType = old_mem; // coerce *[len]T to []T - const slice_info = @typeInfo(SliceType).pointer; - comptime assert(slice_info.size == .slice); - const T = slice_info.child; - if (slice.len == 0) { +) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) { + const slice_info = @typeInfo(@TypeOf(old_mem)).pointer; + const T = if (slice_info.size != .slice) comptime T: { + assert(slice_info.size == .one); + break :T @typeInfo(slice_info.child).array.child; + } else slice_info.child; + if (old_mem.len == 0) { return self.allocAdvancedWithRetAddr(T, .fromByteUnitsOptional(slice_info.attrs.@"align"), new_n, return_address); } if (new_n == 0) { - self.free(slice); + self.free(old_mem); const alignment = slice_info.attrs.@"align" orelse @alignOf(T); const addr = comptime std.mem.alignBackward(usize, math.maxInt(usize), alignment); const ptr: *align(alignment) [0]T = @ptrFromInt(addr); return ptr; } - const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(slice))); + const old_byte_slice: []u8 = @ptrCast(@constCast(mem.absorbSentinel(old_mem))); const byte_count = math.mul(usize, @sizeOf(T), new_n) catch return error.OutOfMemory; // Note: can't set shrunk memory to undefined as memory shouldn't be modified on realloc failure 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( pub fn free(self: Allocator, memory: anytype) void { const slice_info = @typeInfo(@TypeOf(memory)).pointer; if (slice_info.size != .slice) { - const slice: Slice(@TypeOf(memory)) = memory; // coerce *[len]T to []T - return free(self, slice); + assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array); } - comptime assert(slice_info.size == .slice); const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory))); if (bytes.len == 0) return; @memset(bytes, undefined); @@ -600,6 +601,33 @@ test failing { test "free single-pointer to array" { const allocator = std.testing.allocator; - const bytes = allocator.alloc(u32, 128) catch return error.SkipZigTest; - allocator.free(bytes.ptr[0..128]); + { + const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; + slice[127] = 0; + const ptr = slice[0..127 :0]; + allocator.free(ptr); + } + { + const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; + slice[127] = 0; + const ptr = slice[0..127 :0]; + if (allocator.resize(ptr, 16)) { + allocator.free(ptr[0..16]); + } else allocator.free(ptr); + } + { + const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; + slice[127] = 0; + const ptr = slice[0..127 :0]; + if (allocator.remap(ptr, 16)) |new| { + allocator.free(new); + } else allocator.free(ptr); + } + { + const slice = allocator.alloc(u32, 128) catch return error.SkipZigTest; + slice[127] = 0; + const ptr = slice[0..127 :0]; + const new = allocator.realloc(ptr, 16) catch return error.SkipZigTest; + allocator.free(new); + } } diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 76f0ae867f66dd158e30521bbd2c1e7e4ef53f9d..edc4a5c4e8eca6de0891b5435e644f0917a1ed28 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -1091,6 +1091,28 @@ pub fn Slice(comptime Pointer: type) type { } } +/// Given a pointer type, removes the sentinel if present, returning an +/// equivalent pointer type with no sentinel +pub fn AbsorbSentinel(comptime Pointer: type) type { + const info = @typeInfo(Pointer).pointer; + switch (info.size) { + .slice => return @Pointer(.slice, info.attrs, info.child, null), + .one => { + const child_info = @typeInfo(info.child).array; + if (child_info.sentinel_ptr == null) { + return Pointer; + } else { + return @Pointer(.one, info.attrs, [child_info.len + 1]child_info.child, null); + } + }, + else => unreachable, + } +} + test Slice { try testing.expectEqual([]i32, Slice(*[10]i32)); } + +test AbsorbSentinel { + try testing.expectEqual(*[5]u32, AbsorbSentinel(*[4:0]u32)); +} -- 2.54.0