once-ptr-cell 0.1.0

Allocation-free, no_std lazy CAS-published pointer cell for use inside a #[global_allocator]: fallible init with OOM rollback, and losers re-race the CAS instead of blocking (unlike OnceLock). See the crate docs for the full state-machine/safety contract.
Documentation
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
use core::marker::PhantomData;
use core::ptr::NonNull;

// The atomics are aliased so loom can shadow the REAL `OncePtrCell` type: under
// `--cfg loom` the cell is built on `loom::sync::atomic`, so the shipped loom
// tests (in `tests/`) model-check the actual implementation, not a hand-copied
// transcription. Under normal builds it is `core::sync::atomic`, keeping the
// crate `no_std` and allocation-free.
//
// CONSUMER HAZARD: `--cfg loom` is a global `RUSTFLAGS` cfg — it applies to
// every crate in the build, not only the one whose loom suite you meant to
// run. Under it `OncePtrCell::new` is NOT `const` (see its doc), so a
// `static CELL: OncePtrCell<T> = OncePtrCell::new();` anywhere in the build
// fails to compile. Scope the flag (`cargo test -p <crate> ...`), or supply a
// `#[cfg(loom)]` const-capable stand-in in your own crate — see
// `src/registry/bootstrap.rs`'s `loom_shim` in the `sefer-alloc` repository
// this crate is extracted from, for a worked example.
#[cfg(not(loom))]
use core::sync::atomic::{AtomicPtr, Ordering};
#[cfg(loom)]
use loom::sync::atomic::{AtomicPtr, Ordering};

/// The loser spin-wait hint. In a normal build this is [`core::hint::spin_loop`]
/// (a PAUSE/YIELD CPU hint, no scheduler involvement). Under `--cfg loom` the
/// real busy-spin is opaque to loom's model executor and would exhaust its
/// branch budget ("processor must make progress"); there we yield to loom's
/// fair scheduler instead, so it can advance the winner thread to its publish.
/// Same happens-before semantics either way (a hint/yield synchronises nothing);
/// only the scheduling nudge differs.
#[cfg(loom)]
#[inline]
fn spin_hint() {
    loom::thread::yield_now();
}
#[cfg(not(loom))]
#[inline]
fn spin_hint() {
    core::hint::spin_loop();
}

/// The `INITIALIZING` sentinel address: a non-null, non-real marker meaning
/// "one thread won the CAS and is currently running the init closure". Never
/// dereferenced — only compared for pointer equality against the cell's stored
/// value. An *aligned* pointer to `T` can never equal this address
/// (`align_of::<T>() >= 2` is asserted at construction); a *misaligned or
/// synthesised* pointer at this address is reachable from safe code and is
/// rejected by a release-active `assert!` in
/// [`OncePtrCell::get_or_try_init`], not by this constant alone.
const SENTINEL_INITIALIZING: usize = 1;

/// The outcome of [`OncePtrCell::dbg_rollback_reenterable`] — exactly the two
/// answers that probe can give, and no third one it could never produce.
///
/// In particular there is no "rollback is broken" variant: the probe cannot
/// distinguish that from "another thread legitimately owns the cell now",
/// because both make its postcondition CAS fail identically. See the
/// method's own docs for the full argument.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RollbackProbe {
    /// The rollback provably cleared the sentinel: the probe's postcondition
    /// CAS re-won the cell afterwards, so no future winner or spinning loser
    /// can be wedged by it. The cell is restored to `UNINIT` before
    /// returning.
    Proven,
    /// The probe could not run its check, and this is NOT evidence that
    /// rollback is broken. Either the cell was not `UNINIT` when the probe
    /// entered (already `READY`, or owned by another thread at that
    /// instant) — in which case the probe never touched it at all — or a
    /// real `get_or_try_init` caller re-won the cell during the probe's own
    /// rollback-then-reCAS window, in which case the probe still does not
    /// touch it, but the cell is no longer necessarily `UNINIT`: the real
    /// caller may already be running `init`, or may have published `READY`,
    /// by the time this returns. Either way the probe never clobbers a
    /// state it does not own.
    NotApplicable,
}

