mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Add ExternalShared and RawRefCount (#23013)
Add `bun.ptr.ExternalShared`, a shared pointer whose reference count is
managed externally; e.g., by extern functions. This can be used to work
with `RefCounted` C++ objects in Zig. For example:
```cpp
// C++:
struct MyType : RefCounted<MyType> { ... };
extern "C" void MyType__ref(MyType* self) { self->ref(); }
extern "C" void MyType__ref(MyType* self) { self->deref(); }
```
```zig
// Zig:
const MyType = opaque {
extern fn MyType__ref(self: *MyType) void;
extern fn MyType__deref(self: *MyType) void;
pub const Ref = bun.ptr.ExternalShared(MyType);
// This enables `ExternalShared` to work.
pub const external_shared_descriptor = struct {
pub const ref = MyType__ref;
pub const deref = MyType__deref;
};
};
// Now `MyType.Ref` behaves just like `Ref<MyType>` in C++:
var some_ref: MyType.Ref = someFunctionReturningMyTypeRef();
const ptr: *MyType = some_ref.get(); // gets the inner pointer
var some_other_ref = some_ref.clone(); // increments the ref count
some_ref.deinit(); // decrements the ref count
// decrements the ref count again; if no other refs exist, the object
// is destroyed
some_other_ref.deinit();
```
This commit also adds `RawRefCount`, a simple wrapper around an integer
reference count that can be used to implement the interface required by
`ExternalShared`. Generally, for reference-counted Zig types,
`bun.ptr.Shared` is preferred, but occasionally it is useful to have an
“intrusive” reference-counted type where the ref count is stored in the
type itself. For this purpose, `ExternalShared` + `RawRefCount` is more
flexible and less error-prone than the deprecated `bun.ptr.RefCounted`
type.
(For internal tracking: fixes STAB-1287, STAB-1288)
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
111
src/ptr/external_shared.zig
Normal file
111
src/ptr/external_shared.zig
Normal file
@@ -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 };
|
||||
}
|
||||
};
|
||||
}
|
||||
74
src/ptr/raw_ref_count.zig
Normal file
74
src/ptr/raw_ref_count.zig
Normal file
@@ -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");
|
||||
@@ -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<WTF::StringImpl>`.
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user