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 9ab983f8b82c2ba60b6f5b45399b7fb8031b1336..bbd9e94c175286f3c2e7968b79851e4e48afbc17 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -8,6 +8,8 @@ const assert = std.debug.assert; 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,8 +318,10 @@ 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 slice_info = @typeInfo(@TypeOf(allocation)).pointer; - comptime assert(slice_info.size == .slice); - const T = slice_info.child; + 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(allocation); return true; @@ -351,10 +355,12 @@ 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) ?@TypeOf(allocation) { +pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?Slice(AbsorbSentinel(@TypeOf(allocation))) { const slice_info = @typeInfo(@TypeOf(allocation)).pointer; - comptime assert(slice_info.size == .slice); - const T = slice_info.child; + 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(allocation); @@ -393,7 +399,7 @@ pub fn remap(self: Allocator, allocation: anytype, new_len: usize) ?@TypeOf(allo /// 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!@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()); } @@ -402,10 +408,12 @@ pub fn reallocAdvanced( old_mem: anytype, new_n: usize, return_address: usize, -) Error!@TypeOf(old_mem) { +) Error!Slice(AbsorbSentinel(@TypeOf(old_mem))) { const slice_info = @typeInfo(@TypeOf(old_mem)).pointer; - comptime assert(slice_info.size == .slice); - const T = slice_info.child; + 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); } @@ -440,7 +448,6 @@ pub fn reallocAdvanced( pub fn free(self: Allocator, memory: anytype) void { const slice_info = @typeInfo(@TypeOf(memory)).pointer; if (slice_info.size != .slice) { - // slicing with comptime-known start and end results in *[len]T, which may be free'd comptime assert(slice_info.size == .one and @typeInfo(slice_info.child) == .array); } const bytes: []u8 = @ptrCast(@constCast(mem.absorbSentinel(memory))); @@ -585,3 +592,39 @@ test failing { try std.testing.expectError(error.OutOfMemory, f.alloc(u8, std.math.maxInt(usize))); try std.testing.expectError(error.OutOfMemory, f.allocSentinel(u8, std.math.maxInt(usize) - 1, 0)); } + +test "free single-pointer to array" { + const allocator = std.testing.allocator; + { + const allocation = try allocator.alloc(u32, 128); + allocation[127] = 0; + const ptr: *[127:0]u32 = allocation[0..127 :0]; + allocator.free(ptr); + } + { + const allocation = try allocator.alloc(u32, 128); + allocation[127] = 0; + const ptr: *[127:0]u32 = allocation[0..127 :0]; + if (allocator.resize(ptr, 16)) { + allocator.free(ptr[0..16]); + } else allocator.free(ptr); + } + { + const allocation = try allocator.alloc(u32, 128); + allocation[127] = 0; + const ptr: *[127:0]u32 = allocation[0..127 :0]; + if (allocator.remap(ptr, 16)) |new| { + allocator.free(new); + } else allocator.free(ptr); + } + { + const allocation = try allocator.alloc(u32, 128); + allocation[127] = 0; + const ptr: *[127:0]u32 = allocation[0..127 :0]; + if (allocator.realloc(ptr, 16)) |new| { + allocator.free(new); + } else |_| { + allocator.free(allocation); + } + } +} diff --git a/lib/std/meta.zig b/lib/std/meta.zig index cfeb6a758524a4e0e5a3f9e6dcac57fd029123c6..edc4a5c4e8eca6de0891b5435e644f0917a1ed28 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -1,10 +1,9 @@ const builtin = @import("builtin"); + const std = @import("std.zig"); -const debug = std.debug; +const assert = std.debug.assert; const mem = std.mem; -const math = std.math; const testing = std.testing; -const root = @import("root"); pub const TrailerFlags = @import("meta/trailer_flags.zig").TrailerFlags; @@ -821,8 +820,8 @@ pub fn isError(error_union: anytype) bool { } test isError { - try std.testing.expect(isError(math.divTrunc(u8, 5, 0))); - try std.testing.expect(!isError(math.divTrunc(u8, 5, 5))); + try std.testing.expect(isError(std.math.divTrunc(u8, 5, 0))); + try std.testing.expect(!isError(std.math.divTrunc(u8, 5, 5))); } /// Returns true if a type has a namespace and the namespace contains `name`; @@ -1070,3 +1069,50 @@ test hasUniqueRepresentation { try testing.expect(hasUniqueRepresentation(StructWithComptimeFields)); } + +/// Given a pointer type, type-erases the array length if present, returning an +/// equivalent pointer type that is always a slice. +pub fn Slice(comptime Pointer: type) type { + const info = @typeInfo(Pointer).pointer; + switch (info.size) { + .slice => return Pointer, + .one => { + const child_info = @typeInfo(info.child); + comptime assert(child_info == .array); + const sentinel_ptr: ?*const child_info.array.child = @ptrCast(@alignCast(child_info.array.sentinel_ptr)); + return @Pointer( + .slice, + info.attrs, + child_info.array.child, + if (sentinel_ptr) |ptr| ptr.* else null, + ); + }, + else => unreachable, + } +} + +/// 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)); +}