diff --git a/cmake/sources/ZigSources.txt b/cmake/sources/ZigSources.txt index 8f2f834bd1..dfa050738a 100644 --- a/cmake/sources/ZigSources.txt +++ b/cmake/sources/ZigSources.txt @@ -791,9 +791,9 @@ src/Progress.zig src/ptr.zig src/ptr/Cow.zig src/ptr/CowSlice.zig +src/ptr/meta.zig src/ptr/owned.zig src/ptr/owned/maybe.zig -src/ptr/owned/meta.zig src/ptr/ref_count.zig src/ptr/tagged_pointer.zig src/ptr/weak_ptr.zig diff --git a/src/allocators.zig b/src/allocators.zig index 7a11ac9000..9ea5cec49f 100644 --- a/src/allocators.zig +++ b/src/allocators.zig @@ -226,7 +226,6 @@ pub fn BSSList(comptime ValueType: type, comptime _count: anytype) type { } }; - const Allocator = std.mem.Allocator; const Self = @This(); allocator: Allocator, @@ -312,7 +311,6 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type return struct { pub const Overflow = OverflowList([]const u8, count / 4); - const Allocator = std.mem.Allocator; const Self = @This(); backing_buf: [count * item_length]u8, @@ -496,7 +494,6 @@ pub fn BSSStringList(comptime _count: usize, comptime _item_length: usize) type pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_keys: bool, comptime estimated_key_length: usize, comptime remove_trailing_slashes: bool) type { const max_index = count - 1; const BSSMapType = struct { - const Allocator = std.mem.Allocator; const Self = @This(); const Overflow = OverflowList(ValueType, count / 4); @@ -773,6 +770,36 @@ pub fn BSSMap(comptime ValueType: type, comptime count: anytype, comptime store_ }; } +pub fn isDefault(allocator: Allocator) bool { + return allocator.vtable == c_allocator.vtable; +} + +/// Allocate memory for a value of type `T` using the provided allocator, and initialize the memory +/// with `value`. +/// +/// If `allocator` is `bun.default_allocator`, this will internally use `bun.tryNew` to benefit from +/// the added assertions. +pub fn create(comptime T: type, allocator: Allocator, value: T) OOM!*T { + if ((comptime Environment.allow_assert) and isDefault(allocator)) { + return bun.tryNew(T, value); + } + const ptr = try allocator.create(T); + ptr.* = value; + return ptr; +} + +/// Free memory previously allocated by `create`. +/// +/// The memory must have been allocated by the `create` function in this namespace, not +/// directly by `allocator.create`. +pub fn destroy(allocator: Allocator, ptr: anytype) void { + if ((comptime Environment.allow_assert) and isDefault(allocator)) { + bun.destroy(ptr); + } else { + allocator.destroy(ptr); + } +} + const basic = if (bun.use_mimalloc) @import("./allocators/basic.zig") else @@ -780,6 +807,7 @@ else const Environment = @import("./env.zig"); const std = @import("std"); +const Allocator = std.mem.Allocator; const bun = @import("bun"); const OOM = bun.OOM; diff --git a/src/allocators/basic.zig b/src/allocators/basic.zig index 980ddf8898..3c313b8a41 100644 --- a/src/allocators/basic.zig +++ b/src/allocators/basic.zig @@ -3,7 +3,7 @@ const log = bun.Output.scoped(.mimalloc, .hidden); fn mimalloc_free( _: *anyopaque, buf: []u8, - alignment: mem.Alignment, + alignment: Alignment, _: usize, ) void { if (comptime Environment.enable_logs) @@ -23,7 +23,7 @@ fn mimalloc_free( } const MimallocAllocator = struct { - fn alignedAlloc(len: usize, alignment: mem.Alignment) ?[*]u8 { + fn alignedAlloc(len: usize, alignment: Alignment) ?[*]u8 { if (comptime Environment.enable_logs) log("mi_alloc({d}, {d})", .{ len, alignment.toByteUnits() }); @@ -48,15 +48,15 @@ const MimallocAllocator = struct { return mimalloc.mi_malloc_size(ptr); } - fn alloc_with_default_allocator(_: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 { + fn alloc_with_default_allocator(_: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 { return alignedAlloc(len, alignment); } - fn resize_with_default_allocator(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool { + fn resize_with_default_allocator(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool { return mimalloc.mi_expand(buf.ptr, new_len) != null; } - fn remap_with_default_allocator(_: *anyopaque, buf: []u8, alignment: mem.Alignment, new_len: usize, _: usize) ?[*]u8 { + fn remap_with_default_allocator(_: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 { return @ptrCast(mimalloc.mi_realloc_aligned(buf.ptr, new_len, alignment.toByteUnits())); } @@ -76,7 +76,7 @@ const c_allocator_vtable = &Allocator.VTable{ }; const ZAllocator = struct { - fn alignedAlloc(len: usize, alignment: mem.Alignment) ?[*]u8 { + fn alignedAlloc(len: usize, alignment: Alignment) ?[*]u8 { log("ZAllocator.alignedAlloc: {d}\n", .{len}); const ptr = if (mimalloc.mustUseAlignedAlloc(alignment)) @@ -100,11 +100,11 @@ const ZAllocator = struct { return mimalloc.mi_malloc_size(ptr); } - fn alloc_with_z_allocator(_: *anyopaque, len: usize, alignment: mem.Alignment, _: usize) ?[*]u8 { + fn alloc_with_z_allocator(_: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 { return alignedAlloc(len, alignment); } - fn resize_with_z_allocator(_: *anyopaque, buf: []u8, _: mem.Alignment, new_len: usize, _: usize) bool { + fn resize_with_z_allocator(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool { if (new_len <= buf.len) { return true; } @@ -135,7 +135,7 @@ pub const z_allocator = Allocator{ const z_allocator_vtable = Allocator.VTable{ .alloc = &ZAllocator.alloc_with_z_allocator, .resize = &ZAllocator.resize_with_z_allocator, - .remap = &std.mem.Allocator.noRemap, + .remap = &Allocator.noRemap, .free = &ZAllocator.free_with_z_allocator, }; @@ -150,5 +150,5 @@ const std = @import("std"); const bun = @import("bun"); const mimalloc = bun.mimalloc; -const mem = @import("std").mem; -const Allocator = mem.Allocator; +const Alignment = std.mem.Alignment; +const Allocator = std.mem.Allocator; diff --git a/src/bun.zig b/src/bun.zig index 2f063d380c..75f32dd43c 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -2642,19 +2642,23 @@ pub const heap_breakdown = @import("./heap_breakdown.zig"); /// /// On macOS, you can use `Bun.unsafe.mimallocDump()` to dump the heap. pub inline fn new(comptime T: type, init: T) *T { + return handleOom(tryNew(T, init)); +} + +/// Error-returning version of `new`. +pub inline fn tryNew(comptime T: type, init: T) OOM!*T { const pointer = if (heap_breakdown.enabled) - heap_breakdown.getZoneT(T).create(T, init) + try heap_breakdown.getZoneT(T).tryCreate(T, init) else pointer: { - const pointer = default_allocator.create(T) catch outOfMemory(); + const pointer = try default_allocator.create(T); pointer.* = init; break :pointer pointer; }; if (comptime Environment.allow_assert) { - const logAlloc = Output.scoped(.alloc, .visibleIf(@hasDecl(T, "logAllocations"))); + const logAlloc = Output.scoped(.alloc, .visibleIf(meta.hasDecl(T, "log_allocations"))); logAlloc("new({s}) = {*}", .{ meta.typeName(T), pointer }); } - return pointer; } @@ -2668,16 +2672,14 @@ pub inline fn destroy(pointer: anytype) void { const T = std.meta.Child(@TypeOf(pointer)); if (Environment.allow_assert) { - const logAlloc = Output.scoped(.alloc, .visibleIf(@hasDecl(T, "logAllocations"))); + const logAlloc = Output.scoped(.alloc, .visibleIf(meta.hasDecl(T, "log_allocations"))); logAlloc("destroy({s}) = {*}", .{ meta.typeName(T), pointer }); // If this type implements a RefCount, make sure it is zero. ptr.ref_count.maybeAssertNoRefs(T, pointer); - switch (@typeInfo(T)) { - .@"struct", .@"union", .@"enum" => if (@hasDecl(T, "assertBeforeDestroy")) - pointer.assertBeforeDestroy(), - else => {}, + if (comptime std.meta.hasFn(T, "assertBeforeDestroy")) { + pointer.assertBeforeDestroy(); } } @@ -3008,7 +3010,7 @@ noinline fn assertionFailure() noreturn { noinline fn assertionFailureAtLocation(src: std.builtin.SourceLocation) noreturn { if (@inComptime()) { - @compileError(std.fmt.comptimePrint("assertion failure")); + @compileError(std.fmt.comptimePrint("assertion failure", .{})); } else { @branchHint(.cold); Output.panic(assertion_failure_msg ++ " at {s}:{d}:{d}", .{ src.file, src.line, src.column }); @@ -3126,17 +3128,12 @@ pub fn assertWithLocation(value: bool, src: std.builtin.SourceLocation) callconv /// This has no effect on the real code but capturing 'a' and 'b' into /// parameters makes assertion failures much easier inspect in a debugger. pub inline fn assert_eql(a: anytype, b: anytype) void { + if (a == b) return; if (@inComptime()) { - if (a != b) { - @compileLog(a); - @compileLog(b); - @compileError("A != B"); - } + @compileError(std.fmt.comptimePrint("Assertion failure: {any} != {any}", .{ a, b })); } if (!Environment.allow_assert) return; - if (a != b) { - Output.panic("Assertion failure: {any} != {any}", .{ a, b }); - } + Output.panic("Assertion failure: {any} != {any}", .{ a, b }); } /// This has no effect on the real code but capturing 'a' and 'b' into diff --git a/src/heap_breakdown.zig b/src/heap_breakdown.zig index 929cf1a713..8948a42ece 100644 --- a/src/heap_breakdown.zig +++ b/src/heap_breakdown.zig @@ -3,7 +3,7 @@ const vm_size_t = usize; pub const enabled = Environment.allow_assert and Environment.isMac; fn heapLabel(comptime T: type) [:0]const u8 { - const base_name = if (@hasDecl(T, "heap_label")) + const base_name = if (comptime bun.meta.hasDecl(T, "heap_label")) T.heap_label else bun.meta.typeBaseName(@typeName(T)); @@ -95,6 +95,11 @@ pub const Zone = opaque { /// Create a single-item pointer with initialized data. pub inline fn create(zone: *Zone, comptime T: type, data: T) *T { + return bun.handleOom(zone.tryCreate(T, data)); + } + + /// Error-returning version of `create`. + pub inline fn tryCreate(zone: *Zone, comptime T: type, data: T) !*T { const alignment: std.mem.Alignment = .fromByteUnits(@alignOf(T)); const ptr: *T = @alignCast(@ptrCast( rawAlloc(zone, @sizeOf(T), alignment, @returnAddress()) orelse bun.outOfMemory(), diff --git a/src/meta.zig b/src/meta.zig index 9501ebe772..5e9686b496 100644 --- a/src/meta.zig +++ b/src/meta.zig @@ -357,5 +357,19 @@ pub fn voidFieldTypeDiscardHelper(data: anytype) void { _ = data; } +pub fn hasDecl(comptime T: type, comptime name: []const u8) bool { + return switch (@typeInfo(T)) { + .@"struct", .@"union", .@"enum", .@"opaque" => @hasDecl(T, name), + else => false, + }; +} + +pub fn hasField(comptime T: type, comptime name: []const u8) bool { + return switch (@typeInfo(T)) { + .@"struct", .@"union", .@"enum" => @hasField(T, name), + else => false, + }; +} + const bun = @import("bun"); const std = @import("std"); diff --git a/src/ptr.zig b/src/ptr.zig index 222d684f24..709b9d1b0c 100644 --- a/src/ptr.zig +++ b/src/ptr.zig @@ -6,10 +6,9 @@ pub const CowSliceZ = @import("./ptr/CowSlice.zig").CowSliceZ; pub const CowString = CowSlice(u8); pub const owned = @import("./ptr/owned.zig"); -pub const Owned = owned.Owned; -pub const OwnedWithOpts = owned.OwnedWithOpts; -pub const MaybeOwned = owned.MaybeOwned; -pub const MaybeOwnedWithOpts = owned.MaybeOwnedWithOpts; +pub const Owned = owned.Owned; // owned pointer allocated with default allocator +pub const DynamicOwned = owned.Dynamic; // owned pointer allocated with any allocator +pub const MaybeOwned = owned.maybe.MaybeOwned; // owned or borrowed pointer pub const ref_count = @import("./ptr/ref_count.zig"); pub const RefCount = ref_count.RefCount; diff --git a/src/ptr/owned/meta.zig b/src/ptr/meta.zig similarity index 78% rename from src/ptr/owned/meta.zig rename to src/ptr/meta.zig index 5df3971f39..7c4d395161 100644 --- a/src/ptr/owned/meta.zig +++ b/src/ptr/meta.zig @@ -1,4 +1,4 @@ -//! Private utilities used in the implementation of `Owned` and `MaybeOwned`. +//! Private utilities used in smart pointer implementations. pub const PointerInfo = struct { const Self = @This(); @@ -35,7 +35,12 @@ pub const PointerInfo = struct { return @typeInfo(self.NonOptionalPointer).pointer.is_const; } - pub fn parse(comptime Pointer: type) Self { + pub const ParseOptions = struct { + allow_const: bool = true, + allow_slices: bool = true, + }; + + pub fn parse(comptime Pointer: type, comptime options: ParseOptions) Self { const NonOptionalPointer = switch (@typeInfo(Pointer)) { .optional => |opt| opt.child, else => Pointer, @@ -43,17 +48,20 @@ pub const PointerInfo = struct { const pointer_info = switch (@typeInfo(NonOptionalPointer)) { .pointer => |ptr| ptr, - else => { - @compileError("type must be a (possibly optional) slice or single-item pointer"); - }, + else => @compileError("type must be a (possibly optional) pointer"), }; const Child = pointer_info.child; switch (pointer_info.size) { - .one, .slice => {}, - else => @compileError("only slices and single-item pointers are supported"), + .one => {}, + .slice => if (!options.allow_slices) @compileError("slices not supported"), + .many => @compileError("many-item pointers not supported"), + .c => @compileError("C pointers not supported"), } + if (pointer_info.is_const and !options.allow_const) { + @compileError("const pointers not supported"); + } if (pointer_info.is_volatile) { @compileError("volatile pointers not supported"); } diff --git a/src/ptr/owned.zig b/src/ptr/owned.zig index 114acfcf3e..7b350811a9 100644 --- a/src/ptr/owned.zig +++ b/src/ptr/owned.zig @@ -1,31 +1,56 @@ -/// Options for `OwnedWithOpts`. +const owned = @This(); + +/// Options for `WithOptions`. pub const Options = struct { // Whether to call `deinit` on the data before freeing it, if such a method exists. deinit: bool = true, + + // If non-null, the owned pointer will always use the provided allocator. This makes it the + // same size as a raw pointer, as it no longer has to store the allocator at runtime, but it + // means it will be a different type from owned pointers that use different allocators. + allocator: ?Allocator = bun.default_allocator, + + fn asDynamic(self: Options) Options { + var new = self; + new.allocator = null; + return new; + } }; -/// An owned pointer or slice. +/// An owned pointer or slice that was allocated using the default allocator. /// -/// This type is a wrapper around a pointer or slice of type `Pointer`, and the allocator that was -/// used to allocate the memory. Calling `deinit` on this type first calls `deinit` on the -/// underlying data, and then frees the memory. +/// This type is a wrapper around a pointer or slice of type `Pointer` that was allocated using +/// `bun.default_allocator`. Calling `deinit` on this type first calls `deinit` on the underlying +/// data, and then frees the memory. /// /// `Pointer` can be a single-item pointer, a slice, or an optional version of either of those; /// e.g., `Owned(*u8)`, `Owned([]u8)`, `Owned(?*u8)`, or `Owned(?[]u8)`. /// /// Use the `alloc*` functions to create an `Owned(Pointer)` by allocating memory, or use -/// `fromRawOwned` to create one from a raw pointer and allocator. Use `get` to access the inner -/// pointer, and call `deinit` to free the memory. If `Pointer` is optional, use `initNull` to -/// create a null `Owned(Pointer)`. +/// `fromRawOwned` to create one from a raw pointer. Use `get` to access the inner pointer, and +/// call `deinit` to free the memory. If `Pointer` is optional, use `initNull` to create a null +/// `Owned(Pointer)`. +/// +/// See `Dynamic` for a version that supports any allocator. You can also specify a different +/// fixed allocator using `WithOptions(Pointer, .{ .allocator = some_other_allocator })`. pub fn Owned(comptime Pointer: type) type { - return OwnedWithOpts(Pointer, .{}); + return WithOptions(Pointer, .{}); +} + +/// An owned pointer or slice allocated using any allocator. +/// +/// This type is like `Owned`, but it supports data allocated by any allocator. To do this, it +/// stores the allocator at runtime, which increases the size of the type. An unmanaged version +/// which doesn't store the allocator is available with `Dynamic(Pointer).Unmanaged`. +pub fn Dynamic(comptime Pointer: type) type { + return WithOptions(Pointer, .{ .allocator = null }); } /// Like `Owned`, but takes explicit options. /// -/// `Owned(Pointer)` is simply an alias of `OwnedWithOpts(Pointer, .{})`. -pub fn OwnedWithOpts(comptime Pointer: type, comptime options: Options) type { - const info = PointerInfo.parse(Pointer); +/// `Owned(Pointer)` is simply an alias of `WithOptions(Pointer, .{})`. +pub fn WithOptions(comptime Pointer: type, comptime options: Options) type { + const info = PointerInfo.parse(Pointer, .{}); const NonOptionalPointer = info.NonOptionalPointer; const Child = info.Child; @@ -33,55 +58,100 @@ pub fn OwnedWithOpts(comptime Pointer: type, comptime options: Options) type { const Self = @This(); unsafe_raw_pointer: Pointer, - unsafe_allocator: Allocator, + unsafe_allocator: if (options.allocator == null) Allocator else void, - pub const Unmanaged = OwnedUnmanaged(Pointer, options); + /// An unmanaged version of this owned pointer. This type doesn't store the allocator and + /// is the same size as a raw pointer. + /// + /// This type is provided only if `options.allocator` is null, since if it's non-null, + /// the owned pointer is already the size of a raw pointer. + pub const Unmanaged = if (options.allocator == null) owned.Unmanaged(Pointer, options); - pub const alloc = switch (info.kind()) { + /// Allocate a new owned pointer. The signature of this function depends on whether the + /// pointer is a single-item pointer or a slice, and whether a fixed allocator was provided + /// in `options`. + pub const alloc = (if (options.allocator) |allocator| switch (info.kind()) { + .single => struct { + /// Allocate memory for a single value using `options.allocator`, and initialize it + /// with `value`. + pub fn alloc(value: Child) Allocator.Error!Self { + return .allocSingle(allocator, value); + } + }, + .slice => struct { + /// Allocate memory for `count` elements using `options.allocator`, and initialize + /// every element with `elem`. + pub fn alloc(count: usize, elem: Child) Allocator.Error!Self { + return .allocSlice(allocator, count, elem); + } + }, + } else switch (info.kind()) { .single => struct { /// Allocate memory for a single value and initialize it with `value`. pub fn alloc(allocator: Allocator, value: Child) Allocator.Error!Self { - const data = try allocator.create(Child); - data.* = value; - return .{ - .unsafe_raw_pointer = data, - .unsafe_allocator = allocator, - }; + return .allocSingle(allocator, value); } }, .slice => struct { /// Allocate memory for `count` elements, and initialize every element with `elem`. pub fn alloc(allocator: Allocator, count: usize, elem: Child) Allocator.Error!Self { - const data = try allocator.alloc(Child, count); - @memset(data, elem); - return .{ - .unsafe_raw_pointer = data, - .unsafe_allocator = allocator, - }; + return .allocSlice(allocator, count, elem); } }, - }.alloc; + }).alloc; - /// Create an `Owned(Pointer)` by allocating memory and performing a shallow copy of `data`. - pub fn allocDupe(data: NonOptionalPointer, allocator: Allocator) Allocator.Error!Self { - return switch (comptime info.kind()) { - .single => .alloc(allocator, data.*), - .slice => .fromRawOwned(try allocator.dupe(Child, data), allocator), - }; - } + const supports_default_allocator = if (options.allocator) |allocator| + bun.allocators.isDefault(allocator) + else + true; - /// Create an `Owned(Pointer)` from a raw pointer and allocator. - /// - /// Requirements: - /// - /// * `data` must have been allocated by `allocator`. - /// * `data` must not be freed for the life of the `Owned(Pointer)`. - pub fn fromRawOwned(data: NonOptionalPointer, allocator: Allocator) Self { - return .{ - .unsafe_raw_pointer = data, - .unsafe_allocator = allocator, - }; - } + /// Allocate an owned pointer using the default allocator. This function calls + /// `bun.outOfMemory` if memory allocation fails. + pub const new = if (info.kind() == .single and supports_default_allocator) struct { + pub fn new(value: Child) Self { + return bun.handleOom(Self.allocSingle(bun.default_allocator, value)); + } + }.new; + + /// Create an owned pointer by allocating memory and performing a shallow copy of + /// `data`. + pub const allocDupe = (if (options.allocator) |allocator| struct { + pub fn allocDupe(data: NonOptionalPointer) Allocator.Error!Self { + return .allocDupeImpl(data, allocator); + } + } else struct { + pub fn allocDupe(data: NonOptionalPointer, allocator: Allocator) Allocator.Error!Self { + return .allocDupeImpl(data, allocator); + } + }).allocDupe; + + pub const fromRawOwned = (if (options.allocator == null) struct { + /// Create an owned pointer from a raw pointer and allocator. + /// + /// Requirements: + /// + /// * `data` must have been allocated by `allocator`. + /// * `data` must not be freed for the life of the owned pointer. + pub fn fromRawOwned(data: NonOptionalPointer, allocator: Allocator) Self { + return .{ + .unsafe_raw_pointer = data, + .unsafe_allocator = allocator, + }; + } + } else struct { + /// Create an owned pointer from a raw pointer. + /// + /// Requirements: + /// + /// * `data` must have been allocated by `options.allocator`. + /// * `data` must not be freed for the life of the owned pointer. + pub fn fromRawOwned(data: NonOptionalPointer) Self { + return .{ + .unsafe_raw_pointer = data, + .unsafe_allocator = {}, + }; + } + }).fromRawOwned; /// Deinitialize the pointer or slice, freeing its memory. /// @@ -100,13 +170,15 @@ pub fn OwnedWithOpts(comptime Pointer: type, comptime options: Options) type { } } switch (comptime info.kind()) { - .single => self.unsafe_allocator.destroy(data), - .slice => self.unsafe_allocator.free(data), + .single => bun.allocators.destroy(self.getAllocator(), data), + .slice => self.getAllocator().free(data), } } + const SelfOrPtr = if (info.isConst()) Self else *Self; + /// Returns the inner pointer or slice. - pub fn get(self: if (info.isConst()) Self else *Self) Pointer { + pub fn get(self: SelfOrPtr) Pointer { return self.unsafe_raw_pointer; } @@ -119,25 +191,25 @@ pub fn OwnedWithOpts(comptime Pointer: type, comptime options: Options) type { } }.getConst; - /// Converts an `Owned(Pointer)` into its constituent parts, a raw pointer and an allocator. + /// Converts an owned pointer into a raw pointer. If `options.allocator` is non-null, + /// this method also returns the allocator. /// - /// Do not use `self` or call `deinit` after calling this method. - pub const intoRawOwned = switch (info.isOptional()) { - // Regular, non-optional pointer (e.g., `*u8`, `[]u8`). - false => struct { - pub fn intoRawOwned(self: Self) struct { Pointer, Allocator } { - return .{ self.unsafe_raw_pointer, self.unsafe_allocator }; - } - }, - // Optional pointer (e.g., `?*u8`, `?[]u8`). - true => struct { - pub fn intoRawOwned(self: Self) ?struct { NonOptionalPointer, Allocator } { - return .{ self.unsafe_raw_pointer orelse return null, self.unsafe_allocator }; - } - }, - }.intoRawOwned; + /// This method invalidates `self`. + pub const intoRawOwned = (if (options.allocator != null) struct { + pub fn intoRawOwned(self: Self) Pointer { + return self.unsafe_raw_pointer; + } + } else if (info.isOptional()) struct { + pub fn intoRawOwned(self: Self) struct { Pointer, Allocator } { + return .{ self.unsafe_raw_pointer, self.unsafe_allocator }; + } + } else struct { + pub fn intoRawOwned(self: Self) ?struct { NonOptionalPointer, Allocator } { + return .{ self.unsafe_raw_pointer orelse return null, self.unsafe_allocator }; + } + }).intoRawOwned; - /// Return a null `Owned(Pointer)`. This method is provided only if `Pointer` is an + /// Return a null owned pointer. This function is provided only if `Pointer` is an /// optional type. /// /// It is permitted, but not required, to call `deinit` on the returned value. @@ -150,43 +222,120 @@ pub fn OwnedWithOpts(comptime Pointer: type, comptime options: Options) type { } }.initNull; - /// If this pointer is non-null, convert it to an `Owned(NonOptionalPointer)`, and set - /// this pointer to null. Otherwise, do nothing and return null. + const OwnedNonOptional = WithOptions(NonOptionalPointer, options); + + /// Convert an `Owned(?T)` into an `?Owned(T)`. + /// + /// This method sets `self` to null. It is therefore permitted, but not required, to call + /// `deinit` on `self`. /// /// This method is provided only if `Pointer` is an optional type. - /// - /// It is permitted, but not required, to call deinit on `self` after calling this method. pub const take = if (info.isOptional()) struct { - pub fn take(self: *Self) ?Owned(NonOptionalPointer) { - const data = self.unsafe_raw_pointer orelse return null; - const allocator = self.unsafe_allocator; - self.* = .initNull(); - return .fromRawOwned(data, allocator); + pub fn take(self: *Self) ?OwnedNonOptional { + defer self.* = .initNull(); + return .{ + .unsafe_raw_pointer = self.unsafe_raw_pointer orelse return null, + .unsafe_allocator = self.unsafe_allocator, + }; } }.take; + const OwnedOptional = WithOptions(?Pointer, options); + + /// Convert an `Owned(T)` into a non-null `Owned(?T)`. + /// + /// This method invalidates `self`. + pub const intoOptional = if (!info.isOptional()) struct { + pub fn intoOptional(self: Self) OwnedOptional { + return .{ + .unsafe_raw_pointer = self.unsafe_raw_pointer, + .unsafe_allocator = self.unsafe_allocator, + }; + } + }.intoOptional; + /// Convert this owned pointer into an unmanaged variant that doesn't store the allocator. - pub fn toUnmanaged(self: Self) Unmanaged { + /// + /// This method invalidates `self`. + /// + /// This method is provided only if `options.allocator` is null, since if it's non-null, + /// this type is already the size of a raw pointer. + pub const toUnmanaged = if (options.allocator == null) struct { + pub fn toUnmanaged(self: Self) Self.Unmanaged { + return .{ + .unsafe_raw_pointer = self.unsafe_raw_pointer, + }; + } + }.toUnmanaged; + + const DynamicOwned = WithOptions(Pointer, options.asDynamic()); + + /// Convert an owned pointer that uses a fixed allocator into a dynamic one. + /// + /// This method invalidates `self`. + /// + /// This method is provided only if `options.allocator` is non-null, and returns + /// a new owned pointer that has `options.allocator` set to null. + pub const toDynamic = if (options.allocator) |allocator| struct { + pub fn toDynamic(self: Self) DynamicOwned { + return .{ + .unsafe_raw_pointer = self.unsafe_raw_pointer, + .unsafe_allocator = allocator, + }; + } + }.toDynamic; + + fn rawInit(data: NonOptionalPointer, allocator: Allocator) Self { return .{ - .unsafe_raw_pointer = self.unsafe_raw_pointer, + .unsafe_raw_pointer = data, + .unsafe_allocator = if (comptime options.allocator == null) allocator, }; } + + fn allocSingle(allocator: Allocator, value: Child) !Self { + const data = try bun.allocators.create(Child, allocator, value); + return .rawInit(data, allocator); + } + + fn allocSlice(allocator: Allocator, count: usize, elem: Child) !Self { + const data = try allocator.alloc(Child, count); + @memset(data, elem); + return .rawInit(data, allocator); + } + + fn allocDupeImpl(data: NonOptionalPointer, allocator: Allocator) !Self { + return switch (comptime info.kind()) { + .single => .allocSingle(allocator, data.*), + .slice => .rawInit(try allocator.dupe(Child, data), allocator), + }; + } + + fn getAllocator(self: Self) Allocator { + return (comptime options.allocator) orelse self.unsafe_allocator; + } }; } -/// An unmanaged version of `Owned(Pointer)` that doesn't store the allocator. -pub fn OwnedUnmanaged(comptime Pointer: type, comptime options: Options) type { - const info = PointerInfo.parse(Pointer); +/// An unmanaged version of `Dynamic(Pointer)` that doesn't store the allocator. +fn Unmanaged(comptime Pointer: type, comptime options: Options) type { + const info = PointerInfo.parse(Pointer, .{}); + bun.assertf( + options.allocator == null, + "owned.Unmanaged is useless if options.allocator is provided", + .{}, + ); return struct { const Self = @This(); unsafe_raw_pointer: Pointer, + const Managed = WithOptions(Pointer, options); + /// Convert this unmanaged owned pointer back into a managed version. /// /// `allocator` must be the allocator that was used to allocate the pointer. - pub fn toManaged(self: Self, allocator: Allocator) OwnedWithOpts(Pointer, options) { + pub fn toManaged(self: Self, allocator: Allocator) Managed { const data = if (comptime info.isOptional()) self.unsafe_raw_pointer orelse return .initNull() else @@ -201,8 +350,10 @@ pub fn OwnedUnmanaged(comptime Pointer: type, comptime options: Options) type { self.toManaged(allocator).deinit(); } + const SelfOrPtr = if (info.isConst()) Self else *Self; + /// Returns the inner pointer or slice. - pub fn get(self: if (info.isConst()) Self else *Self) Pointer { + pub fn get(self: SelfOrPtr) Pointer { return self.unsafe_raw_pointer; } @@ -217,12 +368,12 @@ pub fn OwnedUnmanaged(comptime Pointer: type, comptime options: Options) type { }; } -pub const MaybeOwned = @import("./owned/maybe.zig").MaybeOwned; -pub const MaybeOwnedWithOpts = @import("./owned/maybe.zig").MaybeOwned; +pub const maybe = @import("./owned/maybe.zig"); +const bun = @import("bun"); const std = @import("std"); const Allocator = std.mem.Allocator; -const meta = @import("./owned/meta.zig"); +const meta = @import("./meta.zig"); const AddConst = meta.AddConst; const PointerInfo = meta.PointerInfo; diff --git a/src/ptr/owned/maybe.zig b/src/ptr/owned/maybe.zig index dabd036684..614249d1c5 100644 --- a/src/ptr/owned/maybe.zig +++ b/src/ptr/owned/maybe.zig @@ -1,3 +1,16 @@ +/// Options for `WithOptions`. +pub const Options = struct { + // Whether to call `deinit` on the data before freeing it, if such a method exists. + deinit: bool = true, + + fn toOwned(self: Options) owned.Options { + return .{ + .deinit = self.deinit, + .allocator = null, + }; + } +}; + /// A possibly owned pointer or slice. /// /// Memory held by this type is either owned or borrowed. If owned, this type also holds the @@ -12,14 +25,14 @@ /// `deinit`, even if the data is borrowed. It's a no-op in that case but doing so will help prevent /// leaks.) If `Pointer` is optional, use `initNull` to create a null `MaybeOwned(Pointer)`. pub fn MaybeOwned(comptime Pointer: type) type { - return MaybeOwnedWithOpts(Pointer, .{}); + return WithOptions(Pointer, .{}); } /// Like `MaybeOwned`, but takes explicit options. /// -/// `MaybeOwned(Pointer)` is simply an alias of `MaybeOwnedWithOpts(Pointer, .{})`. -pub fn MaybeOwnedWithOpts(comptime Pointer: type, comptime options: Options) type { - const info = PointerInfo.parse(Pointer); +/// `MaybeOwned(Pointer)` is simply an alias of `WithOptions(Pointer, .{})`. +pub fn WithOptions(comptime Pointer: type, comptime options: Options) type { + const info = PointerInfo.parse(Pointer, .{}); const NonOptionalPointer = info.NonOptionalPointer; return struct { @@ -28,14 +41,16 @@ pub fn MaybeOwnedWithOpts(comptime Pointer: type, comptime options: Options) typ unsafe_raw_pointer: Pointer, unsafe_allocator: NullableAllocator, + const Owned = owned.WithOptions(Pointer, options.toOwned()); + /// Create a `MaybeOwned(Pointer)` from an `Owned(Pointer)`. /// - /// Don't use `owned` (including calling `deinit`) after calling this method. - pub fn fromOwned(owned: OwnedWithOpts(Pointer, options)) Self { + /// This method invalidates `owned`. + pub fn fromOwned(owned_ptr: Owned) Self { const data, const allocator = if (comptime info.isOptional()) - owned.intoRawOwned() orelse return .initNull() + owned_ptr.intoRawOwned() orelse return .initNull() else - owned.intoRawOwned(); + owned_ptr.intoRawOwned(); return .{ .unsafe_raw_pointer = data, .unsafe_allocator = .init(allocator), @@ -53,6 +68,8 @@ pub fn MaybeOwnedWithOpts(comptime Pointer: type, comptime options: Options) typ } /// Create a `MaybeOwned(Pointer)` from borrowed slice or pointer. + /// + /// `data` must not be freed for the life of the `MaybeOwned`. pub fn fromBorrowed(data: NonOptionalPointer) Self { return .{ .unsafe_raw_pointer = data, @@ -70,12 +87,14 @@ pub fn MaybeOwnedWithOpts(comptime Pointer: type, comptime options: Options) typ else self.intoRaw(); if (maybe_allocator) |allocator| { - OwnedWithOpts(Pointer, options).fromRawOwned(data, allocator).deinit(); + Owned.fromRawOwned(data, allocator).deinit(); } } + const SelfOrPtr = if (info.isConst()) Self else *Self; + /// Returns the inner pointer or slice. - pub fn get(self: if (info.isConst()) Self else *Self) Pointer { + pub fn get(self: SelfOrPtr) Pointer { return self.unsafe_raw_pointer; } @@ -134,10 +153,8 @@ const bun = @import("bun"); const std = @import("std"); const Allocator = std.mem.Allocator; const NullableAllocator = bun.allocators.NullableAllocator; +const owned = bun.ptr.owned; -const meta = @import("./meta.zig"); +const meta = @import("../meta.zig"); const AddConst = meta.AddConst; const PointerInfo = meta.PointerInfo; - -const Options = bun.ptr.owned.Options; -const OwnedWithOpts = bun.ptr.owned.OwnedWithOpts; diff --git a/src/ptr/ref_count.zig b/src/ptr/ref_count.zig index 9c38f5a27c..bb4112cd4f 100644 --- a/src/ptr/ref_count.zig +++ b/src/ptr/ref_count.zig @@ -547,7 +547,7 @@ fn genericDump( } pub fn maybeAssertNoRefs(T: type, ptr: *const T) void { - if (!@hasField(T, "ref_count")) return; + if (comptime !bun.meta.hasField(T, "ref_count")) return; const Rc = @FieldType(T, "ref_count"); switch (@typeInfo(Rc)) { .@"struct" => if (@hasDecl(Rc, "assertNoRefs"))