/// A lazy, CAS-published pointer cell: `UNINIT -> INITIALIZING -> READY` over a
/// single `AtomicPtr<T>`, with fallible init (OOM rolls back and losers
/// re-race). See the [crate-level docs](crate) for the full state machine, the
/// anti-livelock loser-spin rule, and the "usable inside a
/// `#[global_allocator]`" niche.
///
/// The cell never drops, frees, or reads through the pointee — it only
/// publishes and hands back the `*mut T` the init closure produced.
///
/// `#[repr(transparent)]`: the "one `AtomicPtr`"/"one word" claims made
/// throughout this crate's docs are a LAYOUT GUARANTEE, not an
/// implementation detail that happens to be true on the current compiler.
/// `PhantomData<*mut T>` is the only other field; it is always zero-sized
/// with alignment 1, which is exactly what `repr(transparent)` requires of
/// every field beyond the one real one.
#[repr(transparent)]
pub struct OncePtrCell<T> {
    /// The one word driving the state machine: `null` = `UNINIT`,
    /// [`SENTINEL_INITIALIZING`] = `INITIALIZING`, any other value = `READY`
    /// (a real published pointer).
    ptr: AtomicPtr<T>,
    /// `OncePtrCell<T>` behaves like it holds a `*mut T` it hands out; the
    /// marker documents the relationship without owning a `T`.
    _marker: PhantomData<*mut T>,
}

// The cell is `Send + Sync` UNCONDITIONALLY, exactly like the `AtomicPtr<T>` it
// wraps — and for the same reason. The cell never dereferences `T` or hands out
// a `&T`; it only stores and returns a RAW `*mut T` / `NonNull<T>`. Whether the
// pointee is safe to *access* from another thread is the CALLER's contract (the
// `get`/`get_or_try_init` accessors return raw pointers, and reading through
// them is `unsafe`), not this type's — precisely the `AtomicPtr` model, which is
// `Send + Sync` for every `T`. This is what lets the cell hold a pointer to a
// `!Sync` payload (e.g. a per-thread heap) whose actual access the caller guards
// by its own single-writer/`&mut` discipline. The `PhantomData<*mut T>` (present
// only to document the "holds a `*mut T`" relationship and pin variance) is what
// removes the auto-impls, so we restore them here.
//
// SAFETY: `ptr` is an `AtomicPtr`, so all concurrent access to the cell's own
// state is race-free; the only value crossing a thread boundary through the cell
// is a raw `*mut T`, which is `Send`/`Sync`-neutral (raw pointers carry no
// sharing obligation — the obligation is on the caller's later deref). Identical
// to `AtomicPtr<T>`'s own unconditional `Send + Sync`.
unsafe impl<T> Send for OncePtrCell<T> {}
// SAFETY: see the `Send` impl above.
unsafe impl<T> Sync for OncePtrCell<T> {}

/// RAII rollback guard held across the init closure: if `init`
/// unwinds instead of returning, the winner thread's stack unwinds through
/// this guard's `Drop`, which stores `null` with `Release` — exactly the
/// same rollback the explicit OOM path performs. Without this, an unwinding
/// `init` leaves the `INITIALIZING` sentinel stuck forever: every concurrent
/// loser busy-spins at 100% CPU indefinitely (they spin on `==
/// INITIALIZING`, which never changes), and every future
/// `get_or_try_init`/`get` caller observes permanent `INITIALIZING` — a
/// silent whole-process livelock, and a strictly worse outcome than the
/// `OnceLock` equivalent, which leaves its cell uninitialised and lets the
/// next caller retry.
///
/// Defused (via [`RollbackGuard::defuse`]) on both non-unwinding exits — the
/// successful publish and the explicit `None`/OOM rollback — so the normal
/// paths are unaffected; this guard only ever fires on the unwind path.
///
/// Test coverage note: `tests/cell_unit.rs`'s
/// `panicking_init_rolls_back_and_subsequent_call_succeeds` proves a
/// strictly weaker property than the one described above — that a
/// SUBSEQUENT call on an already-quiescent cell succeeds after a panicking
/// init unwound and rolled back. The same file's
/// `concurrent_get_or_try_init_started_before_unwind_completes_still_succeeds`
/// goes further: a real concurrent caller, whose own `get_or_try_init` call
/// is issued no later than the point where it observes the winner already
/// holds the sentinel, is never lost — either it observes the live sentinel
/// and spins until the rollback wakes it, or it observes the already-rolled-
/// back cell and wins the CAS itself directly. Both interleavings are
/// possible depending on scheduling, and the test only guarantees success
/// across whichever one actually happens; it does NOT deterministically
/// force the spin-and-wake path specifically (that would need a hook inside
/// the CAS/spin loop itself, which this crate does not have). A future
/// change that made the rollback conditional (e.g. skipping it when no
/// loser is observed waiting) would still very likely reintroduce a
/// livelock this test would time out on, just not with airtight certainty
/// that the spin branch itself was exercised on every run. Not closed by a
/// loom test: loom's deterministic scheduling model and
/// `std::panic::catch_unwind` do not compose cleanly (loom needs to replay
/// every interleaving of an unwind path, which its own docs do not treat as
/// a first-class supported pattern).
struct RollbackGuard<'a, T> {
    ptr: &'a AtomicPtr<T>,
    defused: bool,
}

