Skip to main content

Module arc

Module arc 

Source
Expand description

ARC reference counting FFI for JIT-compiled code.

§Route A close (ADR-006 §2.7.14 / W11-jit-new-array)

Both entry points operate on a JIT-emitted UnifiedValue<T> allocation pointer (or, equivalently, a v2 *mut HeapHeader-prefixed allocation — BOTH layouts carry a kind: u16 at offset 0 and a refcount at a layout-fixed offset). The caller contract (post-W11) is that the MIR emitter only emits these calls for slots whose NativeKind satisfies [NativeKind::is_refcounted] — String (Arc raw pointer) or Ptr(HeapKind::*) (Arc raw pointer wrapped in UnifiedValue). Raw scalar slots (Int64, Float64, Bool, etc.) never reach this FFI; the discrimination lives at the emitter side in [crate::mir_compiler::ownership::refcount_disposition].

§Refcount layout

Two #[repr(C)] shapes flow through this FFI today:

  1. JIT-emitted UnifiedValue<T> (ffi/jit_kinds.rs):

    offset  0: kind: u16
    offset  2: flags: u8
    offset  3: _reserved: u8
    offset  4: refcount: AtomicU32  ← retain/release target
    offset  8: data: T

    Used by box_string, box_typed_object, unified_box-family.

  2. v2 HeapHeader-prefixed allocations (shape_value::v2::heap_header):

    offset  0: refcount: AtomicU32  ← retain/release target
    offset  4: kind: u16
    offset  6: flags: u8
    offset  7: _pad: u8

    Used by TypedArray<T>, TypedClosureHeader, v2_alloc_struct. These have their OWN dedicated FFI (jit_v2_retain / jit_v2_release) and the MIR emitter routes them via Skip_TypedCellCarrier (v2_typed_array_elem_kind-guarded), so they do NOT reach this function in production. Disambiguating the two shapes from ptr alone would require a tag-bit probe (CLAUDE.md “Forbidden Patterns” #4); the contract is “MIR emitter routes by kind”.

§Discriminator on release

When refcount reaches zero, the underlying Box::from_raw reclaim needs to know the inner T of the UnifiedValue<T> (or the HeapValue discriminant for the Ptr(HeapKind::*) arms). The kind: u16 field at offset 0 of UnifiedValue IS the canonical discriminator (§2.7.6 / Q8 single-discriminator — same field, same semantics as HeapValue::kind()); reading it is NOT a tag-bit probe because it’s a structural field on the heap object, not a bit-pack on an inline value. The free path dispatches on this kind via [shape_value::release::release_v2_heap_by_kind].

§Forbidden

  • Bool-default fallback for unknown kind (CLAUDE.md “Forbidden rationalizations”).
  • tag_bits decode on the ptr value (CLAUDE.md “Forbidden Patterns” #4).
  • Silent no-op’ing the body (the supervisor explicitly refused this shape during the W11 reopen — “Soft-fail counter for now” pattern).
  • “ARC bridge” / “retain helper” / “kind-injection adapter” framing (CLAUDE.md “Renames to refuse on sight” — broader family rule).

Functions§

jit_arc_release
Release a JIT-emitted refcounted heap value.
jit_arc_retain
Retain a JIT-emitted refcounted heap value.