Skip to content

Atomic Variables #3

@iso2t

Description

@iso2t

Summary

This proposal introduces atomic variables to Loom, enabling low-level concurrency primitives and memory-safe access to shared data in multithreaded or interrupt-driven environments. Atomic variables are declared with the atomic modifier:

pub var x -> atomic i32;
priv var flag -> atomic bool;

This feature aligns with Loom’s goal of providing fine-grained control over memory, concurrency, and performance on bare-metal architectures.

Motivation

Atomic operations are fundamental in systems programming for:
• Lock-free data structures
• Spinlocks, semaphores, and wait-free counters
• Hardware flag control and memory-mapped registers
• Interrupt-safe variables and state toggling
• Multicore synchronization

Without native atomic support, Loom developers would have to rely on unsafe compiler intrinsics or manual locking, which contradicts Loom’s design goal of safe and explicit control.

Guide-Level Explanation

Declaration Syntax

Atomic variables use the standard Loom field declaration syntax with the atomic modifier:

pub var count -> atomic i32;
priv var locked -> atomic bool; 

Supported Types

Initially, only basic primitives may be declared as atomic:
• atomic bool
• atomic i8, atomic i16, atomic i32, atomic i64
• atomic u8, atomic u16, atomic u32, atomic u64

Future extensions may include atomic usize, atomic isize, or even custom atomic wrappers.

Usage

pub func main() {
    var counter -> atomic i32;

    counter.store(1);
    var value = counter.load();

    counter.fetchAdd(1);
    counter.compareExchange(2, 3);
} 

Supported Methods

Atomic values expose explicit methods:
• load(ordering?)
• store(value, ordering?)
• exchange(value, ordering?)
• compareExchange(expected, new, ordering?)
• fetchAdd(value, ordering?)
• fetchSub(value, ordering?)
• fetchAnd(value, ordering?)
• fetchOr(value, ordering?)
• fetchXor(value, ordering?)

Memory Ordering (Optional)

Optionally specify memory ordering:

counter.store(42, relaxed);
let x = counter.load(acquire); 

Available ordering modes:
• relaxed
• acquire
• release
• acq_rel
• seq_cst (default)

Reference-Level Explanation

Grammar Changes

Type ::= "atomic" PrimitiveType

PrimitiveType ::= "bool" | "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" 

Field and variable declarations can include -> atomic T where T is a supported primitive type.

Type System
• Atomic variables are distinct types from their non-atomic equivalents (e.g., atomic i32 ≠ i32)
• Cannot perform direct arithmetic (+, -, etc.) unless via atomic methods
• Comparisons and loads return the base type (i32, etc.)

Operator Restrictions
• Arithmetic operators (+=, -=) are disallowed by default
• Optional: allow sugar for atomic ops only on atomic types with strict desugaring

counter += 1; // desugars to counter.fetchAdd(1) 

This sugar must not be available to non-atomic types to avoid ambiguity.

IR / Codegen

Emit platform-specific atomic instructions:
• x86: lock xadd, cmpxchg, mov with memory ordering fences
• ARM: LDREX, STREX, DMB, DSB
• Fallback: critical sections or spinlocks (for unsupported platforms)

Ensure atomicity is preserved even with aggressive compiler optimizations.

Drawbacks

Concern Details
Complexity Increases type and memory model complexity
Misuse Developers may misuse atomic types without understanding memory ordering
Portability Not all targets support all atomic widths (e.g., atomic i64 on 32-bit MCUs)
Performance Incorrect ordering may degrade performance or introduce contention

Alternatives
• Use an Atomic wrapper class — more verbose, less ergonomic
• Rely on unsafe blocks and inline assembly — less safe, not Loom-style
• External libraries — not viable for core concurrency features

Prior Art

Language Atomic Support Notes
C/C++ std::atomic Verbose but flexible with full memory model support
Rust AtomicI32, Ordering Type-safe, explicit, fine-grained
Zig @atomicLoad, @cmpxchgStrong Intrinsic-based with explicit memory models
Java volatile, AtomicInteger High-level but GC-dependent
Go sync/atomic Package-based; function wrappers only

Unresolved Questions
• Should operator sugar be included or kept out of MVP?
• Should memory ordering keywords be lowercase (relaxed) or namespaced (Ordering::Relaxed)?
• Should atomic types implement traits or interfaces (AtomicOps)?

Future Directions
• Add support for atomic usize, isize based on platform word size
• Add higher-level atomic types (e.g., AtomicRef, AtomicFlag)
• Add atomic wait/notify primitives (wait, notifyOne, notifyAll)
• Add memory fences (fence(Ordering))

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions