core/atomic
skip to the library navigationShared: available on every enabled target.
A cell holding one u8, u16, u32 or u64, read and written only atomically, its operations' orderings in their names.
| Call | What it does |
|---|---|
atomic.new | Make a cell holding a value, for a local or a field; module storage starts one zeroed. |
atomic.load, atomic.store | Read with acquire ordering, write with release ordering. |
atomic.load_relaxed, atomic.store_relaxed | The same, ordering nothing else: for a cache or a count. |
atomic.sole_add | Add as the cell's one writer, wrapping, and return what it held: a relaxed load and a release store. |
atomic.exchange | Store a value and return what the cell held, in one step that acquires and releases. |
atomic.add, atomic.add_relaxed | Add, wrapping, and return what the cell held, in one step; the relaxed form orders nothing else. |
atomic.compare_exchange | Store a value if the cell holds the one expected, and return what it held: equal to the expected exactly when it stored. |
Every operation takes the cell's address, addr cell, so a cell lives in module storage or a field others reach by pointer, and is never copied while another context uses it. Sequential consistency is not offered; the compiler's builtins have it ([1620]).
sole_add is correct only while one context alone writes the cell, an interrupt handler or one thread, since a second writer's store between its load and its store is lost; it needs no read-modify-write, so every target has it. Prototype 5's tick is one: the handler counts with it and any reader loads.
mut ticks: atomic.cell(u32) = zeroed count_tick: () -> none = _ = atomic.sole_add(addr ticks, 1) end count_tick now_ticks: () -> (count: u32) = count = atomic.load(addr ticks) end now_ticks
Cortex-M0 has no atomic read-modify-write and no eight-byte atomic access. There a u64 cell, exchange, add, add_relaxed and compare_exchange are refused with L0344 where the program writes them, the refusal naming sole_add and platform/cortex_m's critical section, disable_interrupts to restore_interrupts, which state several contexts write takes instead. A cell of any other type is refused on every target.
Items
Concepts 2
Types 1
Functions 10
Also on this page
public exchangeable: type = concept (item: type) is scalar
exchange: (cell: ptr mut item, value: item) -> (previous: item)
add: (cell: ptr mut item, value: item) -> (previous: item)
add_relaxed: (cell: ptr mut item, value: item) -> (previous: item)
compare_exchange: (cell: ptr mut item, expected: item, desired: item)
-> (observed: item)
end exchangeable
The scalars whose cells also take a read-modify-write: each of u8, u16, u32 and u64 where the target has atomic read-modify-write, which Cortex-M0 has not.
public scalar: type = concept (item: type)
load: (cell: ptr item) -> (value: item)
load_relaxed: (cell: ptr item) -> (value: item)
store: (cell: ptr mut item, value: item) -> none
store_relaxed: (cell: ptr mut item, value: item) -> none
sole_add: (cell: ptr mut item, value: item) -> (previous: item)
end scalar
The scalars a cell can hold, with the accesses every target admits for them: u8, u16 and u32 everywhere, and u64 where the target has eight-byte atomics, which Cortex-M0 has not. The entries are the cell's operations' own, over the scalar's address; use the operations below.
public cell: type (item: type is scalar) = cell_value(item)
One u8, u16, u32 or u64 read and written atomically. A zeroed cell holds zero.
public add: (item: type is exchangeable, at: ptr mut cell(item), value: item)
-> (previous: item)
Add value to at, wrapping, and return what it held, in one indivisible step that acquires and releases. Refused on Cortex-M0.
public add_relaxed: (item: type is exchangeable, at: ptr mut cell(item),
value: item) -> (previous: item)
Add value to at, wrapping, and return what it held, in one indivisible step with no ordering beyond the cell's own: a counter several contexts bump. Refused on Cortex-M0.
public compare_exchange: (item: type is exchangeable, at: ptr mut cell(item),
expected: item, desired: item)
-> (observed: item)
Store desired into at if it holds expected, and return what it held, in one indivisible step: the store happened exactly when the result equals expected, and never fails spuriously. A store acquires and releases; a comparison that fails only acquires. Refused on Cortex-M0.
public exchange: (item: type is exchangeable, at: ptr mut cell(item),
value: item) -> (previous: item)
Store value into at and return what it held, in one indivisible step that acquires and releases. Refused on Cortex-M0.
The value at holds, with acquire ordering: what the store that wrote it released is visible after it.
The value at holds, with no ordering beyond the cell's own.
A cell holding value, for a local or a field; module storage writes zeroed instead. Making it is not atomic: make it before another context can reach it.
Add value to at, wrapping, as the cell's one writer, and return what it held: a relaxed load and a release store, so a reader that acquires the sum sees what the writer did before. It is correct only while one context alone writes the cell, an interrupt handler or a thread, since a second writer's store between the two is lost; then it needs no read-modify-write, and every target has it, Cortex-M0 included. Where several contexts write, use add.
Store value into at, with release ordering: whatever this context wrote before it is visible to a load that acquires this value.
Store value into at, with no ordering beyond the cell's own.
Example program
This complete program is one of the compiler's runtime tests. View source.
The program, 35 lines
-- Prototype 5's firmware/tick: time from an interrupt, with one writer, -- the handler, and any number of readers. The writer needs no -- read-modify-write, which Cortex-M0 has not: sole_add is a relaxed load -- and a release store, and a reader's load acquires. A runtime fixture -- places no vector, so the program is the writer here and counts where -- it would wait; environments/cortex-m/probes/fibre-capacity.ldn runs the -- same writer as SysTick's handler on Cortex-M0. import core/atomic mut ticks: atomic.cell(u32) = zeroed count_tick: () -> none = _ = atomic.sole_add(addr ticks, 1) end count_tick now_ticks: () -> (count: u32) = count = atomic.load(addr ticks) end now_ticks -- Wait for three ticks after the first reading, as a fibre waiting on the -- clock does, with a bound so that a count that never moves fails. public main: () -> (code: i32) = code = 1 first: u32 = now_ticks() mut seen: u32 = first mut waits: u32 = 0 while seen - first < 3 and waits < 1000 do count_tick() seen = now_ticks() inc waits end while if seen - first == 3 and waits == 3 then code = 42 end if end main