mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 04:49:06 +00:00
Improve owned pointer types (#21908)
(For internal tracking: fixes STAB-1005, STAB-1006, STAB-1007, STAB-1008, STAB-1009)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
33
src/bun.zig
33
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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
14
src/meta.zig
14
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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user