impl<'a, T> RollbackGuard<'a, T> {
    #[inline]
    fn new(ptr: &'a AtomicPtr<T>) -> Self {
        Self {
            ptr,
            defused: false,
        }
    }

    /// Disarm the guard: its `Drop` becomes a no-op. Call once the caller has
    /// itself handled the `INITIALIZING` state (published `READY`, or
    /// performed the explicit `None`/OOM rollback).
    #[inline]
    fn defuse(&mut self) {
        self.defused = true;
    }
}

impl<T> Drop for RollbackGuard<'_, T> {
    #[inline]
    fn drop(&mut self) {
        if !self.defused {
            // Same ordering rationale as the explicit OOM rollback in
            // `get_or_try_init`: `Release` pairs with the retrying thread's
            // later CAS `Acquire`; there is no partially-initialised state to
            // synchronise (init never published), only the "cell is free
            // again" fact.
            self.ptr.store(core::ptr::null_mut(), Ordering::Release);
        }
    }
}

impl<T> OncePtrCell<T> {
    /// Construct a fresh `UNINIT` cell (null pointer).
    ///
    /// **Not `const` under `--cfg loom`** (loom's atomics have no const
    /// constructor); on normal builds it is `const` so the cell can live in a
    /// `static`. Because `--cfg loom` is a global `RUSTFLAGS` cfg, this
    /// applies to every crate in a build that sets it, not only crates that
    /// mean to run loom against `OncePtrCell` itself — a
    /// `static CELL: OncePtrCell<T> = OncePtrCell::new();` anywhere in such a
    /// build fails to compile. Scope the flag to this crate
    /// (`cargo test -p once-ptr-cell ...`), or supply your own
    /// `#[cfg(loom)]` const-capable stand-in if you need the flag
    /// workspace-wide.
    ///
    /// # Panics
    ///
    /// Panics if `align_of::<T>() == 1`. The `INITIALIZING` sentinel is encoded
    /// as the address `1` (see the crate-level "Sentinel encoding" docs); that
    /// encoding needs a spare low bit, which requires every valid aligned
    /// address of `T` to be even — i.e. `align_of::<T>() >= 2`. In the
    /// documented `static CELL: OncePtrCell<T> = OncePtrCell::new();` usage
    /// this `assert!` is evaluated at compile time (a const-eval failure, not
    /// a runtime panic); called from a non-const context (e.g. inside a
    /// function, or via `OncePtrCell::<T>::default()`) with a `T` whose
    /// alignment is 1, it panics at runtime instead.
    #[cfg(not(loom))]
    #[must_use]
    pub const fn new() -> Self {
        // Compile-time guard: the sentinel address (1) must not be a valid
        // aligned address for `T`, or it could collide with a real pointer.
        // Every `T` used behind this cell must have alignment >= 2.
        assert!(
            core::mem::align_of::<T>() >= 2,
            "OncePtrCell<T> requires align_of::<T>() >= 2 so the INITIALIZING \
             sentinel (address 1) can never collide with a real published pointer"
        );
        OncePtrCell {
            ptr: AtomicPtr::new(core::ptr::null_mut()),
            _marker: PhantomData,
        }
    }

