feat(atomic): drop the dead dmb barrier on single-core targets

A seq_cst px4::atomic emits a hardware `dmb` on ARM. That barrier only orders
accesses as seen by a second observer (another CPU core or DMA); for inter-thread
synchronisation on a single-core (uniprocessor) target there is no second core, so
the `dmb` is dead weight - only the compiler ordering is required.

On a NuttX build without CONFIG_SMP, keep full seq_cst semantics but emit a
compiler-only fence (__atomic_signal_fence, zero instructions) instead of the
`dmb`. This mirrors Linux, where smp_mb()/smp_rmb()/smp_wmb() collapse to a
compiler barrier on uniprocessor builds. SMP NuttX and POSIX are unchanged (real
barriers, so SITL under ThreadSanitizer keeps full ordering). 64-bit types (not
lock-free on a 32-bit core) keep the existing interrupts-off critical section,
which already provides both atomicity and ordering.

The public API is unchanged - no per-call ordering knob. Verified on
arm-none-eabi Cortex-M7 that the single-core path emits no `dmb` while keeping
atomicity, and that SMP/POSIX is unchanged:

  load  (single-core): ldr                      (signal_fence: 0 instr)
  store (single-core): str
  fadd  (single-core): ldrex/strex loop, no dmb
  load  (seq_cst/SMP): dmb ish; ldr; dmb ish
  store (seq_cst/SMP): dmb ish; str; dmb ish
  fadd  (seq_cst/SMP): dmb ish; ldrex/strex; dmb ish

Shrinks fmu-v6x by ~2.8 KB (dead barriers removed from atomics already in use),
with no behaviour change. Note: inter-thread ordering only - DMA/device sync
still needs explicit barriers.
This commit is contained in:
Julian Oes
2026-06-15 14:27:02 +12:00
committed by Beat Küng
parent 034069866f
commit 84ddad88e3

View File

