Memory model and data races

Ordinary memory requires external synchronization when threads may conflict; atomic<T> provides the Core operation-level synchronization primitive.

Conflicting accesses

Two operations conflict when they access the same or overlapping storage and at least one operation writes. Operations performed by different native threads must be ordered by valid synchronization or must use supported atomic storage. Two unordered ordinary reads do not conflict.

Concurrent operations on one locationRule
Ordinary read + ordinary readValid
Ordinary read + ordinary writeData race: undefined behavior
Ordinary write + ordinary writeData race: undefined behavior
Supported atomic operationsValid under Xenon's atomic semantics

Undefined behavior means Xenon does not promise a particular value, trap, interleaving or recovery. The optimizer may assume that a valid program has no ordinary data races. A result that appears stable in one build or on one processor is not a language guarantee.

Ordinary storage

Locals, ordinary static fields, struct fields, raw pointer targets and array elements use ordinary loads and stores unless their declared storage is explicitly atomic. Accessing distinct non-overlapping locations concurrently is allowed when their lifetimes are valid.

struct State
{
    public int Value;          // ordinary storage
    public atomic<int> Count; // atomic storage
}

Making a pointer value atomic protects replacement of the pointer bits, not the pointed-to object. Likewise, atomic<T[]> protects the array handle while its elements remain ordinary, whereas atomic<T>[] gives each array element its own atomic storage.

Atomic semantics

Supported operations on one atomic<T> are indivisible. Atomic reads, writes, read-modify-write operators, exchange and compare-exchange participate in one sequentially consistent order that agrees with the order observed by each native thread. Composite atomic values provide the same source-level guarantee regardless of their native representation.

All supported atomic operations use the documented sequentially consistent semantics. Xenon does not provide source-level memory-order names, fence operations or weaker ordering choices.

No mixed access path

atomic<T> is a distinct type. Xenon does not provide an ordinary reference to its stored value. Bypassing the type system through incompatible native aliases and racing an ordinary access against an atomic operation is undefined behavior.

Ownership is not content synchronization

shared<T> and weak<T> synchronize their internal lifetime counters. Separate handles may be retained, released and upgraded concurrently according to their ownership rules. Those counters do not make fields of T atomic.

shared<State> first = new State();
shared<State> second = first;

// Concurrent first->Value and second->Value writes would race.
// Concurrent operations on first->Count use atomic semantics.

The storage holding a handle is also ordinary storage. Concurrently moving from or overwriting the same handle variable while another thread reads it is a data race. Safe transfer must be arranged by the host or through a supported atomic ownership form such as atomic<shared<T>>.

Readonly, borrowing and lifetime

readonly prevents mutation through one access path. It does not change the generated load into an atomic load and cannot prevent another thread from writing through a different mutable alias. A readonly read concurrent with an unordered ordinary write is therefore a data race.

The borrow checker rejects conflicting aliases and lifetime errors that are visible in analyzed Xenon flow. Borrowing does not create a runtime lock, atomic operation or cross-thread ordering edge. Native code must not retain a borrowed reference beyond its proven lifetime or use an external alias to violate the borrow contract.

Calling free or delete, running final ownership release, or otherwise ending an allocation's lifetime while another thread can access it is undefined behavior. A thread-safe allocator makes independent allocation operations safe; it cannot make use-after-release safe.

Thread-local and host-created threads

Different native threads access different instances of a threadlocal field, so those instances do not race with each other. Values reached through TLS pointers or ownership handles can still refer to shared objects and follow the ordinary rules above.

Core deliberately does not define thread creation or join APIs. A native host may call exported Xenon functions from its own threads; the host is responsible for using its synchronization primitives to order ordinary Xenon memory accesses across those calls.

If a host-created thread initializes non-trivial Xenon TLS, the defining native module must remain loaded until that thread terminates. See Thread-local storage for the complete lazy-initialization, destruction and module-unload contract.

What the compiler enforces

The compiler preserves the distinction between ordinary and atomic storage, checks supported atomic types and operators, enforces readonly access paths, and applies borrow and ownership lifetime rules. It does not infer external thread scheduling or insert hidden atomics around ordinary data.

Because parallel calls may originate outside Xenon, many data races cannot be diagnosed statically. Absence of a diagnostic is not proof that unsynchronized ordinary access is safe; it remains the program's responsibility to satisfy this memory model.