mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
132 lines
3.8 KiB
Plaintext
132 lines
3.8 KiB
Plaintext
<*
|
|
Ref provides a general *external* ref counted wrapper for a pointer. For convenience, a ref count of 0
|
|
means the reference is still valid.
|
|
|
|
When the rc drops to -1, it will first run the dealloc function on the underlying pointer (if it exists),
|
|
then free the pointer and the atomic variable assuming that they are allocated using the Allocator in the Ref.
|
|
|
|
@require !$defined(Type.dealloc) ||| $defined(Type.dealloc(&&(Type){})) : "'dealloc' must only take a pointer to the underlying type"
|
|
@require !$defined(Type.dealloc) ||| $typeof((Type){}.dealloc()) == void : "'dealloc' must return 'void'"
|
|
*>
|
|
module std::core::mem::ref <Type>;
|
|
import std::thread, std::atomic;
|
|
|
|
const OVERALIGNED @private = Type.alignof > mem::DEFAULT_MEM_ALIGNMENT;
|
|
|
|
alias DeallocFn = fn void(void*);
|
|
|
|
fn Ref wrap(Type* ptr, Allocator allocator = mem)
|
|
{
|
|
return { .refcount = allocator::new(allocator, Atomic{int}), .ptr = ptr, .allocator = allocator };
|
|
}
|
|
<*
|
|
@require $vacount < 2 : "Too many arguments."
|
|
@require $vacount == 0 ||| $defined(Type a = $vaexpr[0]) : "The first argument must be an initializer for the type"
|
|
*>
|
|
macro Ref new(..., Allocator allocator = mem)
|
|
{
|
|
|
|
$switch:
|
|
$case OVERALIGNED && !$vacount:
|
|
Type* ptr = allocator::calloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
|
|
$case OVERALIGNED:
|
|
Type* ptr = allocator::malloc_aligned(allocator, Type.sizeof, Type.alignof)!!;
|
|
*ptr = $vaexpr[0];
|
|
$case !$vacount:
|
|
Type* ptr = allocator::calloc(allocator, Type.sizeof);
|
|
$default:
|
|
Type* ptr = allocator::malloc(allocator, Type.sizeof);
|
|
*ptr = $vaexpr[0];
|
|
$endswitch
|
|
return { .refcount = allocator::new(allocator, Atomic{int}),
|
|
.ptr = ptr,
|
|
.allocator = allocator };
|
|
}
|
|
|
|
struct Ref
|
|
{
|
|
Atomic{int}* refcount;
|
|
Type* ptr;
|
|
Allocator allocator;
|
|
}
|
|
|
|
fn Ref* Ref.retain(&self)
|
|
{
|
|
assert(self.refcount != null, "Reference already released");
|
|
assert(self.refcount.load(RELAXED) >= 0, "Retaining zombie");
|
|
self.refcount.add(1, RELAXED);
|
|
return self;
|
|
}
|
|
|
|
fn void Ref.release(&self)
|
|
{
|
|
assert(self.refcount != null, "Reference already released");
|
|
assert(self.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
|
|
if (self.refcount.sub(1, RELAXED) == 0)
|
|
{
|
|
thread::fence(ACQUIRE);
|
|
$if $defined(Type.dealloc):
|
|
self.ptr.dealloc();
|
|
$endif
|
|
$if OVERALIGNED:
|
|
allocator::free_aligned(self.allocator, self.ptr);
|
|
$else
|
|
allocator::free(self.allocator, self.ptr);
|
|
$endif
|
|
allocator::free(self.allocator, self.refcount);
|
|
*self = {};
|
|
}
|
|
}
|
|
|
|
module std::core::mem::rc;
|
|
import std::thread, std::atomic;
|
|
|
|
<*
|
|
A RefCounted struct should be an inline base of a struct.
|
|
If a `dealloc` is defined, then it will be called rather than `free`
|
|
|
|
For convenience, a ref count of 0 is still valid, and the struct is
|
|
only freed when when ref count drops to -1.
|
|
|
|
The macros rc::retain and rc::release must be used on the full pointer,
|
|
not on the RefCounted substruct.
|
|
|
|
So `Foo* f = ...; RefCounted* rc = f; rc::release(rc);` will not do the right thing.
|
|
*>
|
|
struct RefCounted
|
|
{
|
|
Atomic{int} refcount;
|
|
}
|
|
|
|
<*
|
|
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
|
*>
|
|
macro retain(refcounted)
|
|
{
|
|
if (refcounted)
|
|
{
|
|
assert(refcounted.refcount.load(RELAXED) >= 0, "Retaining zombie");
|
|
refcounted.refcount.add(1, RELAXED);
|
|
}
|
|
return refcounted;
|
|
}
|
|
|
|
<*
|
|
@require $defined(RefCounted* c = refcounted) : "Expected a ref counted value"
|
|
@require !$defined(refcounted.dealloc()) ||| $typeof(refcounted.dealloc()) == void
|
|
: "Expected refcounted type to have a valid dealloc"
|
|
*>
|
|
macro void release(refcounted)
|
|
{
|
|
if (!refcounted) return;
|
|
assert(refcounted.refcount.load(RELAXED) >= 0, "Overrelease of refcount");
|
|
if (refcounted.refcount.sub(1, RELAXED) == 0)
|
|
{
|
|
thread::fence(ACQUIRE);
|
|
$if $defined(refcounted.dealloc):
|
|
refcounted.dealloc();
|
|
$else
|
|
free(refcounted);
|
|
$endif
|
|
}
|
|
} |