Improve owned pointer types (#21908)

(For internal tracking: fixes STAB-1005, STAB-1006, STAB-1007,
STAB-1008, STAB-1009)
This commit is contained in:
taylor.fish
2025-08-15 19:05:25 -07:00
committed by GitHub
parent 599947de28
commit ecd74ac14c
11 changed files with 365 additions and 146 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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(),

View File

@@ -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");

View File

@@ -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;

View File

@@ -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");
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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"))