    /// Construct a fresh `UNINIT` cell (loom build — non-`const`).
    ///
    /// # Panics
    ///
    /// Panics if `align_of::<T>() == 1` — see the non-loom [`OncePtrCell::new`]
    /// doc above for why (identical condition; this build cannot be `const` so
    /// the check always runs at runtime here).
    #[cfg(loom)]
    #[must_use]
    pub fn new() -> Self {
        assert!(
            core::mem::align_of::<T>() >= 2,
            "OncePtrCell<T> requires align_of::<T>() >= 2"
        );
        OncePtrCell {
            ptr: AtomicPtr::new(core::ptr::null_mut()),
            _marker: PhantomData,
        }
    }

    /// The `INITIALIZING` sentinel as a `*mut T` — a bare marker, never
    /// dereferenced, constructed WITHOUT provenance (strict-provenance-clean).
    #[inline]
    fn sentinel() -> *mut T {
        core::ptr::without_provenance_mut::<T>(SENTINEL_INITIALIZING)
    }

    /// `true` iff `p` is a real published pointer (non-null AND non-sentinel).
    #[inline]
    fn is_ready(p: *mut T) -> bool {
        let a = p.addr();
        a != 0 && a != SENTINEL_INITIALIZING
    }

    /// Return the published pointer if the cell is `READY`, else `None`.
    ///
    /// A pure `Acquire` load: no CAS, no init, no spin. `None` means the cell is
    /// `UNINIT` or `INITIALIZING` right now (neither the sentinel nor null is
    /// ever returned as `Some`).
    ///
    /// The returned pointer is the exact value the init closure produced; the
    /// `Acquire` load pairs with the winner's `Release` publish, so any read the
    /// caller performs through the pointer sees the fully initialised pointee.
    #[inline]
    #[must_use]
    pub fn get(&self) -> Option<NonNull<T>> {
        let p = self.ptr.load(Ordering::Acquire);
        if Self::is_ready(p) {
            // SAFETY: `is_ready(p)` just proved `p` is non-null (neither null
            // nor the sentinel).
            Some(unsafe { NonNull::new_unchecked(p) })
        } else {
            None
        }
    }

