1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//! 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<String> raw pointer) or
//! `Ptr(HeapKind::*)` (Arc<HeapValue> 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`):
//! ```text
//! 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`):
//! ```text
//! 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).
use ;
/// Refcount call counters for W11-jit-new-array leak-balance verification.
/// Surfaced via `--trace-jit=shape_jit::arc_counters=info` (cluster-2
/// closure-wave-F tracing-crate migration 2026-05-16; supersedes
/// `SHAPE_JIT_ARC_COUNTERS=1`). The atomic writes still happen
/// unconditionally (the counters are pub(crate) accessible from tests); the
/// tracing macros at the reader site collapse to no-ops when the `jit-trace`
/// feature is OFF, so the read-of-counters work is skipped in release
/// builds. Per the supervisor's reopen Step 4: in addition to stdout
/// matching, confirm the retain/release sequence is balanced via these
/// counters.
pub static JIT_ARC_RETAIN_CALLS: AtomicU64 = new;
pub static JIT_ARC_RELEASE_CALLS: AtomicU64 = new;
pub static JIT_ARC_RELEASE_FREES: AtomicU64 = new;
/// String-carrier-specific counters for the cluster-2-closure-wave-E
/// measurement protocol (cluster-2-inventory §F). These track the §2.7.5
/// `Arc::into_raw(Arc<String>) as u64` carrier path independently from the
/// `UnifiedValue<T>` counters above — `arc_string_constant` allocates an
/// `Arc<String>` and bumps the strong count to 2 (the "permanent share"
/// + "active share" discipline at `ffi/string.rs:111-143`), while the
/// JIT-emitted retain/release pairs operate on the active share via
/// `jit_arc_string_retain` / `jit_arc_string_release`.
///
/// The leak shape per the inventory §F.3 estimate is one permanent share
/// per distinct `arc_string_constant` allocation never released; these
/// counters quantify it directly:
///
/// - `STRING_CONSTANT_ALLOCS` = number of `arc_string_constant` calls
/// (one per JIT-compile-time `MirConstant::Str` / `MirConstant::StringId`
/// / `MirConstant::Method` materialization)
/// - `STRING_RETAIN_CALLS` = `jit_arc_string_retain` invocations (per
/// runtime active-share copy)
/// - `STRING_RELEASE_CALLS` = `jit_arc_string_release` invocations (per
/// runtime active-share drop)
/// - `STRING_RELEASE_FREES` = times the release reached refcount 0 (the
/// §2.7.5 `Arc<String>` was actually deallocated). With the permanent-
/// share discipline this should equal 0 for every constant produced by
/// `arc_string_constant` (the leak surface).
///
/// Leak quantification: `STRING_CONSTANT_ALLOCS - STRING_RELEASE_FREES` =
/// number of permanently-leaked `Arc<String>` allocations at program end.
pub static STRING_CONSTANT_ALLOCS: AtomicU64 = new;
pub static STRING_RETAIN_CALLS: AtomicU64 = new;
pub static STRING_RELEASE_CALLS: AtomicU64 = new;
pub static STRING_RELEASE_FREES: AtomicU64 = new;
/// Offset of the `refcount: AtomicU32` field within a JIT-emitted
/// `UnifiedValue<T>` allocation. Must match `#[repr(C)]` layout of
/// `crate::ffi::jit_kinds::UnifiedValue`.
const UNIFIED_VALUE_REFCOUNT_OFFSET: usize = 4;
/// Retain a JIT-emitted refcounted heap value.
///
/// `ptr` is a non-null pointer to a `UnifiedValue<T>` allocation produced
/// by `unified_box` / `box_string` / `box_typed_object`. Atomically bumps
/// the `refcount: AtomicU32` field at offset 4 by 1 (Relaxed ordering,
/// matching the typed-Arc retain contract).
///
/// Caller contract (W11-jit-new-array): the MIR emitter only emits this
/// call for slots whose `NativeKind` satisfies
/// `NativeKind::is_refcounted` (i.e. `String` or `Ptr(HeapKind::*)`).
/// Scalar slots are filtered out at the emitter side per ADR-006 §2.7.5
/// stamp-at-compile-time.
///
/// # Safety
///
/// `ptr` must be a non-null `*const UnifiedValue<T>` from a live
/// JIT-emitted heap allocation, or null. Passing a dangling, mistyped,
/// or non-`UnifiedValue` pointer is undefined behavior. Null is
/// silently no-op'd (the MIR emitter MAY route a null-initialized slot
/// here on a dead-store path that the borrow-checker proves never
/// reads).
pub extern "C"
/// Release a JIT-emitted refcounted heap value.
///
/// Atomically decrements the `refcount: AtomicU32` field at offset 4 of
/// a `UnifiedValue<T>` allocation. When the count reaches zero,
/// dispatches the kinded free via the `kind: u16` field at offset 0
/// (the §2.7.6 / Q8 single-discriminator).
///
/// The kinded reclaim is delegated to
/// [`crate::ffi::jit_release::release_unified_value_by_kind`] so the
/// per-kind `Box::from_raw` arms stay colocated with the
/// `UnifiedValue<T>` constructors in `jit_kinds.rs` / `value_ffi.rs`.
///
/// # Safety
///
/// `ptr` must be a non-null `*const UnifiedValue<T>` from a live
/// JIT-emitted heap allocation, or null. Passing a dangling, mistyped,
/// or non-`UnifiedValue` pointer is undefined behavior.
pub extern "C"