diff --git a/src/ptr.zig b/src/ptr.zig index 608b0efc50..4df6fac14c 100644 --- a/src/ptr.zig +++ b/src/ptr.zig @@ -13,6 +13,7 @@ pub const DynamicOwned = owned.Dynamic; // owned pointer allocated with any `std pub const shared = @import("./ptr/shared.zig"); pub const Shared = shared.Shared; pub const AtomicShared = shared.AtomicShared; +pub const ExternalShared = @import("./ptr/external_shared.zig").ExternalShared; pub const ref_count = @import("./ptr/ref_count.zig"); /// Deprecated; use `Shared(*T)`. @@ -22,6 +23,9 @@ pub const ThreadSafeRefCount = ref_count.ThreadSafeRefCount; /// Deprecated; use `Shared(*T)`. pub const RefPtr = ref_count.RefPtr; +pub const raw_ref_count = @import("./ptr/raw_ref_count.zig"); +pub const RawRefCount = raw_ref_count.RawRefCount; + pub const TaggedPointer = @import("./ptr/tagged_pointer.zig").TaggedPointer; pub const TaggedPointerUnion = @import("./ptr/tagged_pointer.zig").TaggedPointerUnion; diff --git a/src/ptr/external_shared.zig b/src/ptr/external_shared.zig new file mode 100644 index 0000000000..de59780b7d --- /dev/null +++ b/src/ptr/external_shared.zig @@ -0,0 +1,111 @@ +/// A shared pointer whose reference count is managed externally; e.g., by extern functions. +/// +/// `T.external_shared_descriptor` must be a struct of the following form: +/// +/// pub const external_shared_descriptor = struct { +/// pub fn ref(T*) void; +/// pub fn deref(T*) void; +/// }; +pub fn ExternalShared(comptime T: type) type { + _ = T.external_shared_descriptor.ref; // must define a `ref` function + _ = T.external_shared_descriptor.deref; // must define a `deref` function + return struct { + const Self = @This(); + + #impl: *T, + + /// `incremented_raw` should have already had its ref count incremented by 1. + pub fn adopt(incremented_raw: *T) Self { + return .{ .#impl = incremented_raw }; + } + + /// Deinitializes the shared pointer, decrementing the ref count. + pub fn deinit(self: *Self) void { + T.external_shared_descriptor.deref(self.#impl); + self.* = undefined; + } + + /// Gets the underlying pointer. This pointer may not be valid after `self` is + /// deinitialized. + pub fn get(self: Self) *T { + return self.#impl; + } + + /// Clones the shared pointer, incrementing the ref count. + pub fn clone(self: Self) Self { + T.external_shared_descriptor.ref(self.#impl); + return self; + } + + pub fn cloneFromRaw(raw: *T) Self { + T.external_shared_descriptor.ref(raw); + return .{ .#impl = raw }; + } + + /// Returns the raw pointer without decrementing the ref count. Invalidates `self`. + pub fn leak(self: *Self) *T { + defer self.* = undefined; + return self.#impl; + } + + const NonOptional = Self; + + pub const Optional = struct { + #impl: ?*T = null, + + pub fn initNull() Optional { + return .{}; + } + + /// `incremented_raw`, if non-null, should have already had its ref count incremented + /// by 1. + pub fn adopt(incremented_raw: ?*T) Optional { + return .{ .#impl = incremented_raw }; + } + + pub fn deinit(self: *Optional) void { + if (self.#impl) |impl| { + T.external_shared_descriptor.deref(impl); + } + self.* = undefined; + } + + pub fn get(self: Optional) ?*T { + return self.#impl; + } + + /// Sets `self` to null. + pub fn take(self: *Optional) ?NonOptional { + const result: NonOptional = .{ .#impl = self.#impl orelse return null }; + self.#impl = null; + return result; + } + + pub fn clone(self: Optional) Optional { + if (self.#impl) |impl| { + T.external_shared_descriptor.ref(impl); + } + return self; + } + + pub fn cloneFromRaw(raw: ?*T) Optional { + if (raw) |some_raw| { + T.external_shared_descriptor.ref(some_raw); + } + return .{ .#impl = raw }; + } + + /// Returns the raw pointer without decrementing the ref count. Invalidates `self`. + pub fn leak(self: *Optional) ?*T { + defer self.* = undefined; + return self.#impl; + } + }; + + /// Invalidates `self`. + pub fn intoOptional(self: *Self) Optional { + defer self.* = undefined; + return .{ .#impl = self.#impl }; + } + }; +} diff --git a/src/ptr/raw_ref_count.zig b/src/ptr/raw_ref_count.zig new file mode 100644 index 0000000000..7d65d98fb4 --- /dev/null +++ b/src/ptr/raw_ref_count.zig @@ -0,0 +1,74 @@ +pub const ThreadSafety = enum { + single_threaded, + thread_safe, +}; + +pub const DecrementResult = enum { + keep_alive, + should_destroy, +}; + +/// A simple wrapper around an integer reference count. This type doesn't do any memory management +/// itself. +/// +/// This type may be useful for implementing the interface required by `bun.ptr.ExternalShared`. +pub fn RawRefCount(comptime Int: type, comptime thread_safety: ThreadSafety) type { + return struct { + const Self = @This(); + + raw_value: if (thread_safety == .thread_safe) std.atomic.Value(Int) else Int, + #thread_lock: if (thread_safety == .single_threaded) bun.safety.ThreadLock else void, + + /// Usually the initial count should be 1. + pub fn init(initial_count: Int) Self { + return .{ + .raw_value = switch (comptime thread_safety) { + .single_threaded => initial_count, + .thread_safe => .init(initial_count), + }, + .#thread_lock = switch (comptime thread_safety) { + .single_threaded => .initLockedIfNonComptime(), + .thread_safe => {}, + }, + }; + } + + pub fn increment(self: *Self) void { + switch (comptime thread_safety) { + .single_threaded => { + self.#thread_lock.lockOrAssert(); + self.raw_value += 1; + }, + .thread_safe => { + const old = self.raw_value.fetchAdd(1, .monotonic); + bun.assertf( + old != std.math.maxInt(Int), + "overflow of thread-safe ref count", + .{}, + ); + }, + } + } + + pub fn decrement(self: *Self) DecrementResult { + const new_count = blk: switch (comptime thread_safety) { + .single_threaded => { + self.#thread_lock.lockOrAssert(); + self.raw_value -= 1; + break :blk self.raw_value; + }, + .thread_safe => { + const old = self.raw_value.fetchSub(1, .acq_rel); + bun.assertf(old != 0, "underflow of thread-safe ref count", .{}); + break :blk old - 1; + }, + }; + return if (new_count == 0) .should_destroy else .keep_alive; + } + + pub const deinit = void; + }; +} + +const bun = @import("bun"); +const std = @import("std"); diff --git a/src/string/WTFStringImpl.zig b/src/string/WTFStringImpl.zig index 48810d2a3e..c0afcbed5d 100644 --- a/src/string/WTFStringImpl.zig +++ b/src/string/WTFStringImpl.zig @@ -217,8 +217,16 @@ pub const WTFStringImplStruct = extern struct { pub fn hasPrefix(self: WTFStringImpl, text: []const u8) bool { return bun.cpp.Bun__WTFStringImpl__hasPrefix(self, text.ptr, text.len); } + + pub const external_shared_descriptor = struct { + pub const ref = WTFStringImplStruct.ref; + pub const deref = WTFStringImplStruct.deref; + }; }; +/// Behaves like `WTF::Ref`. +pub const WTFString = bun.ptr.ExternalShared(WTFStringImplStruct); + pub const StringImplAllocator = struct { fn alloc(ptr: *anyopaque, len: usize, _: std.mem.Alignment, _: usize) ?[*]u8 { var this = bun.cast(WTFStringImpl, ptr);