Skip to content

ManagedRef

ManagedRef[T] is a single-owner, move-tracked reference to a heap-allocated T, owned by an underlying reclamation scheme.

Unlike ref T (GC-managed) or ptr T (raw, unmanaged), ManagedRef carries an explicit ownership/lifetime contract that integrates with the umbrella's SMR (nebr) and with the queue payload protocols. Construction allocates; moves transfer ownership; destruction either retires through the manager (when alive) or is a no-op (when moved out / sunk).

See also

  • ManagedSlice — managed-payload analog for contiguous arrays.
  • Chronos — managed temporal payload.
  • SMR / nebr — the reclamation backend used by the managed payload types.

managed_ref

lockfree/managed_ref

Internal slot encoding for ref T payloads under Path C (Queue[ref Foo, ...]). ManagedRef[X] is the internal type with per-MM refcount shim arms and bit-cast guarantees.

NEVER user-facing

ManagedRef[X] is the wire-format the queue stores in its slot array. The user-facing API is ref T. ManagedRef MUST NOT leak into public docs, examples, or signatures.

Why distinct uint and not distinct ptr X
  1. Atomic[uint] and Atomic[Pair[uint, uint]] compile and produce the right cmpxchg16b / ldxp+stxp instructions across every atomics backend we target. Atomic[ptr X] is less well-trodden and the strict-LCRQ DWCAS arm cannot tolerate "may or may not compile".
  2. distinct uint opacifies the slot at the compiler's lifecycle pass — no implicit =destroy / =copy is emitted on the slot value because the compiler sees POD bits. Refcount operations happen at the EXPLICIT call sites in the queue wrappers (push, pop, queue-destroy walk), never silently behind the queue's back.
  3. cast[uint](myRef) ↔ cast[ManagedRef[X]](bits) ↔ cast[ref X](toBits(mref)) is a no-op at runtime and preserves pointer identity, which is the precondition for the per-MM refcount calls to find the correct heap header.
ABI stability

sizeof(ManagedRef[X]) and alignof(ManagedRef[X]) are equal to sizeof(uint) and alignof(uint) on every supported platform. This is the wire-format invariant the queue's atomic slot operations depend on, and it is asserted at compile time in the static: block below. Across-MM ABI promises (arc ↔ orc ↔ atomicArc ↔ none) follow from this identity plus the same field layout in Queue[T, ...]. What we explicitly do NOT promise: refc bit-compat, cross-Nim-version, cross-minor-version.

Per-MM shim arms

The incRefSlot / decRefSlot templates dispatch on the compile-time MM define:

  • arc / orc / atomicArc — bump/drop the cell's refcount via the same per-MM mechanism the compiler would emit for a ref X. The atomicArc arm collapses with arc in the when because the C-RTL substitutes the atomic op transparently.
  • refc — tracing GC: GC_ref / GC_unref.
  • none — strict bit-transport contract: no-op. The user owns lifetime; the queue is pointer-bit transport only.
  • nimony — uses arcInc / arcDec from nimony's std/system/arcops. The signature shape differs from Nim 2.x's nimIncRef family: nimony's arcInc(memLoc: var int) and arcDec(memLoc: var int): bool operate on the refcount field directly (a var int lvalue), NOT a heap-pointer. The arm below uses cast[ptr int](bits)[] to materialise a var int lvalue at the bits location. See partial-port boundary block below for heap-header offset and dispose-symbol caveats.

Implementation note: nimIncRef / nimDecRefIsLast / nimDestroyAndDispose are the underlying symbol path. Those are compilerRtl and emitted static in their TU, so a cross-module call site requires a thin wrapper. GC_ref / GC_unref are the exported wrappers around that exact path — under arc/orc/atomicArc they delegate to nimIncRef / =destroy (which in turn calls nimDecRefIsLast and nimDestroyAndDispose); under refc they hit the tracing-GC path. The semantic is identical; the symbol path differs only in being public. The bit-cast guarantee makes either choice equivalent.

ManagedRef

type ManagedRef[X] = distinct uint

Slot encoding for a ref X payload. Internal — see module

doc-comment. Sized and aligned identically to uint.

toManagedRef inline

proc toManagedRef(r: sink ref X): ManagedRef[X]

Pack a ref X into the slot encoding. Pointer-bit transfer

only — does NOT touch refcount. The queue wrapper (path_c_wrap.nim:wrapOrIdentity) is responsible for the paired incRefSlot that claims +1 of the cell's refcount lifetime for the slot. See path_c_wrap.nim.

Parameters
  • r (sink ref X)
Returns

ManagedRef[X]

toRef inline

proc toRef(mref: ManagedRef[X]): ref X

Unpack the slot encoding back to ref X. Pointer-bit transfer

only — does NOT touch refcount. Pop is destructive (move); the queue's +1 refcount share (claimed at push) is inherited by the caller's binding. See path_c_wrap.nim.

Parameters
  • mref (ManagedRef[X])
Returns

ref X

toBits inline

proc toBits(mref: ManagedRef[X]): uint

Expose the raw pointer bits. Used by the queue's atomic slot

operations (Atomic[uint] ops on the slot array). Identity codegen.

Parameters
  • mref (ManagedRef[X])
Returns

uint

fromBits inline

proc fromBits(_: typedesc[ManagedRef[X]]; bits: uint): ManagedRef[X]

Reconstruct a ManagedRef[X] from raw bits read out of the

slot array. Identity codegen.

Parameters
  • _ (typedesc[ManagedRef[X]])
  • bits (uint)
Returns

ManagedRef[X]

nilManagedRef inline

proc nilManagedRef(_: typedesc[X]): ManagedRef[X]

The all-zero slot — semantically equivalent to a nil ref X.

A generic proc rather than a const because the generic ManagedRef[X] cannot be a top-level const without binding X. Call as nilManagedRef(Foo) (typedesc passed explicitly) from generic queue code where X is bound by context.

Parameters
  • _ (typedesc[X])
Returns

ManagedRef[X]

incRefSlot

template incRefSlot(mref: ManagedRef[X])

Bump the refcount of the cell pointed at by mref. No-op when

mref is the nil slot. Per-MM dispatch.

Parameters
  • mref (ManagedRef[X])

decRefSlot

template decRefSlot(mref: ManagedRef[X])

Drop the refcount of the cell pointed at by mref. No-op when

mref is the nil slot. Per-MM dispatch.

Parameters
  • mref (ManagedRef[X])

incRefSlot

template incRefSlot(mref: ManagedRef[X])

Nimony arm of incRefSlot. Calls arcInc(memLoc: var int) on

the cell's refcount. Experimental.

Partial-port note: the rc field is assumed to live AT the slot bits address. The verified nimony heap-header offset is tracked at v0.2 — see module-level partial-port block.

Parameters
  • mref (ManagedRef[X])

decRefSlot

template decRefSlot(mref: ManagedRef[X])

Nimony arm of decRefSlot. Calls arcDec(memLoc: var int): bool

on the cell's refcount. Experimental.

Partial-port note: the dispose-on-last-ref symbol is omitted pending resolution. See module-level partial-port block.

Parameters
  • mref (ManagedRef[X])

reset inline

proc reset(mref: var ManagedRef[X])

Zero the slot AND drop the refcount in one balanced operation.

Used by the queue's destructor walk and by push-failure rollback. No-op when mref is already nil.

Parameters
  • mref (var ManagedRef[X])