    /// Get the published pointer, or run `init` to produce it — with the full
    /// `UNINIT -> INITIALIZING -> READY` protocol, OOM rollback, and loser
    /// re-race.
    ///
    /// Contract:
    /// - **Fast path**: if the cell is already `READY`, returns the published
    ///   pointer with one `Acquire` load; `init` is not called.
    /// - **Winner**: the thread that CASes `null -> sentinel` calls `init`
    ///   exactly once. `init` returns `Some(ptr)` on success (the cell
    ///   publishes it with `Release` and returns it — `ptr` is leaked for the
    ///   process lifetime, the cell never frees it), or `None` on OOM (the cell
    ///   rolls the sentinel back to `null` and returns `None`; a later call may
    ///   retry).
    /// - **Loser**: a thread that loses the CAS spins with `Acquire` loads
    ///   **only while the state is `INITIALIZING`**. When the winner publishes,
    ///   the loser returns the same pointer. When the winner rolls back after
    ///   OOM (state returns to `null`), the loser falls out of the spin and
    ///   **re-races the CAS itself** — it does not wait for a `READY` that will
    ///   never come.
    ///
    /// Returns `Some(published pointer)` (same value for all threads across a
    /// successful lifetime) or `None` if `init` reported OOM on this thread's
    /// winning attempt. The returned pointer is never null and never the
    /// sentinel.
    ///
    /// `init` is [`FnOnce`], not `FnMut`, because ONE call to this method
    /// invokes it **at most once**: whichever way the winner arm exits
    /// (publish, OOM rollback, or unwind) it leaves the method, and the
    /// loser arm never calls `init` at all — a loser that falls out of the
    /// spin on a rollback re-races the CAS and, if it wins, is making its
    /// own first and only call. `FnOnce` is therefore the accurate bound,
    /// and it lets you pass a closure that consumes what it captures.
    ///
    /// `init` must be reentrancy-safe with respect to whatever the cell guards:
    /// it runs while this thread holds the `INITIALIZING` sentinel, so it must
    /// not itself call back into `get_or_try_init` on the SAME cell (that would
    /// spin forever — the current thread is the only one able to publish).
    ///
    /// The restriction is **transitive, and multiple cells form a lock-order
    /// graph**: `init` must not wait, through any chain of calls, on a cell
    /// whose own initialiser can wait on this one. Two cells are enough for a
    /// deadlock with no direct self-recursion anywhere — thread 1 wins `A` and
    /// its `init` initialises `B`, while thread 2 wins `B` and its `init`
    /// initialises `A`; both spin forever at 100% CPU. Acquire multiple cells
    /// in a fixed global order, exactly as you would locks.
    ///
    /// `init` must also be fast and non-blocking: every loser thread spins for
    /// exactly as long as the winner's `init` call takes (see the module docs'
    /// "spin-wait" section) — there is no bounded-latency guarantee from the
    /// cell itself, only from the caller keeping `init` short.
    ///
    /// Calling this from inside a `#[global_allocator]` adds further hard
    /// obligations on `init` (no allocation, no unwind) — see the crate docs'
    /// ["Using this inside a `#[global_allocator]`"](crate#using-this-inside-a-global_allocator)
    /// section.
    ///
    /// # Panics
    ///
    /// Panics if the winning `init` call returns `Some(ptr)` where `ptr`'s
    /// address is the reserved `INITIALIZING` sentinel (`1`) — a safe `init`
    /// closure can construct and return this exact address, and publishing it
    /// unguarded would make every reader (this thread's own fast path
    /// included) misclassify the cell as still-initializing forever. This
    /// check is release-active, not `debug_assert!`-gated.
    ///
    /// If `init` itself panics (unwinds) instead of returning, the panic
    /// propagates out of `get_or_try_init` and the cell is left in `UNINIT`
    /// (not wedged in `INITIALIZING`) — a later call, on any thread, may
    /// retry `init`. This mirrors the OOM/`None` rollback above; the only
    /// difference is how the winner exits. Note what this does and does not
    /// buy: it keeps the CELL consistent, but it does not make the unwind
    /// itself sound when the frame below is a `GlobalAlloc` method, where
    /// unwinding is undefined behaviour regardless of this cell's state.
    #[must_use = "`None` means `init` reported OOM and the cell was rolled \
                  back to UNINIT — it is NOT initialised, and discarding \
                  this hides the failure"]
    #[inline]
    pub fn get_or_try_init<F>(&self, init: F) -> Option<NonNull<T>>
    where
        F: FnOnce() -> Option<NonNull<T>>,
    {
        // Fast path, and nothing else: one `Acquire` load plus the readiness
        // test. Everything the already-published case does NOT need — the
        // claim CAS, the rollback guard, the release-active `assert!`, the
        // loser spin, the re-race loop — lives in `init_slow`, which is
        // `#[cold] #[inline(never)]` so none of it is inlined into a
        // caller that only ever hits this branch.
        let p = self.ptr.load(Ordering::Acquire);
        if Self::is_ready(p) {
            // SAFETY: `is_ready(p)` just proved `p` is non-null (neither the
            // `null` UNINIT value nor the `SENTINEL_INITIALIZING` marker), so
            // `p` is a real published pointer.
            return Some(unsafe { NonNull::new_unchecked(p) });
        }
        self.init_slow(init)
    }

