luna_core/jit/send_compat.rs
1//! v2.1 Track J-C — cfg-gated Send-friendly aliases & wrappers for
2//! trace IR interior-mutability types.
3//!
4//! Two cfg modes:
5//! - `#[cfg(not(feature = "send"))]` — the default. All aliases
6//! resolve to `Rc` / `Cell` / `RefCell`; identical layout, identical
7//! behavior, identical perf as pre-J-C. **Bare `Vm` stays 0-cost**.
8//! - `#[cfg(feature = "send")]` — Send build. Aliases switch to
9//! `Arc` / `AtomicU32` / `AtomicBool` / `AtomicPtr<u8>` / `RwLock`
10//! so `CompiledTrace` + `Proto.traces` become structurally `Send +
11//! Sync` (combined with the J-A/J-B/J-D/J-E sleeves). No `unsafe
12//! impl Send` lifted in J-C — the lifts happen because the inner
13//! types are already Send.
14//!
15//! Wrapper newtypes (`TCellU32`, `TCellBool`, `TCellPtr`, `TRefLock`)
16//! expose Cell/RefCell-shaped methods (`.get()`/`.set()`/`.borrow()`/
17//! `.borrow_mut()`) so call sites stay path-identical to the pre-J-C
18//! `std::cell::*` shape — the cfg switch happens inside the wrapper.
19//!
20//! `TArc<T>` is a pure type alias to `Rc<T>` or `Arc<T>`. Both
21//! stdlib types share the same inherent-method names
22//! (`::new` / `::from` / `::clone` / `::as_ptr` / `::strong_count`)
23//! so the alias works for all call shapes that previously used
24//! `std::rc::Rc`.
25//!
26//! Performance note (default build): all wrappers are
27//! `#[repr(transparent)]` over their inner `Cell` / `RefCell`. The
28//! generated code is identical to direct Cell/RefCell access (the
29//! wrapper methods inline trivially). Size assertions in
30//! `tests/j_c_zero_cost_default.rs` pin this.
31//!
32//! See `.dev/rfcs/v2.1-track-j-c-verdict.md` for the full migration
33//! list + cfg-gating pattern shape.
34
35// ============================================================
36// TArc<T> — `Rc<T>` (default) or `Arc<T>` (send).
37// ============================================================
38
39/// J-C cfg-gated reference count. `Rc<T>` under the default feature
40/// set, `Arc<T>` under `feature = "send"`. Construction and method
41/// shapes match between the two (`new`, `from`, `clone`, `as_ptr`,
42/// `strong_count`), so most call sites swap `Rc` → `TArc` without
43/// further changes.
44#[cfg(not(feature = "send"))]
45pub type TArc<T> = std::rc::Rc<T>;
46/// J-C cfg-gated reference count (send build). See `feature = "send"`-off
47/// alias above.
48#[cfg(feature = "send")]
49pub type TArc<T> = std::sync::Arc<T>;
50
51// ============================================================
52// TCellU32 — Cell<u32> (default) or AtomicU32 (send).
53// ============================================================
54
55/// J-C cfg-gated `u32` cell. Same API surface as `std::cell::Cell<u32>`
56/// (`new`, `get`, `set`); the send build swaps in `AtomicU32` with
57/// `Relaxed` ordering — the SendVm RwLock supplies the cross-thread
58/// happens-before, so per-op atomic ordering can be relaxed.
59#[repr(transparent)]
60#[derive(Debug)]
61pub struct TCellU32 {
62 #[cfg(not(feature = "send"))]
63 inner: std::cell::Cell<u32>,
64 #[cfg(feature = "send")]
65 inner: std::sync::atomic::AtomicU32,
66}
67
68impl TCellU32 {
69 /// Construct a new cell holding `v`.
70 #[inline]
71 pub const fn new(v: u32) -> Self {
72 #[cfg(not(feature = "send"))]
73 {
74 Self {
75 inner: std::cell::Cell::new(v),
76 }
77 }
78 #[cfg(feature = "send")]
79 {
80 Self {
81 inner: std::sync::atomic::AtomicU32::new(v),
82 }
83 }
84 }
85
86 /// Read the current value.
87 #[inline]
88 pub fn get(&self) -> u32 {
89 #[cfg(not(feature = "send"))]
90 {
91 self.inner.get()
92 }
93 #[cfg(feature = "send")]
94 {
95 self.inner.load(std::sync::atomic::Ordering::Relaxed)
96 }
97 }
98
99 /// Store `v` into the cell.
100 #[inline]
101 pub fn set(&self, v: u32) {
102 #[cfg(not(feature = "send"))]
103 {
104 self.inner.set(v);
105 }
106 #[cfg(feature = "send")]
107 {
108 self.inner.store(v, std::sync::atomic::Ordering::Relaxed);
109 }
110 }
111}
112
113impl Clone for TCellU32 {
114 fn clone(&self) -> Self {
115 Self::new(self.get())
116 }
117}
118
119impl Default for TCellU32 {
120 fn default() -> Self {
121 Self::new(0)
122 }
123}
124
125// ============================================================
126// TCellBool — Cell<bool> (default) or AtomicBool (send).
127// ============================================================
128
129/// J-C cfg-gated `bool` cell. Same API as `std::cell::Cell<bool>`
130/// (`new`, `get`, `set`).
131#[repr(transparent)]
132#[derive(Debug)]
133pub struct TCellBool {
134 #[cfg(not(feature = "send"))]
135 inner: std::cell::Cell<bool>,
136 #[cfg(feature = "send")]
137 inner: std::sync::atomic::AtomicBool,
138}
139
140impl TCellBool {
141 /// Construct a new cell holding `v`.
142 #[inline]
143 pub const fn new(v: bool) -> Self {
144 #[cfg(not(feature = "send"))]
145 {
146 Self {
147 inner: std::cell::Cell::new(v),
148 }
149 }
150 #[cfg(feature = "send")]
151 {
152 Self {
153 inner: std::sync::atomic::AtomicBool::new(v),
154 }
155 }
156 }
157
158 /// Read the current value.
159 #[inline]
160 pub fn get(&self) -> bool {
161 #[cfg(not(feature = "send"))]
162 {
163 self.inner.get()
164 }
165 #[cfg(feature = "send")]
166 {
167 self.inner.load(std::sync::atomic::Ordering::Relaxed)
168 }
169 }
170
171 /// Store `v` into the cell.
172 #[inline]
173 pub fn set(&self, v: bool) {
174 #[cfg(not(feature = "send"))]
175 {
176 self.inner.set(v);
177 }
178 #[cfg(feature = "send")]
179 {
180 self.inner.store(v, std::sync::atomic::Ordering::Relaxed);
181 }
182 }
183}
184
185// ============================================================
186// TCellPtr — Cell<*const u8> (default) or AtomicPtr<u8> (send).
187// ============================================================
188
189/// J-C cfg-gated raw-pointer cell. Same API as `std::cell::Cell<*const u8>`
190/// (`new`, `get`, `set`).
191///
192/// **IR layout invariant** (preserved): both `Cell<*const u8>` and
193/// `AtomicPtr<u8>` are 8-byte-sized, pointer-aligned, and store the
194/// raw pointer bits at offset 0. The Cranelift IR emits
195/// `iconst(I64, cell_addr) + load.i64` to read these cells; under
196/// `feature = "send"` the same load reads the AtomicPtr's bits with
197/// equivalent semantics — `AtomicPtr::load(Relaxed)` lowers to a plain
198/// pointer-sized load on the targets luna supports (arm64, x86_64),
199/// matching the pre-J-C `Cell::get` codegen byte-for-byte.
200#[repr(transparent)]
201pub struct TCellPtr {
202 #[cfg(not(feature = "send"))]
203 inner: std::cell::Cell<*const u8>,
204 #[cfg(feature = "send")]
205 inner: std::sync::atomic::AtomicPtr<u8>,
206}
207
208impl std::fmt::Debug for TCellPtr {
209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 f.debug_struct("TCellPtr")
211 .field("ptr", &self.get())
212 .finish()
213 }
214}
215
216impl TCellPtr {
217 /// Construct a new cell holding the null pointer.
218 #[inline]
219 pub const fn null() -> Self {
220 #[cfg(not(feature = "send"))]
221 {
222 Self {
223 inner: std::cell::Cell::new(std::ptr::null()),
224 }
225 }
226 #[cfg(feature = "send")]
227 {
228 Self {
229 inner: std::sync::atomic::AtomicPtr::new(std::ptr::null_mut()),
230 }
231 }
232 }
233
234 /// Construct a new cell holding `p`.
235 #[inline]
236 pub fn new(p: *const u8) -> Self {
237 #[cfg(not(feature = "send"))]
238 {
239 Self {
240 inner: std::cell::Cell::new(p),
241 }
242 }
243 #[cfg(feature = "send")]
244 {
245 Self {
246 inner: std::sync::atomic::AtomicPtr::new(p as *mut u8),
247 }
248 }
249 }
250
251 /// Read the current pointer bits.
252 #[inline]
253 pub fn get(&self) -> *const u8 {
254 #[cfg(not(feature = "send"))]
255 {
256 self.inner.get()
257 }
258 #[cfg(feature = "send")]
259 {
260 self.inner.load(std::sync::atomic::Ordering::Relaxed)
261 }
262 }
263
264 /// Store `p` into the cell.
265 #[inline]
266 pub fn set(&self, p: *const u8) {
267 #[cfg(not(feature = "send"))]
268 {
269 self.inner.set(p);
270 }
271 #[cfg(feature = "send")]
272 {
273 self.inner
274 .store(p as *mut u8, std::sync::atomic::Ordering::Relaxed);
275 }
276 }
277
278 /// Address of the cell itself — the value Cranelift IR bakes as
279 /// `iconst(I64, _)` to issue direct loads at runtime.
280 #[inline]
281 pub fn cell_addr(&self) -> *const () {
282 self as *const _ as *const ()
283 }
284}
285
286/// Clone mirrors `Cell<*const u8>: Clone` (`Cell<T>` is Clone whenever
287/// `T: Copy`). Produces a new cell at a different heap location with
288/// the same pointer bits. Callers that rely on heap-address stability
289/// (Cranelift IR loads of side-trace ptr cells) must NOT clone the
290/// containing `Box<TCellPtr>` once the IR has baked the original's
291/// address — same invariant as pre-J-C `Box<Cell<*const u8>>`.
292impl Clone for TCellPtr {
293 fn clone(&self) -> Self {
294 Self::new(self.get())
295 }
296}
297
298// ============================================================
299// TRefLock<T> — RefCell<T> (default) or RwLock<T> (send).
300// ============================================================
301
302/// J-C cfg-gated interior-mutable lock. Exposes `RefCell`-shaped
303/// `.borrow()` / `.borrow_mut()` whose returned guards `Deref<Target =
304/// T>`. Under the default feature set this is a thin newtype around
305/// `RefCell<T>` (zero overhead vs. the pre-J-C `RefCell` field);
306/// under `feature = "send"` it wraps `RwLock<T>` and the guards
307/// become `RwLockReadGuard` / `RwLockWriteGuard`.
308///
309/// Lock failure handling: the send build `unwrap()`s the lock result.
310/// The SendVm's outer `RwLock<()>` serializes mutator access so a
311/// poisoned lock is a real bug (a panic in a guard's user) and the
312/// propagating panic is the same UX as `RefCell::borrow_mut` on a
313/// re-entrant borrow.
314#[repr(transparent)]
315#[derive(Debug)]
316pub struct TRefLock<T: ?Sized> {
317 #[cfg(not(feature = "send"))]
318 inner: std::cell::RefCell<T>,
319 #[cfg(feature = "send")]
320 inner: std::sync::RwLock<T>,
321}
322
323impl<T> TRefLock<T> {
324 /// Construct a new lock around `v`.
325 #[inline]
326 pub const fn new(v: T) -> Self {
327 #[cfg(not(feature = "send"))]
328 {
329 Self {
330 inner: std::cell::RefCell::new(v),
331 }
332 }
333 #[cfg(feature = "send")]
334 {
335 Self {
336 inner: std::sync::RwLock::new(v),
337 }
338 }
339 }
340
341 /// Borrow the lock immutably. The returned guard derefs to `&T`;
342 /// callers use it identically to `RefCell::borrow`.
343 #[cfg(not(feature = "send"))]
344 #[inline]
345 pub fn borrow(&self) -> std::cell::Ref<'_, T> {
346 self.inner.borrow()
347 }
348
349 /// Borrow the lock immutably (send build — wraps `RwLock::read`).
350 #[cfg(feature = "send")]
351 #[inline]
352 pub fn borrow(&self) -> std::sync::RwLockReadGuard<'_, T> {
353 self.inner.read().unwrap()
354 }
355
356 /// Borrow the lock mutably. The returned guard derefs to `&mut T`;
357 /// callers use it identically to `RefCell::borrow_mut`.
358 #[cfg(not(feature = "send"))]
359 #[inline]
360 pub fn borrow_mut(&self) -> std::cell::RefMut<'_, T> {
361 self.inner.borrow_mut()
362 }
363
364 /// Borrow the lock mutably (send build — wraps `RwLock::write`).
365 #[cfg(feature = "send")]
366 #[inline]
367 pub fn borrow_mut(&self) -> std::sync::RwLockWriteGuard<'_, T> {
368 self.inner.write().unwrap()
369 }
370}