@@ -34,17 +34,30 @@
/**
* @file atomic.h
*
* Provides atomic integers and counters. Each method is executed atomically and thus
* can be used to prevent data races and add memory synchronization between threads.
*
* In addition to the atomicity, each method serves as a memory barrier (sequential
* consistent ordering). This means all operations that happen before and could
* potentially have visible side-effects in other threads will happen before
* the method is executed.
* Provides atomic integers and counters. Each method is executed atomically and
* acts as a sequentially-consistent memory barrier (the same contract as before).
*
* The implementation uses the built-in methods from GCC (supported by Clang as well).
* @see https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html.
*
* ## Single-core lowering
*
* A seq_cst atomic on ARM emits a hardware `dmb` barrier. That barrier only matters
* for ordering as seen by a *second observer* - another CPU core or DMA. On a
* single-core (uniprocessor) target there is no second core, so for inter-thread
* synchronisation the `dmb` is dead weight; only the *compiler* ordering is actually
* required (so the compiler doesn't reorder neighbouring accesses across the atomic).
*
* So on a NuttX build without CONFIG_SMP we keep full seq_cst semantics but emit a
* compiler-only fence (__atomic_signal_fence, zero instructions) instead of the
* `dmb`. This mirrors Linux, where smp_mb()/smp_rmb()/smp_wmb() collapse to a plain
* compiler barrier on uniprocessor builds. The public contract is unchanged; only
* the dead hardware barrier is removed on single-core targets.
*
* @note This class provides *inter-thread* ordering only. Synchronisation with DMA or
* device memory needs explicit barriers (e.g. up_clean_dcache / __DMB) and must not
* rely on the ordering here.
*
* @note: on ARM, the instructions LDREX and STREX might be emitted. To ensure correct
* behavior, the exclusive monitor needs to be cleared on a task switch (via CLREX).
* This happens automatically e.g. on ARMv7-M as part of an exception entry or exit
@@ -59,9 +72,19 @@
#include <stdint.h>
#if defined(__PX4_NUTTX)
# include <nuttx/config.h>
# include <nuttx/irq.h>
#endif // __PX4_NUTTX
// On a single-core NuttX build the hardware memory barrier (`dmb`) a seq_cst atomic
// emits is unnecessary for inter-thread ordering; a compiler-only fence is sufficient
// and saves the barrier. SMP NuttX and POSIX keep the real barriers.
#if defined(__PX4_NUTTX) && !defined(CONFIG_SMP)
# define PX4_ATOMIC_SINGLE_CORE 1
#else
# define PX4_ATOMIC_SINGLE_CORE 0
#endif
namespace px4
{
@@ -87,16 +110,28 @@ public:
#if defined(__PX4_NUTTX)
if (!__atomic_always_lock_free(sizeof(T), 0)) {
// Not lock-free (e.g. 64-bit on a 32-bit core): a critical section
// provides both atomicity and full ordering.
irqstate_t flags = enter_critical_section();
T val = _value;
leave_critical_section(flags);
return val;
} else
#endif // __PX4_NUTTX
{
return __atomic_load_n(&_value, __ATOMIC_SEQ_CST);
}
#endif // __PX4_NUTTX
#if PX4_ATOMIC_SINGLE_CORE
// Single core: a relaxed load (a plain, non-tearing ldr) plus a
// compiler-only fence gives seq_cst inter-thread ordering without a `dmb`.
T val;
__atomic_load(&_value, &val, __ATOMIC_RELAXED);
__atomic_signal_fence(__ATOMIC_SEQ_CST);
return val;
#else
T val;
__atomic_load(&_value, &val, __ATOMIC_SEQ_CST);
return val;
#endif
}
/**
@@ -110,12 +145,17 @@ public:
irqstate_t flags = enter_critical_section();
_value = value;
leave_critical_section(flags);
} else
#endif // __PX4_NUTTX
{
__atomic_store(&_value, &value, __ATOMIC_SEQ_CST);
return;
}
#endif // __PX4_NUTTX
#if PX4_ATOMIC_SINGLE_CORE
__atomic_signal_fence(__ATOMIC_SEQ_CST);
__atomic_store(&_value, &value, __ATOMIC_RELAXED);
#else
__atomic_store(&_value, &value, __ATOMIC_SEQ_CST);
#endif
}
/**
@@ -132,12 +172,18 @@ public:
_value += num;
leave_critical_section(flags);
return ret;
} else
#endif // __PX4_NUTTX
{
return __atomic_fetch_add(&_value, num, __ATOMIC_SEQ_CST);
}
#endif // __PX4_NUTTX
#if PX4_ATOMIC_SINGLE_CORE
__atomic_signal_fence(__ATOMIC_SEQ_CST);
T ret = __atomic_fetch_add(&_value, num, __ATOMIC_RELAXED);
__atomic_signal_fence(__ATOMIC_SEQ_CST);
return ret;
#else
return __atomic_fetch_add(&_value, num, __ATOMIC_SEQ_CST);
#endif
}
/**
@@ -154,12 +200,18 @@ public:
_value -= num;
leave_critical_section(flags);
return ret;
} else
#endif // __PX4_NUTTX
{
return __atomic_fetch_sub(&_value, num, __ATOMIC_SEQ_CST);
}
#endif // __PX4_NUTTX
#if PX4_ATOMIC_SINGLE_CORE
__atomic_signal_fence(__ATOMIC_SEQ_CST);
T ret = __atomic_fetch_sub(&_value, num, __ATOMIC_RELAXED);
__atomic_signal_fence(__ATOMIC_SEQ_CST);
return ret;
#else
return __atomic_fetch_sub(&_value, num, __ATOMIC_SEQ_CST);
#endif
}
/**
@@ -176,12 +228,18 @@ public:
_value &= num;
leave_critical_section(flags);
return val;
} else
#endif // __PX4_NUTTX
{
return __atomic_fetch_and(&_value, num, __ATOMIC_SEQ_CST);
}
#endif // __PX4_NUTTX
#if PX4_ATOMIC_SINGLE_CORE
__atomic_signal_fence(__ATOMIC_SEQ_CST);
T ret = __atomic_fetch_and(&_value, num, __ATOMIC_RELAXED);
__atomic_signal_fence(__ATOMIC_SEQ_CST);
return ret;
#else
return __atomic_fetch_and(&_value, num, __ATOMIC_SEQ_CST);
#endif
}
/**
@@ -198,12 +256,18 @@ public:
_value ^= num;
leave_critical_section(flags);
return val;
} else
#endif // __PX4_NUTTX
{
return __atomic_fetch_xor(&_value, num, __ATOMIC_SEQ_CST);
}
#endif // __PX4_NUTTX
#if PX4_ATOMIC_SINGLE_CORE
__atomic_signal_fence(__ATOMIC_SEQ_CST);
T ret = __atomic_fetch_xor(&_value, num, __ATOMIC_RELAXED);
__atomic_signal_fence(__ATOMIC_SEQ_CST);
return ret;
#else
return __atomic_fetch_xor(&_value, num, __ATOMIC_SEQ_CST);
#endif
}
/**
@@ -220,12 +284,18 @@ public:
_value |= num;
leave_critical_section(flags);
return val;
} else
#endif // __PX4_NUTTX
{
return __atomic_fetch_or(&_value, num, __ATOMIC_SEQ_CST);
}
#endif // __PX4_NUTTX
#if PX4_ATOMIC_SINGLE_CORE
__atomic_signal_fence(__ATOMIC_SEQ_CST);
T ret = __atomic_fetch_or(&_value, num, __ATOMIC_RELAXED);
__atomic_signal_fence(__ATOMIC_SEQ_CST);
return ret;
#else
return __atomic_fetch_or(&_value, num, __ATOMIC_SEQ_CST);
#endif
}
/**
@@ -242,12 +312,18 @@ public:
_value = ~(_value & num);
leave_critical_section(flags);
return ret;
} else
#endif // __PX4_NUTTX
{
return __atomic_fetch_nand(&_value, num, __ATOMIC_SEQ_CST);
}
#endif // __PX4_NUTTX
#if PX4_ATOMIC_SINGLE_CORE
__atomic_signal_fence(__ATOMIC_SEQ_CST);
T ret = __atomic_fetch_nand(&_value, num, __ATOMIC_RELAXED);
__atomic_signal_fence(__ATOMIC_SEQ_CST);
return ret;
#else
return __atomic_fetch_nand(&_value, num, __ATOMIC_SEQ_CST);
#endif
}
/**
@@ -275,15 +351,22 @@ public:
leave_critical_section(flags);
return false;
}
} else
#endif // __PX4_NUTTX
{
return __atomic_compare_exchange(&_value, expected, &desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
}
#endif // __PX4_NUTTX
#if PX4_ATOMIC_SINGLE_CORE
__atomic_signal_fence(__ATOMIC_SEQ_CST);
bool ret = __atomic_compare_exchange(&_value, expected, &desired, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED);
__atomic_signal_fence(__ATOMIC_SEQ_CST);
return ret;
#else
return __atomic_compare_exchange(&_value, expected, &desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
#endif
}
private:
T _value {};
};