    /// The full `UNINIT -> INITIALIZING -> READY` protocol: claim CAS, init
    /// closure, publish/rollback, loser spin, re-race. Split out of
    /// [`OncePtrCell::get_or_try_init`] so the already-READY fast path stays
    /// small enough to inline on its own; correctness is unchanged, and the
    /// re-checked fast path at the top of the loop below is still needed
    /// here (a re-racing loser re-enters it after a rollback).
    ///
    /// Note this does NOT deduplicate monomorphised code: the slow path is
    /// still generic over `F`, so one copy exists per closure type. It only
    /// keeps that copy out of the caller's hot path.
    #[cold]
    #[inline(never)]
    fn init_slow<F>(&self, init: F) -> Option<NonNull<T>>
    where
        F: FnOnce() -> Option<NonNull<T>>,
    {
        loop {
            // Re-checked fast path: a loser that fell out of the spin on a
            // rollback, or lost the CAS to a winner that has since
            // published, lands here.
            let p = self.ptr.load(Ordering::Acquire);
            if Self::is_ready(p) {
                // SAFETY: `is_ready(p)` just proved `p` is non-null (neither
                // the `null` UNINIT value nor the `SENTINEL_INITIALIZING`
                // marker), so `p` is a real published pointer.
                return Some(unsafe { NonNull::new_unchecked(p) });
            }

            // Slow path: race to become the initialising winner.
            match self.ptr.compare_exchange(
                core::ptr::null_mut(),
                Self::sentinel(),
                // Success `Acquire`: synchronises-with whichever `Release`
                // store last returned the cell to `null` — the explicit OOM
                // rollback in this function's own winner arm, the unwind
                // guard's `Drop`, or either of `dbg_rollback_reenterable`'s
                // two null-stores. It says nothing
                // about the publish this thread is about to perform: an
                // acquire cannot pair with a release that has not happened
                // yet. The load-bearing pair for the pointee is this winner's
                // own `Release` publish below against every reader's
                // `Acquire` load.
                //
                // Whether `Relaxed` would suffice here — a rollback leaves no
                // payload state for a new winner to acquire — remains an open
                // question, and is DELIBERATELY not acted on. Weakening it
                // needs BOTH a loom counterfactual proving the weaker form
                // sound and a measurement showing it is worth anything, and
                // the second half is unobtainable on the hardware this crate
                // is developed on: `Acquire` on x86-64 is a plain load, so a
                // local A/B can only ever report noise. The same applies to
                // the loser spin's per-iteration `Acquire` below. Both stay
                // as they are — over-strong, never under-strong — until
                // someone can measure them on a weakly-ordered target
                // (AArch64/ARM) with a model to back the change.
                Ordering::Acquire,
                // Failure `Relaxed`: we re-load in the spin loop below.
                Ordering::Relaxed,
            ) {
                Ok(_) => {
                    // ── Winner ──────────────────────────────────────────────
                    // We hold the INITIALIZING sentinel; we are the sole
                    // initialiser. Hold a rollback guard across `init()` so an
                    // UNWINDING init (a panic in caller code, or the `assert!`
                    // below firing) also rolls the sentinel back — see
                    // `RollbackGuard`'s own doc for why this is load-bearing.
                    let mut guard = RollbackGuard::new(&self.ptr);
                    match init() {
                        Some(ptr) => {
                            let raw = ptr.as_ptr();
                            // Release-active `assert!`, not `debug_assert!`
                            // a SAFE init closure can construct
                            // `NonNull::new(without_provenance_mut(1))` and
                            // hand back the very SENTINEL address this cell
                            // uses to mean "still initialising". In release,
                            // a `debug_assert!` here compiles out, so the
                            // sentinel would get published as if it were
                            // READY — every current loser and every future
                            // caller then spins forever, since the published
                            // value reads back as `INITIALIZING`, not `READY`
                            // (`is_ready`'s own definition), with no
                            // diagnostic anywhere. Two integer compares on a
                            // once-per-cell cold path is a negligible cost
                            // for closing a violation of this method's own
                            // documented "never null, never the sentinel"
                            // guarantee that is reachable from 100% safe
                            // code — exactly the class `debug_assert!` is
                            // NOT meant for. If this fires, the rollback
                            // guard above unwinds it cleanly.
                            assert!(
                                Self::is_ready(raw),
                                "OncePtrCell: init returned the null/sentinel address"
                            );
                            // Publish with `Release` so every subsequent
                            // `Acquire` load (fast path here, plus every loser's
                            // spin-load) sees the fully constructed pointee.
                            // This is THE ordering the Relaxed-publish
                            // counterfactual breaks.
                            self.ptr.store(raw, Ordering::Release);
                            // Defuse: the guard's rollback must NOT fire now
                            // that the real pointer is published.
                            guard.defuse();
                            return Some(ptr);
                        }
                        None => {
                            // OOM: roll the sentinel back to null so losers
                            // spinning on `== INITIALIZING` fall out and
                            // re-race, and future callers can retry. `Release`
                            // pairs with the retrying thread's later CAS
                            // `Acquire`: there is no partially-initialised state
                            // to synchronise (init never published), only the
                            // "cell is free again" fact. Explicit here (rather
                            // than relying on the guard's Drop) to keep this
                            // path's ordering self-documenting; defuse first so
                            // the guard does not redundantly store again.
                            guard.defuse();
                            self.ptr.store(core::ptr::null_mut(), Ordering::Release);
                            return None;
                        }
                    }
                }
                Err(_) => {
                    // ── Loser ───────────────────────────────────────────────
                    // Spin ONLY while the state is INITIALIZING. This is the
                    // anti-livelock rule: a `!= READY` spin would
                    // deadlock if the winner rolled back to null after OOM
                    // (READY never comes). Falling out on any non-INITIALIZING
                    // observation lets us return READY (winner published) or
                    // loop back to the top and re-race (winner rolled back).
                    loop {
                        let p = self.ptr.load(Ordering::Acquire);
                        let a = p.addr();
                        if a == SENTINEL_INITIALIZING {
                            // Still initialising — keep spinning.
                            spin_hint();
                            continue;
                        }
                        if a != 0 {
                            // READY: the winner published a real pointer.
                            // SAFETY: `a != 0` (checked above) and `a !=
                            // SENTINEL_INITIALIZING` (the `if` above this one
                            // already returned/continued on that value), so
                            // `p` is neither null nor the sentinel — a real
                            // published pointer.
                            return Some(unsafe { NonNull::new_unchecked(p) });
                        }
                        // null: the winner rolled back after OOM. Break out of
                        // the spin and re-race the CAS from the top — do NOT
                        // keep waiting for a READY that will never be published.
                        break;
                    }
                    // Fall through to the outer loop: re-race.
                }
            }
        }
    }

