Expand description
Arc<String> strict-typed carrier FFI for JIT-emitted code
(W12-jit-string-carrier-unification, Phase 3 cluster-0 Round 12 T2/T3,
2026-05-13).
ADR-006 §2.7.5 (producing-site classification) names NativeKind::String
as the §2.7.5 String carrier with shape Arc::into_raw(Arc<String>) as u64 — the standard Rust Arc layout with refcount at offset -16 of the
data pointer. The VM-side consumer (crates/shape-vm/src/executor/objects/ set_methods.rs:136-155::result_slot_to_string_arc, mirrors in
hashmap_methods.rs) and KindedSlot::Drop for NativeKind::String
(crates/shape-value/src/kinded_slot.rs:500-502) both decode this exact
shape via Arc::increment_strong_count::<String> / Arc::from_raw(bits as *const String).
§Carrier-shape rule (binding)
-
NativeKind::Stringslot:Arc::into_raw(Arc<String>) as u64, refcount at offset -16. Retain/release dispatches through this module’sjit_arc_string_retain/jit_arc_string_release— bumps the Rust Arc control-block refcount. -
JIT-internal NaN-box string carrier:
Box::into_raw(Box::new( UnifiedValue<Arc<String>>)) as u64, refcount at offset +4 inside the UnifiedValue allocation. Retained/released via the legacyjit_arc_retain/jit_arc_releaseinffi/arc.rs. Stays for JIT-internal pathways (the dispatch shell’s method-name push atterminators.rs:235,call_string_methodreturns, etc.) that pair the bits with their own JIT-internal decode contract.
Mixing the two segfaults at every retain/release reclaim:
jit_arc_releaseon anArc::into_raw(Arc<String>) as u64slot reads*(bits + 4) as *const AtomicU32— offset 4 inside theStringpayload (String’sptr/cap/lenwords), corrupting the data onfetch_sub.Arc::decrement_strong_count::<String>(bits)on aBox::into_raw( Box::new(UnifiedValue<Arc<String>>))slot decrements*(bits - 16)as if it were the Arc control block — but offset -16 from the UnifiedValue start is whatever the allocator placed there. UB.
§Round 7A precedent
The Result/Option Arc carriers in ffi/result.rs::jit_arc_result_retain
/ _release / jit_arc_option_retain / _release (Round 7A close
commit d01d83b7 + 9f27edcd) and the Round 9 typed-Arc collection
retain/release pairs in ffi/v2/collection_arc.rs are the bound
precedent shape for every body in this module.
§Round 12 T2/T3 surface closures
- Smoke 4 JIT:
let mut s = Set(); s.add("a"); s.add("b"); print( s.size())→2VM == JIT. The"a"/"b"constants flow asMirConstant::Stroperands stampedNativeKind::String; the VM trampoline’sKindedSlot::Dropdecodes viaArc::from_raw(bits as *const String). Pre-Round-12box_stringreturned NaN-box bits → UB at the VM consumer’sArc::from_raw. print("hello")JIT: was clean SURFACE at the print Call-terminator’sNativeKind::Stringarm interminators.rs::466(Round 8A reopen surfaced). Post-Round-12 the §2.7.5 producer emits the matching carrier shape andjit_print_strreads&Stringdirectly.
Functions§
- arc_
string_ constant - Compile-time helper: produce a §2.7.5
Arc::into_raw-shape carrier pointer for aMirConstant::Str/MirConstant::StringIdsite, content-deduplicated through the program-wide [intern_pool]. - jit_
arc_ string_ release - Release an
Arc<String>strong-count share. Mirrorsjit_arc_string_retain’s increment — usesArc::decrement_strong_count::<String>per Rust Arc contract. Reaching refcount zero runsString::Drop(drops the inner buffer). - jit_
arc_ string_ retain - Retain (clone) an
Arc<String>strong-count share. Bumps the standard Rust Arc refcount at offset -16 of theArc::into_rawpointer viaArc::increment_strong_count::<String>— NOT the W-seriesUnifiedValue<T>refcount at offset 4 (jit_arc_retain’s shape).