/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/runtime/sync.nv
2 655 строк
93 KB
Evgeniy Golovin
feat(248, wave 3): 11 атомиков на значение внутри (D447 #no_copy) + TcpStream.rc указателем
06 авг 2026, 06:16
06 авг 2026, 06:16
83788f7
Код
Авторство
О чём код?
// SPDX-License-Identifier: MIT OR Apache-2.0 // std.sync: fiber-aware synchronization primitives. // // AtomicInt — lock-free address-sized atomic integer (nova_int width, // intptr_t). Its SeqCst-default API covers the former // int32-precision legacy AtomicInt subset. // AtomicUint — lock-free address-sized unsigned atomic integer (nova_uint // width, uintptr_t). // AtomicBool — lock-free atomic boolean; swap() useful for one-shot ownership. // Mutex — fair FIFO fiber-aware mutex; park/wake via nova_sched. // WaitGroup — counter-based rendezvous; wait() parks until count == 0. // Once — exactly-once execution barrier; run()/done() pair. // MemOrdering — memory ordering enum for atomic ops and fences. // fence() — memory fence function; ordered by MemOrdering. // // Sync-class annotations: // #realtime — leaf method; allowed in realtime{} and blocking{}. // #parks — may park the calling fiber; forbidden in realtime{} and blocking{}. // #wakes — leaf wake (no self-park, but wakes waiters); forbidden in realtime{}. // // See compiler-codegen/nova_rt/sync_primitives.h for C implementations. module runtime.sync // ─── MemOrdering ───────────────────────────────────────────────── /// Memory ordering for atomic operations and fences. Controls the /// happens-before relationships between operations in different fibers. /// /// Default ordering for the simple-overload methods — `SeqCst` (design decision M1): /// safe for all use cases, can be overridden for perf-critical code via the /// `_ordered` overloads (Plan 103.2+). /// /// **Semantics** (D167): /// /// - `Relaxed`: atomicity of the operation only; no happens-before. /// Use case: telemetry counters, statistics. Cost: dirty reads /// possible cross-fiber, but no torn writes. /// - `Acquire`: for loads; establishes happens-before with a paired Release. /// Use case: load shared payload after seeing flag set. /// - `Release`: for stores; establishes happens-before with a paired Acquire. /// Use case: publish payload before setting flag. /// - `AcqRel`: for RMW (swap, CAS, fetch_*); combination Acquire+Release. /// Use case: lock-free queue head/tail; one-shot ownership transfer. /// - `SeqCst`: sequentially consistent — a total order over all SeqCst /// operations in all fibers. Use case: simple mental model, /// correctness without deep analysis. Default. /// /// **Note**: `MemOrdering` (memory ordering) differs from /// `Ordering` (three-way comparison: Less|Equal|Greater) in prelude. /// Different types, different uses. /// /// **Variant tag values** (sync_primitives.h coordination): /// Relaxed=0 Acquire=1 Release=2 AcqRel=3 SeqCst=4 #stable(since = "0.1") export type MemOrdering enum | Relaxed | Acquire | Release | AcqRel | SeqCst /// Memory fence — a sequenced ordering point without an atomic operation. /// /// Used to separate unsynchronized reads/writes from synchronized /// (publish/subscribe pattern, refcount-drop with deferred-free). /// /// **Semantics** (D167): /// /// - `fence(MemOrdering.Relaxed)`: no-op. Syntactically valid for /// consistency, but has no ordering effect (Acquire/Release needed). /// - `fence(MemOrdering.Acquire)`: all subsequent reads/writes /// happen-after all prior Release-stores (visible-to-this-fiber). /// - `fence(MemOrdering.Release)`: all prior reads/writes happen-before /// all subsequent Acquire-loads (visible-to-other-fibers). /// - `fence(MemOrdering.AcqRel)`: combination Acquire+Release. /// - `fence(MemOrdering.SeqCst)`: total-order participation; sequenced /// relative to all other SeqCst fences and SeqCst operations. /// /// # Example: refcount-drop with deferred-free /// /// ```nova /// // worker fiber: /// let prev = refcount.fetch_sub(1) // Release-dec (103.2) /// if prev == 1 { /// fence(MemOrdering.Acquire) // synchronize with all Release-decs /// // safe to access data — last fiber holds it /// destroy(data) /// } /// ``` #realtime #stable(since = "0.1") export extern "nova" fn fence(ord MemOrdering) // ─── CAS-witness raw carriers ─────────────────────────────────── // // `compare_exchange`/`compare_exchange_weak` return `Result[(), T]`: `Ok(())` // on success, `Err(actual)` on failure where `actual` is the value the C11 // atomic op observed (witness) — no extra `load()` needed to retry a CAS // loop. The private `@cmpxchg` intrinsic (module-private, not exported) // returns the raw (ok, witness) pair from ONE atomic op; each public // compare_exchange/_weak method below is a plain (non-extern) wrapper that // builds the `Result` from it. // // These `CasRaw*` types are declared for the type-checker only — the C // struct (`NovaTuple_CasRaw*`) is hand-written in `sync_primitives.h` and // pre-registered in `RUNTIME_DEFINED_TYPES` (emit_c.rs), so codegen does not // re-emit the struct body (same convention as `MutexGuard`/`MemOrdering`). type CasRawI64(ok bool, witness i64) type CasRawI32(ok bool, witness i32) type CasRawI16(ok bool, witness i16) type CasRawI8(ok bool, witness i8) type CasRawU64(ok bool, witness u64) type CasRawU32(ok bool, witness u32) type CasRawU16(ok bool, witness u16) type CasRawU8(ok bool, witness u8) type CasRawInt(ok bool, witness int) type CasRawUint(ok bool, witness uint) type CasRawBool(ok bool, witness bool) // ─── AtomicI64 ─────────────────────────────────────────────────── /// Lock-free 64-bit signed atomic integer. /// /// Wraparound: signed overflow in fetch_add/sub — modular arithmetic /// (consistent with Rust default, Java AtomicLong). /// /// # Default ordering /// /// Methods without a `MemOrdering` parameter use `SeqCst` (M1) — safest. /// For perf-critical code use the overloads with an explicit [`MemOrdering`]. /// /// # See Also /// /// - `[AtomicInt]` — address-sized (`intptr_t`) atomic; use when the width /// must track the platform word (indexes/offsets), not a fixed 64 bits /// - `[AtomicBool]` — atomic boolean /// - `[MemOrdering]` — ordering parameter enum #stable(since = "0.1") #share #no_copy export type AtomicI64 value priv { v i64 } #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64.new(v i64) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 @load(ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 @load() -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @store(v i64, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @store(v i64) #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @swap(v i64, ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @swap(v i64) -> i64 #realtime extern "nova" fn AtomicI64 mut @cmpxchg(expected i64, desired i64, weak bool, success MemOrdering, failure MemOrdering) -> CasRawI64 /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicI64 mut @compare_exchange(expected i64, desired i64, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), i64] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicI64 mut @compare_exchange_weak(expected i64, desired i64, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), i64] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_add(v i64, ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_add(v i64) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_sub(v i64, ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_sub(v i64) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_or(v i64, ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_or(v i64) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_and(v i64, ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_and(v i64) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_xor(v i64, ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_xor(v i64) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_max(v i64, ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_max(v i64) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_min(v i64, ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_min(v i64) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_nand(v i64, ord MemOrdering) -> i64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI64 mut @fetch_nand(v i64) -> i64 // ─── AtomicI32 ─────────────────────────────────────────────────── /// Lock-free 32-bit signed atomic integer. /// /// Most common signed atomic; fits in single cache slot. /// Wraparound: signed overflow — modular arithmetic. #stable(since = "0.1") #share #no_copy export type AtomicI32 value priv { v i32 } #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32.new(v i32) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 @load(ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 @load() -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @store(v i32, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @store(v i32) #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @swap(v i32, ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @swap(v i32) -> i32 #realtime extern "nova" fn AtomicI32 mut @cmpxchg(expected i32, desired i32, weak bool, success MemOrdering, failure MemOrdering) -> CasRawI32 /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicI32 mut @compare_exchange(expected i32, desired i32, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), i32] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicI32 mut @compare_exchange_weak(expected i32, desired i32, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), i32] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_add(v i32, ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_add(v i32) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_sub(v i32, ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_sub(v i32) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_or(v i32, ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_or(v i32) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_and(v i32, ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_and(v i32) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_xor(v i32, ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_xor(v i32) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_max(v i32, ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_max(v i32) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_min(v i32, ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_min(v i32) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_nand(v i32, ord MemOrdering) -> i32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI32 mut @fetch_nand(v i32) -> i32 // ─── AtomicI16 ─────────────────────────────────────────────────── /// Lock-free 16-bit signed atomic integer. /// /// For compact counters and sized flags. /// Wraparound: signed overflow — modular arithmetic. #stable(since = "0.1") #share #no_copy export type AtomicI16 value priv { v i16 } #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16.new(v i16) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 @load(ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 @load() -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @store(v i16, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @store(v i16) #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @swap(v i16, ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @swap(v i16) -> i16 #realtime extern "nova" fn AtomicI16 mut @cmpxchg(expected i16, desired i16, weak bool, success MemOrdering, failure MemOrdering) -> CasRawI16 /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicI16 mut @compare_exchange(expected i16, desired i16, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), i16] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicI16 mut @compare_exchange_weak(expected i16, desired i16, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), i16] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_add(v i16, ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_add(v i16) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_sub(v i16, ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_sub(v i16) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_or(v i16, ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_or(v i16) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_and(v i16, ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_and(v i16) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_xor(v i16, ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_xor(v i16) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_max(v i16, ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_max(v i16) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_min(v i16, ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_min(v i16) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_nand(v i16, ord MemOrdering) -> i16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI16 mut @fetch_nand(v i16) -> i16 // ─── AtomicI8 ──────────────────────────────────────────────────── /// Lock-free 8-bit signed atomic integer. /// /// Byte-sized flags and narrow counters. /// Wraparound: i8 [-128, 127]; overflow wraps modularly. #stable(since = "0.1") #share #no_copy export type AtomicI8 value priv { v i8 } #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8.new(v i8) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 @load(ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 @load() -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @store(v i8, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @store(v i8) #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @swap(v i8, ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @swap(v i8) -> i8 #realtime extern "nova" fn AtomicI8 mut @cmpxchg(expected i8, desired i8, weak bool, success MemOrdering, failure MemOrdering) -> CasRawI8 /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicI8 mut @compare_exchange(expected i8, desired i8, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), i8] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicI8 mut @compare_exchange_weak(expected i8, desired i8, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), i8] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_add(v i8, ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_add(v i8) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_sub(v i8, ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_sub(v i8) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_or(v i8, ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_or(v i8) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_and(v i8, ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_and(v i8) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_xor(v i8, ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_xor(v i8) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_max(v i8, ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_max(v i8) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_min(v i8, ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_min(v i8) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_nand(v i8, ord MemOrdering) -> i8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicI8 mut @fetch_nand(v i8) -> i8 // ─── AtomicU64 ─────────────────────────────────────────────────── /// Lock-free 64-bit unsigned atomic integer. /// /// Wide unsigned counters, timestamps. /// Wraparound: unsigned overflow wraps modularly. #stable(since = "0.1") #share #no_copy export type AtomicU64 value priv { v u64 } #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64.new(v u64) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 @load(ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 @load() -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @store(v u64, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @store(v u64) #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @swap(v u64, ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @swap(v u64) -> u64 #realtime extern "nova" fn AtomicU64 mut @cmpxchg(expected u64, desired u64, weak bool, success MemOrdering, failure MemOrdering) -> CasRawU64 /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicU64 mut @compare_exchange(expected u64, desired u64, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), u64] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicU64 mut @compare_exchange_weak(expected u64, desired u64, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), u64] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_add(v u64, ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_add(v u64) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_sub(v u64, ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_sub(v u64) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_or(v u64, ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_or(v u64) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_and(v u64, ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_and(v u64) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_xor(v u64, ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_xor(v u64) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_max(v u64, ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_max(v u64) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_min(v u64, ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_min(v u64) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_nand(v u64, ord MemOrdering) -> u64 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU64 mut @fetch_nand(v u64) -> u64 // ─── AtomicU32 ─────────────────────────────────────────────────── /// Lock-free 32-bit unsigned atomic integer. /// /// Bitset slots, refcount. Most common unsigned atomic. /// Wraparound: unsigned overflow wraps modularly. #stable(since = "0.1") #share #no_copy export type AtomicU32 value priv { v u32 } #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32.new(v u32) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 @load(ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 @load() -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @store(v u32, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @store(v u32) #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @swap(v u32, ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @swap(v u32) -> u32 #realtime extern "nova" fn AtomicU32 mut @cmpxchg(expected u32, desired u32, weak bool, success MemOrdering, failure MemOrdering) -> CasRawU32 /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicU32 mut @compare_exchange(expected u32, desired u32, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), u32] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicU32 mut @compare_exchange_weak(expected u32, desired u32, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), u32] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_add(v u32, ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_add(v u32) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_sub(v u32, ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_sub(v u32) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_or(v u32, ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_or(v u32) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_and(v u32, ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_and(v u32) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_xor(v u32, ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_xor(v u32) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_max(v u32, ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_max(v u32) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_min(v u32, ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_min(v u32) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_nand(v u32, ord MemOrdering) -> u32 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU32 mut @fetch_nand(v u32) -> u32 // ─── AtomicU16 ─────────────────────────────────────────────────── /// Lock-free 16-bit unsigned atomic integer. /// /// Sized unsigned counters. /// Wraparound: unsigned overflow wraps modularly. #stable(since = "0.1") #share #no_copy export type AtomicU16 value priv { v u16 } #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16.new(v u16) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 @load(ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 @load() -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @store(v u16, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @store(v u16) #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @swap(v u16, ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @swap(v u16) -> u16 #realtime extern "nova" fn AtomicU16 mut @cmpxchg(expected u16, desired u16, weak bool, success MemOrdering, failure MemOrdering) -> CasRawU16 /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicU16 mut @compare_exchange(expected u16, desired u16, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), u16] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicU16 mut @compare_exchange_weak(expected u16, desired u16, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), u16] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_add(v u16, ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_add(v u16) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_sub(v u16, ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_sub(v u16) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_or(v u16, ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_or(v u16) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_and(v u16, ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_and(v u16) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_xor(v u16, ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_xor(v u16) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_max(v u16, ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_max(v u16) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_min(v u16, ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_min(v u16) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_nand(v u16, ord MemOrdering) -> u16 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU16 mut @fetch_nand(v u16) -> u16 // ─── AtomicU8 ──────────────────────────────────────────────────── /// Lock-free 8-bit unsigned atomic integer (byte). /// /// Byte-sized unsigned flags, bitmap bytes. /// Wraparound: u8 [0, 255]; overflow wraps modularly. #stable(since = "0.1") #share #no_copy export type AtomicU8 value priv { v u8 } #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8.new(v u8) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 @load(ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 @load() -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @store(v u8, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @store(v u8) #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @swap(v u8, ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @swap(v u8) -> u8 #realtime extern "nova" fn AtomicU8 mut @cmpxchg(expected u8, desired u8, weak bool, success MemOrdering, failure MemOrdering) -> CasRawU8 /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicU8 mut @compare_exchange(expected u8, desired u8, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), u8] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicU8 mut @compare_exchange_weak(expected u8, desired u8, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), u8] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_add(v u8, ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_add(v u8) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_sub(v u8, ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_sub(v u8) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_or(v u8, ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_or(v u8) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_and(v u8, ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_and(v u8) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_xor(v u8, ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_xor(v u8) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_max(v u8, ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_max(v u8) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_min(v u8, ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_min(v u8) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_nand(v u8, ord MemOrdering) -> u8 #realtime #stable(since = "0.1") export extern "nova" fn AtomicU8 mut @fetch_nand(v u8) -> u8 // ─── AtomicInt ─────────────────────────────────────────────────── /// Lock-free platform-word signed atomic integer. /// /// `int` = `nova_int` = `intptr_t` (address-sized, Plan 133; on x64 /// it matches `int64_t` in width). Platform-word signed atomic. /// Useful for signed indexes and offsets. /// /// Plan 207 (2026-07-16 name consolidation): formerly called `Atomic` + /// `Isize` — renamed to `AtomicInt`, taking over the slot of the old legacy /// `AtomicInt` (an int32-backed stripped API without `MemOrdering` parameters, /// removed entirely; its calls (`new/load/store/fetch_add/fetch_sub/ /// compare_exchange` without ordering) are covered one-to-one by the default /// SeqCst overloads of this type). #stable(since = "0.1") #share #no_copy export type AtomicInt value priv { v int } #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt.new(v int) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt @load(ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt @load() -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @store(v int, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @store(v int) #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @swap(v int, ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @swap(v int) -> int #realtime extern "nova" fn AtomicInt mut @cmpxchg(expected int, desired int, weak bool, success MemOrdering, failure MemOrdering) -> CasRawInt /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicInt mut @compare_exchange(expected int, desired int, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), int] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicInt mut @compare_exchange_weak(expected int, desired int, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), int] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_add(v int, ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_add(v int) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_sub(v int, ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_sub(v int) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_or(v int, ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_or(v int) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_and(v int, ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_and(v int) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_xor(v int, ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_xor(v int) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_max(v int, ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_max(v int) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_min(v int, ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_min(v int) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_nand(v int, ord MemOrdering) -> int #realtime #stable(since = "0.1") export extern "nova" fn AtomicInt mut @fetch_nand(v int) -> int // ─── AtomicUint ────────────────────────────────────────────────── /// Lock-free platform-word unsigned atomic integer. /// /// `uint` = `nova_uint` = `uintptr_t` (address-sized, Plan 133/70.5; on /// x64 it matches `uint64_t` in width). Platform-word unsigned atomic. /// Useful for array indexes, buffer sizes. /// /// Plan 207 (2026-07-16 name consolidation): formerly called `Atomic` + /// `Usize` — renamed to `AtomicUint`. #stable(since = "0.1") #share #no_copy export type AtomicUint value priv { v uint } #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint.new(v uint) -> Self #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint @load(ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint @load() -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @store(v uint, ord MemOrdering) #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @store(v uint) #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @swap(v uint, ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @swap(v uint) -> uint #realtime extern "nova" fn AtomicUint mut @cmpxchg(expected uint, desired uint, weak bool, success MemOrdering, failure MemOrdering) -> CasRawUint /// Compare-and-swap (Plan 207 [M-cas-return-witnessed-value]): `Ok(())` — success /// (the value was replaced with `desired`). `Err(actual)` — failure, `actual` = the /// value actually read (witness), free from the C11 op — no extra `load()` in the CAS loop. #realtime #stable(since = "0.1") export fn AtomicUint mut @compare_exchange(expected uint, desired uint, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), uint] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak: may spuriously fail even if the current value == /// expected (ARM). The witness (`Err(actual)`) is correct in the spurious-failure /// case too — `actual == expected`, but the swap did not happen; retry the loop. #realtime #stable(since = "0.1") export fn AtomicUint mut @compare_exchange_weak(expected uint, desired uint, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), uint] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_add(v uint, ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_add(v uint) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_sub(v uint, ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_sub(v uint) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_or(v uint, ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_or(v uint) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_and(v uint, ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_and(v uint) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_xor(v uint, ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_xor(v uint) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_max(v uint, ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_max(v uint) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_min(v uint, ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_min(v uint) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_nand(v uint, ord MemOrdering) -> uint #realtime #stable(since = "0.1") export extern "nova" fn AtomicUint mut @fetch_nand(v uint) -> uint // ─── AtomicBool ────────────────────────────────────────────────── /// Lock-free atomic boolean flag. /// /// `swap()` is useful for the one-shot ownership pattern: /// `if !flag.swap(true) { /* only the first caller executes */ }`. /// /// Ordering-aware overloads added in Plan 103.2. /// Methods without a `MemOrdering` use `SeqCst` (M1) — safest default. /// /// # See Also /// /// - `[AtomicInt]` — address-sized integer atomic /// - `[AtomicI64]` — fixed 64-bit integer atomic /// - `[Once]` — barrier for exactly-once execution /// - `[MemOrdering]` — ordering parameter enum #stable(since = "0.1") #share #no_copy export type AtomicBool value priv { v bool } #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool.new(v bool) -> Self /// Load the current value with an explicit ordering. #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool @load(ord MemOrdering) -> bool /// Load the current value (SeqCst ordering). #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool @load() -> bool /// Store a new value with an explicit ordering. #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @store(v bool, ord MemOrdering) /// Store a new value (SeqCst ordering). #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @store(v bool) #realtime extern "nova" fn AtomicBool mut @cmpxchg(expected bool, desired bool, weak bool, success MemOrdering, failure MemOrdering) -> CasRawBool /// Compare-and-swap with explicit orderings (Plan 207 [M-cas-return-witnessed-value]). /// `Ok(())` — success. `Err(actual)` — failure, `actual` = the witness (the value /// actually read, free from the C11 op). #realtime #stable(since = "0.1") export fn AtomicBool mut @compare_exchange(expected bool, desired bool, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), bool] { ro raw = @cmpxchg(expected, desired, false, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Compare-and-swap weak with explicit orderings. May spuriously fail on ARM — /// the witness (`Err(actual)`) is correct in the spurious-failure case too. #realtime #stable(since = "0.1") export fn AtomicBool mut @compare_exchange_weak(expected bool, desired bool, success MemOrdering = MemOrdering.SeqCst, failure MemOrdering = MemOrdering.SeqCst) -> Result[(), bool] { ro raw = @cmpxchg(expected, desired, true, success, failure) if raw.ok { Ok(()) } else { Err(raw.witness) } } /// Atomically set `v`, return the previous value with an explicit ordering. #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @swap(v bool, ord MemOrdering) -> bool /// Atomically set `v`, return the previous value (SeqCst ordering). /// /// Useful for one-shot ownership: `if !flag.swap(true) { /* first */ }`. #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @swap(v bool) -> bool /// Atomic bitwise-OR, returns previous value. With explicit ordering. #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @fetch_or(v bool, ord MemOrdering) -> bool /// Atomic bitwise-OR, returns previous value (SeqCst ordering). #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @fetch_or(v bool) -> bool /// Atomic bitwise-AND, returns previous value. With explicit ordering. #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @fetch_and(v bool, ord MemOrdering) -> bool /// Atomic bitwise-AND, returns previous value (SeqCst ordering). #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @fetch_and(v bool) -> bool /// Atomic bitwise-XOR, returns previous value. With explicit ordering. #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @fetch_xor(v bool, ord MemOrdering) -> bool /// Atomic bitwise-XOR, returns previous value (SeqCst ordering). #realtime #stable(since = "0.1") export extern "nova" fn AtomicBool mut @fetch_xor(v bool) -> bool // ─── Consume guard types ─────────────────────────────────────── // // Guard types are consume-typed: the holding fiber // must explicitly call the consume method (unlock/release/commit/abort) // OR the compiler enforces via scope-exit obligation. // // C structs are pre-declared in sync_primitives.h (RUNTIME_DEFINED_TYPES). // Nova type declarations here are for type-checker only. /// Guard returned by `Mutex.lock()`. Consume-typed: must call `.unlock()`. /// /// # Examples /// /// ```nova /// consume g = mu.lock() /// defer g.unlock() // released even on panic/throw /// // critical section /// ``` #stable(since = "0.1") export type MutexGuard consume { ptr int } /// Guard returned by `RwLock.read()`. Consume-typed: must call `.unlock()`. #stable(since = "0.1") export type ReadGuard consume { ptr int } /// Guard returned by `RwLock.write()`. Consume-typed: must call `.unlock()`. #stable(since = "0.1") export type WriteGuard consume { ptr int } /// Permit returned by `Semaphore.acquire()`. Consume-typed: must call `.release()`. /// /// # Examples /// /// ```nova /// consume p = sem.acquire() /// defer p.release() /// // use resource /// ``` #stable(since = "0.1") export type Permit consume { ptr int } /// Guard returned by `Once.start()`. Consume-typed: must call `.commit()` or `.abort()`. /// /// **Auto-`@cleanup` considered and rejected.** /// Unlike `MutexGuard`/`ReadGuard`/`WriteGuard`/`Permit`/`TcpStream` (a single /// idempotent release action, safe to silently auto-run), `OnceGuard`'s two /// discharge methods are NOT interchangeable: `.commit()` permanently /// transitions `Once` to `DONE` (skips setup forever after), `.abort()` to /// `POISONED` (all future callers re-panic). Auto-cleanup can only /// react to `ScopeOutcome` (`Success`/`Failure`/`Panic`), not to *why* a /// `Success`-looking path never reached `.commit()` — e.g. an early `return` /// after a non-throwing failure check would auto-resolve to `Success` and /// silently `.commit()` a `Once` whose setup never actually completed, a /// permanent, unrecoverable false-positive (no code path ever retries a /// `DONE` `Once`). Contrast `Transaction`/`Db` (canonical /// `@cleanup` types): a spurious auto-`commit()` there just costs a later, /// recoverable rollback — `OnceGuard` has no such safety net. Kept strictly /// linear: the explicit `.commit()`/`.abort()` choice stays mandatory. /// Separately, a real fiber panic before either call today leaks the `Once` /// in `IN_PROGRESS` forever (deadlocks all waiters) — a pre-existing gap, /// NOT closed by this rollout (would need an asymmetric "auto-abort-only-on- /// Failure/Panic, still-mandatory-explicit-on-Success" mechanism that /// `Cleanup[never]`'s single uniform method can't express). /// /// # Examples /// /// ```nova /// match once.start() { /// Some(consume g) => { /// setup() /// g.commit() // Once → DONE /// } /// None => {} // already done or in progress /// } /// ``` #stable(since = "0.1") export type OnceGuard consume { ptr int } // ─── MutexGuard methods ────────────────────────────────────────── /// Release the mutex lock. The guard is consumed (single-use). /// /// Called automatically by `with_lock(fn)` wrapper. /// Prefer `defer g.unlock()` over manual placement for panic-safety. #wakes #stable(since = "0.1") export extern "nova" fn MutexGuard consume @unlock() /// Cleanup[never]: cleanup calls unlock. Used by /// `consume g = mu.acquire() { body }` scope-block automatic cleanup. /// Infallible (never throws); same C impl as unlock. #wakes #stable(since = "0.1") export extern "nova" fn MutexGuard consume @cleanup(_outcome ScopeOutcome) -> () // ─── ReadGuard methods ─────────────────────────────────────────── /// Release the read lock. The guard is consumed (single-use). #wakes #stable(since = "0.1") export extern "nova" fn ReadGuard consume @unlock() /// Cleanup[never]: cleanup calls unlock. #wakes #stable(since = "0.1") export extern "nova" fn ReadGuard consume @cleanup(_outcome ScopeOutcome) -> () // ─── WriteGuard methods ────────────────────────────────────────── /// Release the write lock. The guard is consumed (single-use). #wakes #stable(since = "0.1") export extern "nova" fn WriteGuard consume @unlock() /// Cleanup[never]: cleanup calls unlock. #wakes #stable(since = "0.1") export extern "nova" fn WriteGuard consume @cleanup(_outcome ScopeOutcome) -> () // ─── Permit methods ────────────────────────────────────────────── /// Release the semaphore permit. The permit is consumed (single-use). /// /// Wakes the next FIFO waiter if any are queued. #wakes #stable(since = "0.1") export extern "nova" fn Permit consume @release() /// Cleanup[never]: cleanup calls release. #wakes #stable(since = "0.1") export extern "nova" fn Permit consume @cleanup(_outcome ScopeOutcome) -> () // ─── OnceGuard methods ─────────────────────────────────────────── /// Mark once body as successfully completed → Once transitions to DONE. /// /// Wakes all parked waiters. They unblock and see `is_completed() == true`. #wakes #stable(since = "0.1") export extern "nova" fn OnceGuard consume @commit() /// Mark once body as failed → Once transitions to POISONED. /// /// Wakes all parked waiters. They unblock and re-panic (OncePoisoned). /// After abort, subsequent `start()` calls also panic. #wakes #stable(since = "0.1") export extern "nova" fn OnceGuard consume @abort() // ─── Mutex ─────────────────────────────────────────────────────── /// Fair FIFO fiber-aware mutex. /// /// Park/wake via `nova_sched` — a blocked fiber does not consume CPU. /// NOT reentrant: a repeated `lock()` from the same fiber deadlocks. /// /// # Examples /// /// ```nova /// let mut mu = Mutex.new() /// mu.lock() /// // critical section /// mu.unlock() /// ``` /// /// # See Also /// /// - `[WaitGroup]` — waiting for several fibers to finish /// - `[Once]` — exactly-once execution barrier /// /// **`#share` audited vouch.** `Mutex` wraps a raw /// runtime handle (opaque-handle newtype `type Mutex(*())`, same shape as /// `Condvar`/`OnceCell`/`Lazy`) — the poison /// base by construction. `#share` is the author's vouch that the handle IS /// real synchronization (a fair FIFO fiber-aware lock) the auto-derive /// engine can't see through the opaque pointer: safe to alias/capture across /// a `spawn`/`parallel for` boundary without a data race. Exactly the /// mechanism a user lock-free type uses — `Mutex` is not special-cased. #stable(since = "0.1") #share export type Mutex(*()) #realtime #stable(since = "0.1") export extern "nova" fn Mutex.new() -> Self /// Acquire the mutex. Parks the calling fiber until released. /// /// V2 (Plan 103.9, D174): returns `MutexGuard consume` — statically /// enforced release via the consume-type system. Old callers that discard /// the return value (`mu.lock()` as statement) continue to compile; /// only `consume g = mu.lock()` creates a consumption obligation. /// /// NOT reentrant — a repeated call from the same fiber deadlocks. #parks #stable(since = "0.1") export extern "nova" fn Mutex @lock() -> consume MutexGuard /// (Deprecated) Release the mutex without a guard. /// /// V2: Use `MutexGuard.unlock()` (returned by `lock()`) instead. /// Bare `unlock()` remains for V1 backward compat with `#deprecated` warning. #deprecated(since = "0.2", note = "use consume g = mu.lock(); defer g.unlock() instead (Plan 103.9 guard API)") #wakes #stable(since = "0.1") export extern "nova" fn Mutex @unlock() /// Try to acquire the mutex without blocking. /// /// Returns `true` if the lock was acquired, `false` if it is held. #realtime #stable(since = "0.1") export extern "nova" fn Mutex @try_lock() -> bool // ─── Mutex extensions ────────────────────────────────────────── /// Create a mutex with a LIFO (unfair) release policy. /// /// Use only after benchmarking — starvation is possible. /// Advantage: fewer context switches in high-contention scenarios /// with short critical sections. /// /// **PREFERRED PATTERN (M15):** use `with_lock { }` instead of lock/unlock. #realtime #stable(since = "0.1") export extern "nova" fn Mutex.new_unfair() -> Self /// Try to acquire the mutex within a timeout. /// /// Returns `true` if the lock was acquired, `false` if the timeout expired. /// timeout <= 0 — equivalent to `try_lock()`. /// /// W_TRY_WITHOUT_SIBLING AMEND (2026-07-17, lint went red, owner): /// renamed from `try_lock_for` — a `try_`-prefix is legal only as the /// fallible half of a same-named infallible (`from`/`try_from`, D77); /// `lock_for` has no infallible sibling with this name (`lock()` — a different /// arity/semantics, not the timeout variant) — a solo operation without a Result /// takes the plain name (R3 D325, nv-coding-style §1). The `bool` return is unchanged. /// /// # Examples /// /// ```nova /// let mut mu = Mutex.new() /// if mu.lock_for(100.to_millis()) { /// defer mu.unlock() /// // critical section /// } /// ``` /// /// # Note (realtime) /// /// Inside `realtime { }` blocks the compiler emits W_REALTIME_TRY_LOCK_FOR_TIMER /// because lock_for uses a libuv timer that may park even for short timeouts. /// Annotated `#realtime` (not `#parks`) because failure path is non-parking; /// compiler enforces via dedicated warning, not an error. (Diagnostic ID kept /// as `W_REALTIME_TRY_LOCK_FOR_TIMER`; scope here is the method name only.) #realtime #stable(since = "0.1") export extern "nova" fn Mutex @lock_for(timeout Duration) -> bool /// Best-effort state check. NOT for CAS patterns. /// /// The state can change between the call and the use of the result. /// Use for observability (debugging, metrics), not for synchronization. #realtime #stable(since = "0.1") export extern "nova" fn Mutex @is_locked() -> bool /// PREFERRED PATTERN (M15). Acquire + automatic release via `defer` — the /// unlock will never be forgotten even on a panic in the body. /// /// V2 (Plan 103.9): a thin wrapper over `MutexGuard consume`; user code /// is unchanged. /// /// # Examples /// /// ```nova /// let mut mu = Mutex.new() /// let result = mu.with_lock { || /// compute_under_lock() /// } /// ``` /// /// # See Also /// /// - `[RwLock.with_read]` / `[RwLock.with_write]` — the RwLock analogue /// - `[ReentrantMutex.with_lock]` — the ReentrantMutex analogue #parks #stable(since = "0.1") export fn Mutex @with_lock[R](body fn() -> R) -> R { consume guard = self.lock() defer guard.unlock() body() } // ─── RwLock ───────────────────────────────────────────────────── /// Fiber-aware reader-writer lock. /// /// Default: writer-priority (prevents writer starvation). /// Multiple readers may hold the lock simultaneously. /// Only one writer, no readers. /// /// **PREFERRED PATTERN (M15):** use `with_read { }` / `with_write { }`. /// /// # Examples /// /// ```nova /// let mut rw = RwLock.new() /// rw.with_read { || /// let v = shared_data /// print(v) /// } /// rw.with_write { || /// shared_data = 42 /// } /// ``` /// /// # See Also /// /// - `[Mutex]` — exclusive access (simpler, less overhead) /// - `[ReentrantMutex]` — recursive mutex /// /// `#share` audited vouch — same reasoning as `Mutex`. #stable(since = "0.1") #share export type RwLock(*()) #realtime #stable(since = "0.1") export extern "nova" fn RwLock.new() -> Self /// Reader-priority opt-in. Readers are not blocked by waiting writers. /// /// **Caution:** writer starvation is possible under a continuous stream of readers. /// Use only in read-heavy scenarios after benchmarking. #realtime #stable(since = "0.1") export extern "nova" fn RwLock.new_reader_priority() -> Self /// Acquire the read lock. Parks if a writer is active or waiting (writer-priority). /// /// V2: returns `ReadGuard consume`. Use `guard.unlock()` to release. /// Old callers that discard the return value still compile. #parks #stable(since = "0.1") export extern "nova" fn RwLock @read() -> consume ReadGuard /// (Deprecated) Release the read lock without a guard. /// /// V2: use `ReadGuard.unlock()` (returned by `read()`) instead. #deprecated(since = "0.2", note = "use consume g = rw.read(); defer g.unlock() instead (Plan 103.9 guard API)") #wakes #stable(since = "0.1") export extern "nova" fn RwLock @read_unlock() /// Try to acquire the read lock without blocking. #realtime #stable(since = "0.1") export extern "nova" fn RwLock @try_read() -> bool /// Try to acquire the read lock within a timeout. /// /// W_TRY_WITHOUT_SIBLING AMEND (2026-07-17, lint went red, owner): /// renamed from `try_read_for` — a `try_`-prefix is legal only as the /// fallible half of a same-named infallible (D77); `read_for` with no /// infallible sibling of this name — a plain name, without `try_` (R3 D325). #parks #stable(since = "0.1") export extern "nova" fn RwLock @read_for(timeout Duration) -> bool /// Acquire the write lock. Waits for all readers and other writers to finish. /// /// V2: returns `WriteGuard consume`. Use `guard.unlock()` to release. /// Old callers that discard the return value still compile. #parks #stable(since = "0.1") export extern "nova" fn RwLock @write() -> consume WriteGuard /// (Deprecated) Release the write lock without a guard. /// /// V2: use `WriteGuard.unlock()` (returned by `write()`) instead. #deprecated(since = "0.2", note = "use consume g = rw.write(); defer g.unlock() instead (Plan 103.9 guard API)") #wakes #stable(since = "0.1") export extern "nova" fn RwLock @write_unlock() /// Try to acquire the write lock without blocking. #realtime #stable(since = "0.1") export extern "nova" fn RwLock @try_write() -> bool /// Try to acquire the write lock within a timeout. /// /// W_TRY_WITHOUT_SIBLING AMEND (2026-07-17, lint went red, owner): /// renamed from `try_write_for` — the same retraction as `read_for` above. #parks #stable(since = "0.1") export extern "nova" fn RwLock @write_for(timeout Duration) -> bool /// Current number of active readers. Best-effort. #realtime #stable(since = "0.1") export extern "nova" fn RwLock @reader_count() -> int /// `true` if the write lock is active. Best-effort. #realtime #stable(since = "0.1") export extern "nova" fn RwLock @is_write_locked() -> bool /// Read acquisition with automatic release via defer. /// /// **PREFERRED PATTERN (M15).** V2 (Plan 103.9): a thin wrapper over `ReadGuard consume`. #parks #stable(since = "0.1") export fn RwLock @with_read[R](body fn() -> R) -> R { consume guard = self.read() defer guard.unlock() body() } /// Write acquisition with automatic release via defer. /// /// **PREFERRED PATTERN (M15).** V2 (Plan 103.9): a thin wrapper over `WriteGuard consume`. #parks #stable(since = "0.1") export fn RwLock @with_write[R](body fn() -> R) -> R { consume guard = self.write() defer guard.unlock() body() } // ─── ReentrantMutex ───────────────────────────────────────────── /// Recursive mutex: the same fiber may lock() multiple times without deadlock. /// /// Tracks the owner-fiber + recursion depth. unlock() must be called as /// many times as lock(). /// /// **Prefer `[Mutex]` by default** — it detects a double-lock error /// immediately. ReentrantMutex is for migrating legacy code. /// /// **Interaction with Condvar (Plan 103.4):** `Condvar.wait()` releases the /// ENTIRE lock (count → 0), a wake re-acquires with count=1. /// AI-diagnostic `W_REENTRANT_CONDVAR_RECOMMEND` on mixing. /// /// **PREFERRED PATTERN (M15):** use `with_lock { }`. /// /// # Examples /// /// ```nova /// let mut rm = ReentrantMutex.new() /// rm.lock() /// rm.lock() // same fiber — no deadlock /// rm.unlock() /// rm.unlock() // released when count = 0 /// ``` /// /// `#share` audited vouch — same reasoning as `Mutex`. #stable(since = "0.1") #share export type ReentrantMutex(*()) #realtime #stable(since = "0.1") export extern "nova" fn ReentrantMutex.new() -> Self /// Acquire the lock. If the same fiber — increment the recursion count. #parks #stable(since = "0.1") export extern "nova" fn ReentrantMutex mut @lock() /// Release the lock. Decrement count; release when count = 0. /// /// Panics if called not from the owner fiber or if the mutex is not locked. #wakes #stable(since = "0.1") export extern "nova" fn ReentrantMutex mut @unlock() /// Try to acquire without blocking. #realtime #stable(since = "0.1") export extern "nova" fn ReentrantMutex mut @try_lock() -> bool /// Try to acquire within a timeout. /// /// W_TRY_WITHOUT_SIBLING AMEND (2026-07-17, lint went red, owner): /// renamed from `try_lock_for` — the same retraction as `Mutex.lock_for`. #parks #stable(since = "0.1") export extern "nova" fn ReentrantMutex mut @lock_for(timeout Duration) -> bool /// Recursion depth for the current fiber. 0 if not the owner. #realtime #stable(since = "0.1") export extern "nova" fn ReentrantMutex @lock_count() -> int /// Acquisition with automatic release via defer. /// /// **PREFERRED PATTERN.** #parks #stable(since = "0.1") export fn ReentrantMutex mut @with_lock[R](body fn() -> R) -> R { self.lock() defer self.unlock() body() } // ─── WaitGroup ─────────────────────────────────────────────────── /// Counter-based rendezvous — waits for a group of fibers to finish. /// /// Usage pattern: `add(n)` before spawn, `done()` in each fiber, /// `wait()` in the coordinating fiber. /// /// # Examples /// /// ```nova /// let mut wg = WaitGroup.new() /// wg.add(3) /// spawn { work1(); wg.done() } /// spawn { work2(); wg.done() } /// spawn { work3(); wg.done() } /// wg.wait() /// ``` /// /// # See Also /// /// - `[Mutex]` — mutual exclusion /// - `[Once]` — exactly-once barrier /// /// `#share` audited vouch — same reasoning as `Mutex`. /// (Required for the `spawn` example right above: `wg` captured by reference /// into sibling `spawn` bodies is exactly the capture-check's baseline /// "safe" case.) #stable(since = "0.1") #share export type WaitGroup(*()) #realtime #stable(since = "0.1") export extern "nova" fn WaitGroup.new() -> Self /// Increment the counter by `delta`. Call before spawning workers. #realtime #stable(since = "0.1") export extern "nova" fn WaitGroup mut @add(delta int) /// Decrement the counter by 1. Wakes all `wait()` if the counter reached 0. /// /// Must not be called more times than were passed to `add()` in total. #wakes #stable(since = "0.1") export extern "nova" fn WaitGroup mut @done() /// Park the calling fiber until the counter reaches zero. #parks #stable(since = "0.1") export extern "nova" fn WaitGroup @wait() // ─── OnceState ─────────────────────────────────────────────────── /// State of a `Once` execution barrier. /// /// Variants: `Fresh` (not started), `Running` (init in progress), /// `Done` (completed successfully), `Poisoned` (init panicked). /// /// C representation pre-declared in sync_primitives.h. #stable(since = "0.1") export type OnceState enum | Fresh | Running | Done | Poisoned // ─── Once ─────────────────────────────────────────────────────── /// Exactly-once execution barrier with panic-safe closure API. /// /// `call_once(fn)` is the primary API: executes the closure exactly /// once across all concurrent callers. If the closure panics, Once /// transitions to `Poisoned` state; subsequent `call_once` calls also /// panic. Use `is_completed()` / `state()` for observability. /// /// Legacy `run()`/`done()` pair is deprecated — use `call_once` instead. /// /// # Examples /// /// ```nova /// let init = Once.new() /// init.call_once { || setup() } /// assert(init.is_completed() == true) /// ``` /// /// # See Also /// /// - `[OnceCell]` — value-capturing one-time init /// - `[Lazy]` — auto-init on first access /// /// `#share` audited vouch — same reasoning as `Mutex`. #stable(since = "0.1") #share export type Once(*()) #realtime #stable(since = "0.1") export extern "nova" fn Once.new() -> Self /// Execute `body` exactly once across all concurrent callers. /// /// If `body` panics, Once transitions to `Poisoned` state. /// Subsequent `call_once` calls re-panic (OncePoisoned). /// All waiters are woken on completion or panic. #parks #stable(since = "0.1") export extern "nova" fn Once mut @call_once(body fn() -> ()) /// Returns `true` if Once has successfully completed (state == Done). #realtime #stable(since = "0.1") export extern "nova" fn Once @is_completed() -> bool /// Returns the current state of the Once barrier. #realtime #stable(since = "0.1") export extern "nova" fn Once @state() -> OnceState /// (Deprecated) Attempt to begin once execution. /// /// Returns `true` if this fiber should execute the once-body. /// Deprecated: use `call_once(fn)` or `start()` instead (guard API). #deprecated(since = "0.1", note = "use call_once(fn) or start() instead (Plan 103.9 guard API)") #parks #stable(since = "0.1") export extern "nova" fn Once mut @run() -> bool /// (Deprecated) Mark once body as complete. /// /// Must be called exactly once by the fiber whose `run()` returned `true`. /// Deprecated: use `OnceGuard.commit()` (via `start()` guard API) instead. #deprecated(since = "0.1", note = "use OnceGuard.commit() via start() instead (Plan 103.9 guard API)") #wakes #stable(since = "0.1") export extern "nova" fn Once mut @done() // ─── Once V2 guard API ────────────────────────────────────────── /// (Internal) Returns `true` if this fiber won the race to initialize. /// /// Same state machine as `run()`. Used by `start()` body to check /// whether to allocate an `OnceGuard`. Not for direct use — prefer `start()`. /// /// W_TRY_WITHOUT_SIBLING AMEND (2026-07-17, lint went red, owner): /// renamed from `try_start_won` — a solo racy operation with no infallible /// sibling takes the plain name, without a `try_`-prefix (R3 D325). #parks #stable(since = "0.1") export extern "nova" fn Once mut @start_won() -> bool /// (Internal) Allocate a `OnceGuard` referencing this `Once`. /// /// Called by `start()` after `start_won()` returns `true`. /// Not for direct use — the guard is opaque; call `.commit()` or `.abort()`. #realtime #stable(since = "0.1") export extern "nova" fn Once mut @make_guard() -> consume OnceGuard /// Attempt to start once initialization. Returns `Some(guard)` to the winning /// fiber; `None` to subsequent callers (who park until DONE or re-panic if POISONED). /// /// W_TRY_WITHOUT_SIBLING AMEND (2026-07-17, lint went red, owner): /// renamed from `try_start` — the same retraction as `start_won` above. /// /// # Examples /// /// ```nova /// let mut once = Once.new() /// match once.start() { /// Some(consume g) => { /// // This fiber won — do the work /// setup_resources() /// g.commit() // Once → DONE; wakes waiters /// } /// None => {} // Already done (or in progress; wait handled internally) /// } /// ``` /// /// # Panic safety /// /// If the winning fiber panics before calling `g.commit()` or `g.abort()`, /// the `OnceGuard` is not consumed — the consume-checker enforces an explicit /// call to `commit()` or `abort()` on every code path. /// `abort()` transitions Once to POISONED; subsequent `start()` callers /// will re-panic. /// /// # See Also /// /// - `[Once.call_once]` — closure-based API with automatic panic safety #parks #stable(since = "0.1") export fn Once mut @start() -> Option[OnceGuard consume] { if self.start_won() { Some(self.make_guard()) } else { None } } // ─── OnceCell[T] ───────────────────────────────────────────────── /// A cell which can be written to at most once. /// /// Supports reading before initialization (returns `None`), setting /// once, and get-or-init with exactly-once semantics. After `take()`, /// the cell is empty again and can be set once more. /// /// Panic in `get_or_init` closure leaves cell empty (retry allowed), /// unlike `Lazy` which poisons on panic. /// /// # See Also /// /// - `[Once]` — once-only execution without a value /// - `[Lazy]` — auto-init wrapper (poisons on panic) /// /// Opaque-handle newtype `type OnceCell[T](*())`. C struct + per-T methods /// are emitted by codegen (emit_oncecell_instance); ABI unchanged. /// `#share` audited vouch — exactly-once init IS /// internal synchronization (same reasoning as `Once`/`Mutex`). #stable(since = "0.1") #share export type OnceCell[T](*()) /// Create an empty OnceCell. #realtime #stable(since = "0.1") export extern "nova" fn OnceCell[T].new() -> Self /// Get the value if initialized, or `None` if empty. #realtime #stable(since = "0.1") export extern "nova" fn OnceCell[T] @get() -> Option[T] /// Set the value if the cell is empty. /// /// Returns `true` on success, `false` if already initialized. #realtime #stable(since = "0.1") export extern "nova" fn OnceCell[T] mut @set(v T) -> bool /// Get the value, initializing with `init` if empty. /// /// `init` runs at most once; concurrent callers park until init /// completes. On panic in `init`, cell stays empty (retry allowed). #parks #stable(since = "0.1") export extern "nova" fn OnceCell[T] mut @get_or_init(init fn() -> T) -> T /// Take the value out, leaving the cell empty. /// /// Returns `Some(v)` if initialized, `None` if already empty. /// After take(), the cell can be set or initialized again. #realtime #stable(since = "0.1") export extern "nova" fn OnceCell[T] mut @take() -> Option[T] /// Returns `true` if the cell has been initialized. #realtime #stable(since = "0.1") export extern "nova" fn OnceCell[T] @is_initialized() -> bool // ─── Lazy[T] ───────────────────────────────────────────────────── /// A value that is lazily initialized on first `force()`. /// /// The init closure runs exactly once; all concurrent callers of /// `force()` receive the same value. If the init closure panics, /// `Lazy` is poisoned — subsequent `force()` calls re-panic /// (unlike `OnceCell.get_or_init` which allows retry). /// /// # Examples /// /// ```nova /// let lazy = Lazy[int].new() { expensive_computation() } /// let v = lazy.force() // runs init if first call /// ``` /// /// # See Also /// /// - `[OnceCell]` — finer-grained control (set/take/retry) /// - `[Once]` — once-only execution without a value /// /// Opaque-handle newtype `type Lazy[T](*())`. C struct + per-T methods /// are emitted by codegen (emit_lazy_instance); ABI unchanged. /// `#share` audited vouch — exactly-once init IS internal synchronization /// (same reasoning as `Once`/`Mutex`). #stable(since = "0.1") #share export type Lazy[T](*()) /// Create a Lazy[T] with the given initializer closure. #realtime #stable(since = "0.1") export extern "nova" fn Lazy[T].new(init fn() -> T) -> Self /// Force initialization and return the value. /// /// If already initialized, returns the cached value without running init. /// On panic in init, Lazy is poisoned; subsequent calls re-panic. #parks #stable(since = "0.1") export extern "nova" fn Lazy[T] mut @force() -> T /// Returns `true` if the value has been successfully initialized. #realtime #stable(since = "0.1") export extern "nova" fn Lazy[T] @is_forced() -> bool // ──────────────────────────────────────────────────────────────── // Coordination primitives // ──────────────────────────────────────────────────────────────── // === Barrier === /// Reusable N-party rendezvous (CyclicBarrier-style). /// /// Cycles automatically: after `parties` fibers call `wait()`, the barrier /// resets for the next round. Generation counter distinguishes rounds. /// /// # Use cases /// - Epoch-based parallel computation (all workers complete phase N before N+1) /// - Synchronized startup (all workers initialized before first task) /// /// # Broken state /// A barrier becomes broken when `wait_for()` times out or `reset()` is called /// while parties are waiting. Broken barrier → `wait()` panics immediately. /// Call `reset()` to repair a broken barrier. /// /// # Memory model /// Allocated uncollectable (GC-race fix). /// /// `#share` audited vouch — same reasoning as `Mutex`. #stable(since = "0.1") #share export type Barrier(*()) #realtime #stable(since = "0.1") export extern "nova" fn Barrier.new(parties int) -> Self /// Park until all `parties` fibers reach the barrier. Returns arrival index /// (0..parties-1). The last-arrival party gets index `parties-1`. /// /// Panics if the barrier is currently broken. Use `reset()` to repair. #parks #stable(since = "0.1") export extern "nova" fn Barrier mut @wait() -> int /// Like `wait()` but the last-arrival fiber executes `action` before waking /// other waiters. Useful for cross-epoch state reset. /// /// `action` runs after the barrier state is updated but before other fibers /// are woken — they see its effects when they resume. #parks #stable(since = "0.1") export extern "nova" fn Barrier mut @wait_with_action(action fn() -> ()) -> int /// Like `wait()` but returns `None` if the timeout expires before all parties /// arrive. A timeout breaks the barrier — all current waiters are woken with /// broken state; future `wait()` calls panic until `reset()`. #parks #stable(since = "0.1") export extern "nova" fn Barrier mut @wait_for(timeout Duration) -> Option[int] /// Returns `true` if the barrier is currently broken. /// Best-effort (no mutex); state may change concurrently. #realtime #stable(since = "0.1") export extern "nova" fn Barrier @is_broken() -> bool /// Force-reset the barrier. All current waiters are woken with broken state /// (their `wait()` calls panic). Resets `arrived_count=0`, increments /// generation, clears `broken` flag — ready for fresh use. #wakes #stable(since = "0.1") export extern "nova" fn Barrier mut @reset() // === End Barrier === // === Condvar === // ─── WaitResult ────────────────────────────────────────────────── /// Return type for `Condvar.wait_for()`. /// /// # Variants /// - `Notified` — fiber woken by `notify_one()` or `notify_all()` /// - `TimedOut` — timeout expired without notification /// /// # See Also /// - `[Condvar.wait_for]` #stable(since = "0.1") export type WaitResult enum | Notified | TimedOut // ─── Condvar ───────────────────────────────────────────────────── /// Fiber-aware condition variable. Tied to `Mutex`. /// /// Allows fibers to park until a predicate on shared state becomes true, /// without busy-waiting. /// /// # Spurious wakeup /// /// `wait()` may return without a `notify_*()` call (spurious wakeup, /// e.g., M:N scheduler rebalance). Always wrap in a predicate loop: /// /// ```nova /// m.lock() /// while !predicate() { /// cv.wait(m) /// } /// // critical section /// m.unlock() /// ``` /// /// Or use the `wait_until` helper which handles this automatically. /// /// # Reentrant mutex interaction /// /// `Condvar.wait(reentrant_m)` releases the ENTIRE recursive lock count /// (lock_count → 0) and re-acquires as count=1 on wake. This is intentional /// — restoring original count is a Java pitfall. /// `W_REENTRANT_CONDVAR_RECOMMEND` lint warns when `ReentrantMutex` is used. /// /// # Precondition /// /// The mutex must be locked before calling `wait()` / `wait_for()`. /// Unconditional runtime panic if not. /// /// Opaque-handle newtype `type Condvar(*())`. C struct `Nova_Condvar` /// continues to live in `nova_rt/sync_condvar.h`; ABI unchanged. Codegen /// suppresses the Nova-level `typedef void* Nova_Condvar` (would conflict /// with runtime header's struct typedef) — see emit_c.rs §RUNTIME_BACKED_NEWTYPES. /// /// `#share` audited vouch — same reasoning as `Mutex`. #stable(since = "0.1") #share export type Condvar(*()) /// Create a new `Condvar`. #realtime #stable(since = "0.1") export extern "nova" fn Condvar.new() -> Self /// Atomically release `m` and park the fiber. Re-acquires `m` before /// returning. Spurious wakeup may occur — always use a predicate loop. /// /// # Panics /// /// Panics unconditionally if `m` is not locked. #parks #stable(since = "0.1") export extern "nova" fn Condvar @wait(mut m Mutex) /// ReentrantMutex overload: same semantics as `wait(Mutex)`, but /// releases ALL recursive lock levels on enter and re-acquires as /// count=1 on wake. W_REENTRANT_CONDVAR_RECOMMEND lint is emitted. #parks #stable(since = "0.1") export extern "nova" fn Condvar @wait(mut m ReentrantMutex) /// Wait with timeout. Atomically release `m`, park up to `timeout`, /// re-acquire `m` before return. Returns `WaitResult.Notified` if woken /// by notify, `WaitResult.TimedOut` if timeout expired. #parks #stable(since = "0.1") export extern "nova" fn Condvar @wait_for(mut m Mutex, timeout Duration) -> WaitResult /// Predicate-loop helper. Calls `wait(m)` in a loop until `predicate()` /// returns true. Handles spurious wakeup automatically. /// /// ```nova /// cv.wait_until(mu) { || buffer.len() > 0 } /// ``` #parks #stable(since = "0.1") export extern "nova" fn Condvar @wait_until(mut m Mutex, predicate fn() -> bool) /// Wake one waiting fiber (FIFO — oldest waiter first). /// No-op if no waiters. Safe to call without holding the mutex. #wakes #stable(since = "0.1") export extern "nova" fn Condvar mut @notify_one() /// Wake all waiting fibers (FIFO order). /// Safe to call without holding the mutex. #wakes #stable(since = "0.1") export extern "nova" fn Condvar mut @notify_all() // === End Condvar === // === CountDownLatch === /// One-shot count-down rendezvous (Java-style). Immutable initial count. /// /// # Vs WaitGroup /// WaitGroup allows `add(n)` after creation — fragile if `await()` /// has already started. CountDownLatch has an immutable initial count, /// making it safer for "init complete" / "shutdown initiated" signals. /// /// # Saturating count_down /// `count_down()` when count is already 0 is a **no-op** (does not panic). /// This is Java parity: the latch is permanently open once count reaches 0. /// /// # Invariant /// `new(count)` panics unconditionally if `count <= 0`. /// count must be ≥ 1 at construction. /// /// # Examples /// /// ```nova /// let mut latch = CountDownLatch.new(5) /// parallel for _i in 0..5 { /// do_work() /// latch.count_down() /// } /// latch.await() /// // all 5 workers have completed /// ``` /// /// # See Also /// /// - `[WaitGroup]` — mutable-count variant (add/done/wait) /// - `[Barrier]` — N-party reusable rendezvous /// /// `#share` audited vouch — required by the `parallel for` example right /// above (`latch` captured by reference across sibling per-element fibers). #stable(since = "0.1") #share export type CountDownLatch(*()) #realtime #stable(since = "0.1") export extern "nova" fn CountDownLatch.new(count int) -> Self /// Decrement count by 1. If count reaches 0, wakes all `await()` callers. /// /// **No-op (saturating)** if count is already 0 — does not panic. /// This is by design (Java parity): the latch stays permanently open. #wakes #stable(since = "0.1") export extern "nova" fn CountDownLatch mut @count_down() /// Decrement count by `n` (saturating at 0). If count reaches 0, wakes all. /// /// If `n >= current_count()`, count becomes 0 (saturating, not negative). /// If `n <= 0`, no-op. #wakes #stable(since = "0.1") export extern "nova" fn CountDownLatch mut @count_down_n(n int) /// Park until count reaches 0. /// /// Returns immediately if count is already 0. /// Handles spurious wakeups internally — guaranteed to return only when /// count == 0. #parks #stable(since = "0.1") export extern "nova" fn CountDownLatch @await() /// Non-blocking check: returns `true` if count is already 0. /// /// Best-effort — the count may reach 0 concurrently between the check /// and the caller acting on the result. #realtime #stable(since = "0.1") export extern "nova" fn CountDownLatch @try_await() -> bool /// Park until count reaches 0 or timeout expires. /// /// Returns `true` if count reached 0 within the timeout, /// `false` if the timeout expired first. /// timeout <= 0 is equivalent to `try_await()`. /// /// W_TRY_WITHOUT_SIBLING AMEND (2026-07-17, lint went red, owner): /// renamed from `try_await_for` — `await_for` has no infallible sibling with /// this name (`await()` — a different arity/semantics) — a plain name, without /// a `try_`-prefix (R3 D325). #parks #stable(since = "0.1") export extern "nova" fn CountDownLatch @await_for(timeout Duration) -> bool /// Best-effort current count. May be stale by the time the caller reads it. /// /// For observability (debugging, metrics) only — not for synchronization. #realtime #stable(since = "0.1") export extern "nova" fn CountDownLatch @current_count() -> int // === End CountDownLatch === // === Semaphore === /// Fiber-aware counting semaphore. Bounded permits. /// Fair FIFO waiter queue. /// /// # Use case /// - Connection pool capacity (Semaphore.new(max_connections)) /// - Rate limiting (N concurrent requests) /// - Resource pool (parallel workers, bounded) /// /// # Over-release /// `release()` without a prior `acquire()` increments permits past the /// initial count (Java-compatible semantics). /// /// # Batch API /// `acquire_n(n)` / `release_n(n)` for batch acquire/release of n permits /// atomically. FIFO fairness: if head waiter needs k > available permits, /// later waiters are NOT skipped (prevents large-acquire starvation). /// /// # `with_permit { }` — preferred API /// Acquires, runs body, releases via `defer`. Panic-safe. /// V2: wraps `Permit consume` guard. /// /// # See Also /// /// - `[Mutex]` — mutual exclusion (binary semaphore with ownership) /// - `[WaitGroup]` — count-down rendezvous /// /// `#share` audited vouch — same reasoning as `Mutex`. #stable(since = "0.1") #share export type Semaphore(*()) #realtime #stable(since = "0.1") export extern "nova" fn Semaphore.new(permits int) -> Self /// Acquire 1 permit. Parks until a permit becomes available (FIFO order). /// /// V2: returns `Permit consume`. Use `permit.release()` to release. /// Old callers that discard the return value still compile. /// /// # Realtime /// Forbidden inside `realtime { }` — may park the fiber. #parks #stable(since = "0.1") export extern "nova" fn Semaphore mut @acquire() -> consume Permit /// (Deprecated) Release 1 permit without guard. /// /// V2: use `Permit.release()` (returned by `acquire()`) instead. /// Over-release (release without prior acquire) increments permits past initial /// (Java semantics, V1). #deprecated(since = "0.2", note = "use consume p = sem.acquire(); defer p.release() instead (Plan 103.9 guard API)") #wakes #stable(since = "0.1") export extern "nova" fn Semaphore mut @release() /// Private bool bridge for `try_acquire` (Permit-guard form below is the /// ONLY public probe: `try_acquire` pairs with `acquire`). /// /// Only succeeds if permits > 0 AND no waiters are queued (FIFO fairness). #realtime extern "nova" fn Semaphore mut @try_acquire_raw() -> bool /// Semaphore address bridge for `try_acquire` (same value C-side /// `acquire()` writes into `Permit.ptr`). Module-private: opaque-newtype /// pointer is unreadable from .nv otherwise. #realtime extern "nova" fn Semaphore mut @ptr_as_int() -> int /// Try to acquire 1 permit without parking, returning a guard. /// /// `Some(Permit)` on success — the permit auto-releases via `@cleanup` on /// scope exit (panic-safe: cleanup runs on unwind too), same guard semantics /// as `acquire()`. `None` if permits are exhausted or waiters are queued /// (FIFO fairness). Named as the non-parking half of the `acquire`/ /// `try_acquire` pair. /// /// Designed for admission control (bounded accept: take slot or reject /// immediately) — see Semaphore doc «Connection pool capacity». #realtime #stable(since = "0.1") export fn Semaphore mut @try_acquire() -> Option[Permit] { if @try_acquire_raw() { Some(Permit { ptr: @ptr_as_int() }) } else { None } } /// Try to acquire 1 permit within `timeout`. Returns `true` if acquired. /// /// Returns `false` if the timeout expires before a permit becomes available. /// `timeout <= Duration.zero` behaves as the non-parking probe (`try_acquire`). /// /// W_TRY_WITHOUT_SIBLING AMEND (2026-07-17, lint went red, owner): /// renamed from `try_acquire_for` — `acquire_for` has no infallible sibling /// with this name (`acquire()` — a different arity/semantics) — a plain name, /// without a `try_`-prefix (R3 D325). /// /// # Realtime /// Forbidden inside `realtime { }` — may park the fiber. #parks #stable(since = "0.1") export extern "nova" fn Semaphore mut @acquire_for(timeout Duration) -> bool /// Best-effort query of available permit count. NOT for synchronization. /// /// The value may be stale by the time the caller uses it. /// Use for observability (metrics, debugging) only. #realtime #stable(since = "0.1") export extern "nova" fn Semaphore @available_permits() -> int /// Acquire `n` permits atomically. Parks until `n` permits available (FIFO). /// /// FIFO fairness: head-of-queue waiter must be satisfiable before this call /// can proceed. Prevents starvation. /// /// Panics: `n < 1` → runtime panic (unconditional). /// /// # Realtime /// Forbidden inside `realtime { }` — may park the fiber. #parks #stable(since = "0.1") export extern "nova" fn Semaphore mut @acquire_n(n int) /// Release `n` permits atomically. Wakes FIFO waiters that can now be satisfied. /// /// Panics: `n < 1` → runtime panic (unconditional). #wakes #stable(since = "0.1") export extern "nova" fn Semaphore mut @release_n(n int) /// PREFERRED PATTERN: acquire + body + automatic release via defer. /// /// Panic-safe: `defer permit.release()` runs even if body throws. /// V2: thin wrapper over `Permit consume` guard. /// User code does not change. /// /// # Examples /// /// ```nova /// let pool = Semaphore.new(5) /// pool.with_permit { || /// do_work() /// } /// ``` #parks #stable(since = "0.1") export fn Semaphore mut @with_permit[R](body fn() -> R) -> R { consume permit = self.acquire() defer permit.release() body() } // === End Semaphore ===