    /// Test-probe introspection: `true` iff the cell is currently `READY`
    /// (holds a real, non-null, non-sentinel pointer). Says nothing about
    /// the published *value* itself (that is [`OncePtrCell::get`]'s
    /// contract).
    ///
    /// This is functionally identical to `get().is_some()` — same single
    /// `Acquire` load, same predicate, no capability `get` lacks — it does
    /// **not** avoid racing a concurrent init any differently than `get`
    /// does (an earlier version of this doc claimed
    /// otherwise). It exists as a named, self-documenting boolean
    /// introspection primitive: a caller writing `assert!(cell.dbg_is_ready())`
    /// reads as "assert the cell materialised" without an
    /// `.is_some()`/`.is_none()` match at the call site. The `sefer-alloc`
    /// allocator this crate was extracted from relies on exactly that: its
    /// own `Registry::dbg_chunk_is_materialised` forwards here to assert
    /// chunk-materialisation state in its regression tests.
    ///
    /// # Stability
    ///
    /// This is a deliberate, STABLE part of the public API — a
    /// `dbg_`-prefixed test-probe surface, not a hidden implementation
    /// detail. It carries the crate's normal semver guarantee like any
    /// other public item; a `#[doc(hidden)]` posture was rejected precisely
    /// because it would advertise this function to downstream consumers'
    /// tests (see [`OncePtrCell::dbg_rollback_reenterable`]'s own doc) while
    /// hiding it from the rustdoc those consumers would need to discover it
    /// — see the crate README's "Test-probe API stability" section for the
    /// full rationale.
    #[inline]
    #[must_use]
    pub fn dbg_is_ready(&self) -> bool {
        Self::is_ready(self.ptr.load(Ordering::Acquire))
    }

    /// Test-only anti-livelock rollback probe. Drives THIS cell through the
    /// exact `null -> sentinel -> rollback -> re-CAS` sequence the internal
    /// OOM-bailout runs, and proves the postcondition the whole design rests on:
    /// after a rollback, a fresh `CAS(null -> sentinel)` MUST succeed (the
    /// sentinel was genuinely cleared, so no future winner or spinning loser is
    /// wedged).
    ///
    /// Returns [`RollbackProbe::Proven`] if the rollback provably cleared the
    /// sentinel (the postcondition CAS re-won the cell; it is restored to
    /// `UNINIT` before returning). [`RollbackProbe::NotApplicable`] covers
    /// TWO distinct "could not test" cases, deliberately conflated because
    /// neither is evidence rollback is broken: (a) the cell was not observed
    /// `UNINIT` on the entry CAS (already `READY`, or another thread owned it
    /// at that instant), or (b) the postcondition CAS in step 3 failed
    /// because a real `get_or_try_init` caller raced in and re-won the cell
    /// during the probe's own rollback-then-reCAS window — in that case the
    /// probe leaves the cell alone (does NOT touch the new owner's state).
    ///
    /// **There is deliberately no "rollback is broken" variant.** This probe
    /// cannot distinguish that from "someone else legitimately owns the cell
    /// now" by construction — both look identical from here, the
    /// postcondition CAS simply fails either way — so the return type
    /// encodes exactly the two answers it can actually give, and no third
    /// one it could never produce.
    ///
    /// Exists so a consumer's test can drive the rollback on a REAL, LIVE cell
    /// (e.g. a process-global registry chunk) — proving the shipped code path,
    /// not a copy — without a process-terminating OOM. The whole probe is a
    /// bounded, single-threaded sequence of atomic ops; callers MUST pick a
    /// cell no other thread is concurrently initialising. The entry CAS is
    /// only a POINT-IN-TIME check, not mutual exclusion across the whole
    /// probe: if the cell is not observed `UNINIT` at that instant, the probe
    /// returns [`RollbackProbe::NotApplicable`] and touches nothing, but a
    /// concurrent
    /// [`OncePtrCell::get_or_try_init`] racing in AFTER the entry CAS (during
    /// the probe's own rollback-then-reCAS window) is not excluded by it — the
    /// probe's final restore step accounts for that by only touching the cell
    /// when its own postcondition CAS actually re-won ownership (see the
    /// step-by-step comments in the body).
    ///
    /// # Stability
    ///
    /// This is a deliberate, STABLE part of the public API, not
    /// `#[doc(hidden)]`. This function is explicitly written to be called
    /// FROM a downstream consumer's own test suite ("a consumer's test can
    /// drive the rollback on a REAL, LIVE cell" above) — a `#[doc(hidden)]`
    /// posture would have advertised it to those consumers while hiding it
    /// from the rustdoc they would need to find it in the first place, an
    /// unresolvable contradiction the crate's rust-intel audit caught. See
    /// the crate README's "Test-probe API stability" section for the full
    /// rationale and the rejected feature-flag alternative.
    #[must_use]
    pub fn dbg_rollback_reenterable(&self) -> RollbackProbe {
        // Step 1: only proceed if the cell is UNINIT (null). If it is already
        // READY or contended, do not touch it.
        if self
            .ptr
            .compare_exchange(
                core::ptr::null_mut(),
                Self::sentinel(),
                Ordering::Acquire,
                Ordering::Relaxed,
            )
            .is_err()
        {
            return RollbackProbe::NotApplicable;
        }

        // Step 2: run the EXACT rollback the internal OOM-bailout runs (sentinel
        // -> null, Release).
        self.ptr.store(core::ptr::null_mut(), Ordering::Release);

        // Step 3: prove the postcondition — a fresh CAS(null -> sentinel) must
        // now succeed.
        let postcondition_holds = self
            .ptr
            .compare_exchange(
                core::ptr::null_mut(),
                Self::sentinel(),
                Ordering::Acquire,
                Ordering::Relaxed,
            )
            .is_ok();

        // Step 4: restore to null, exactly as observed on entry — but ONLY if
        // step 3's CAS actually re-won ownership of the cell (postcondition
        // held). If it failed, the cell is not ours any more: a real
        // `get_or_try_init` caller raced in during the window between step 2's
        // rollback and step 3's CAS, won the CAS itself, and may already be
        // running (or have finished) the caller's init closure. Storing null
        // unconditionally here would clobber that other owner's sentinel (or
        // its published pointer) out from under it — the exact clobber this
        // probe must not cause. When we did not re-win, leave the cell alone
        // and report "not applicable" rather than a false rollback failure: a
        // concurrent owner racing in is not evidence that rollback itself is
        // broken.
        if !postcondition_holds {
            return RollbackProbe::NotApplicable;
        }
        self.ptr.store(core::ptr::null_mut(), Ordering::Release);

        RollbackProbe::Proven
    }
}

impl<T> Default for OncePtrCell<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T> core::fmt::Debug for OncePtrCell<T> {
    /// Diagnostic-only classification of the cell's current state — never
    /// dereferences the pointee, so no `T: Debug` bound is needed (`T` never
    /// appears in the output). `Relaxed` is enough here: unlike `get`, this
    /// never hands the pointer back to the caller to dereference, so there is
    /// no happens-before edge to establish. Like any concurrent type's
    /// `Debug` impl (`OnceLock`'s included), the state printed can be stale
    /// the instant after this call returns.
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let p = self.ptr.load(Ordering::Relaxed);
        f.write_str("OncePtrCell(")?;
        match p.addr() {
            0 => f.write_str("Uninit")?,
            SENTINEL_INITIALIZING => f.write_str("Initializing")?,
            _ => write!(f, "Ready({p:p})")?,
        }
        f.write_str(")")
    }
}