[PATCH 2/2] sparc32: provide libc atomics using CASA or the kernel CAS trap
Magnus Lindholm
linmag7@gmail.com
Thu Sep 24 06:42:16 GMT 2026
Provide the compare-and-swap and read-modify-write entry points libc's
own code needs: a v9 cpu's native compare-and-swap (emitted as its raw
encoding, not "cas", so the object isn't marked as requiring v9), or,
where that's absent, a kernel-assisted trap proposed by a companion,
not-yet-posted series ("sparc32: add a kernel assisted compare-and-
swap"). Both are safe from a signal handler or in ld.so before
locking exists, which a software lock cannot promise; a cpu with
neither is left unsupported. Gated on the same
libc_cv_cas_uses_libatomic configure check that previously just
rejected the target outright.
atomic_store_relaxed/_release and atomic_exchange_relaxed/_acquire/
_release call their __atomic_* builtin unconditionally, which the
compiler lowers to a bare store or swap that bypasses cas32 - so
without overriding these too, a target overriding the read-modify-
writes gets two disagreeing ways to serialise the same word.
Concretely, lll_lock's fast path is a compare-and-swap while its
contended path and unlock are an exchange. __sparc32_atomic_store_4/
_exchange_4 are named for this file, not the compiler-runtime ABI,
since nothing calls either implicitly.
HWCAP_SPARC_CASTRAP moves to sysdeps/sparc/bits/hwcap.h, alongside
every other HWCAP_SPARC_* bit. It describes kernel-service
availability, not hardware absence: the kernel sets it unconditionally
for every sparc32 process, including v9 ones, which check
HWCAP_SPARC_V9 first and never look at it.
kernel_cas checks the trap's result before trusting it: the kernel's
ABI returns the observed value in %o0 on success, or an errno there
with carry set on a permanent access error. Without the check, a
caller could see false success or retry forever. carry is captured
with the same addx idiom sysdeps/sparc/sparc32/add_n.S already uses,
in the same asm block as the trap so nothing else can touch the
condition codes first.
Both of cas32's failure paths terminate through a new cas32_fatal()
(a raw write() syscall, then __builtin_trap()) rather than
__libc_fatal(), which recurses here: its normal implementation
exchanges a pointer through atomic_exchange_acquire(&__abort_msg, ...)
(sysdeps/posix/libc_fatal.c), which this file's own override sends
straight back into cas32(). abort()'s fallback is no safer past its
first raise, since it then takes a pthread rwlock built on this same
kind of atomic.
These definitions are hidden: verified directly that an ordinary
unversioned external reference still binds to a symbol's default
version regardless of its name, so this is not a second public
provider of the compiler-atomic ABI. Static linking is a separate
question this does not answer: verified directly that a static
program calling __atomic_fetch_add with no -latomic resolves it from
this file's own copy in libc.a with no error, since attribute_hidden
governs export, not archive-member pulling. localplt.data records the
resulting intra-object PLT indirection, the same idiom already used
there for the _Q_* routines.
Two rounds of external review since, both fixed: kernel_cas ignoring
the carry bit and __libc_fatal's recursion (both above); a __ret
variable-capture bug in the exchange macro (renamed to
__sparc32_exch_ret per include/atomic.h's own per-macro-unique-prefix
rule); and two overstated comments, on fence availability and on the
hwcap==0 fallback's scope (both corrected).
Signed-off-by: Magnus Lindholm <linmag7@gmail.com>
---
config.h.in | 4 +
sysdeps/sparc/atomic-machine.h | 66 +++++
sysdeps/sparc/bits/hwcap.h | 3 +
sysdeps/sparc/sparc32/Makefile | 26 ++
sysdeps/sparc/sparc32/atomic-ops.c | 249 ++++++++++++++++++
sysdeps/sparc/sparc32/configure | 5 +-
sysdeps/sparc/sparc32/configure.ac | 3 +-
.../sysv/linux/sparc/sparc32/localplt.data | 17 ++
8 files changed, 371 insertions(+), 2 deletions(-)
create mode 100644 sysdeps/sparc/sparc32/atomic-ops.c
diff --git a/config.h.in b/config.h.in
index 17bdd305..21be6e54 100644
--- a/config.h.in
+++ b/config.h.in
@@ -228,6 +228,10 @@
/* Set to 1 if 64 bit atomics are supported. */
#undef HAVE_64B_ATOMICS
+/* Define if the compiler emits calls to libatomic for compare-and-swap on
+ this target, and libc is built with its own routines in place of it. */
+#undef SPARC32_ATOMIC_OPS
+
/* Define to 1 if compiler runtime provides __sfp_handle_exceptions */
#undef HAVE_SFP_HANDLE_EXCEPTIONS
diff --git a/sysdeps/sparc/atomic-machine.h b/sysdeps/sparc/atomic-machine.h
index 840f304e..bda38b70 100644
--- a/sysdeps/sparc/atomic-machine.h
+++ b/sysdeps/sparc/atomic-machine.h
@@ -32,4 +32,70 @@ extern void __cpu_relax (void);
# define atomic_spin_nop() __cpu_relax ()
#endif
+#ifdef SPARC32_ATOMIC_OPS
+
+/* Four-byte atomics route through atomic-ops.c, not a lock-free
+ instruction - only that width is exempted below. */
+# define __atomic_check_size(mem) \
+ _Static_assert ((sizeof (*(mem)) == 4 \
+ || __atomic_always_lock_free (sizeof (*(mem)), 0)) \
+ && !(sizeof (*(mem)) == 8 && HAVE_64B_ATOMICS == 0), \
+ "atomic not lock free!")
+
+/* Routes libc's own atomic stores through cas32() too, so a plain
+ store can't land inside one of its read-modify-writes. */
+extern void __sparc32_atomic_store_4 (volatile void *, unsigned int)
+ attribute_hidden;
+
+# define __sparc32_atomic_store(mem, val, fallback_mo) \
+ do { \
+ if (sizeof (*(mem)) == 4) \
+ __sparc32_atomic_store_4 ((mem), (unsigned int) (val)); \
+ else \
+ __atomic_store_n ((mem), (val), (fallback_mo)); \
+ } while (0)
+
+# define atomic_store_relaxed(mem, val) \
+ do { \
+ __atomic_check_size((mem)); \
+ __sparc32_atomic_store ((mem), (val), __ATOMIC_RELAXED); \
+ } while (0)
+# define atomic_store_release(mem, val) \
+ do { \
+ __atomic_check_size((mem)); \
+ __sparc32_atomic_store ((mem), (val), __ATOMIC_RELEASE); \
+ } while (0)
+
+/* Routes exchange through the same compare-and-swap - lowlevellock
+ mixes compare-exchange and exchange on one futex word. */
+extern unsigned int __sparc32_atomic_exchange_4 (volatile void *,
+ unsigned int)
+ attribute_hidden;
+
+/* __sparc32_exch_ret, not __ret: per-macro unique local names, per
+ include/atomic.h's own rule and this file's pre-2019 precedent. */
+# define __sparc32_atomic_exchange(mem, desired, fallback_mo) \
+ ({ \
+ __typeof (*(mem)) __sparc32_exch_ret; \
+ if (sizeof (*(mem)) == 4) \
+ __sparc32_exch_ret = (__typeof (*(mem))) \
+ __sparc32_atomic_exchange_4 ((mem), (unsigned int) (desired)); \
+ else \
+ __sparc32_exch_ret = __atomic_exchange_n ((mem), (desired), \
+ (fallback_mo)); \
+ __sparc32_exch_ret; \
+ })
+
+# define atomic_exchange_relaxed(mem, desired) \
+ ({ __atomic_check_size((mem)); \
+ __sparc32_atomic_exchange ((mem), (desired), __ATOMIC_RELAXED); })
+# define atomic_exchange_acquire(mem, desired) \
+ ({ __atomic_check_size((mem)); \
+ __sparc32_atomic_exchange ((mem), (desired), __ATOMIC_ACQUIRE); })
+# define atomic_exchange_release(mem, desired) \
+ ({ __atomic_check_size((mem)); \
+ __sparc32_atomic_exchange ((mem), (desired), __ATOMIC_RELEASE); })
+
+#endif /* SPARC32_ATOMIC_OPS */
+
#endif
diff --git a/sysdeps/sparc/bits/hwcap.h b/sysdeps/sparc/bits/hwcap.h
index 887a7f0e..12152493 100644
--- a/sysdeps/sparc/bits/hwcap.h
+++ b/sysdeps/sparc/bits/hwcap.h
@@ -49,3 +49,6 @@
#define HWCAP_SPARC_CBCOND 0x02000000
#define HWCAP_SPARC_CRYPTO 0x04000000
#define HWCAP_SPARC_ADP 0x08000000
+/* Kernel-assisted compare-and-swap trap available - describes the
+ kernel service, not cpu hardware support. Not yet a real ABI. */
+#define HWCAP_SPARC_CASTRAP 0x10000000
diff --git a/sysdeps/sparc/sparc32/Makefile b/sysdeps/sparc/sparc32/Makefile
index 9cc9c6b7..33e02840 100644
--- a/sysdeps/sparc/sparc32/Makefile
+++ b/sysdeps/sparc/sparc32/Makefile
@@ -22,6 +22,32 @@ endif # gnulib
divrem := sdiv udiv rem urem
+ifeq ($(sparc32-atomic-ops),yes)
+ifeq ($(subdir),csu)
+sysdep_routines += atomic-ops
+endif
+
+# atomic-ops.c is hidden, so each separately-linked object below needs
+# its own copy - found by attempting the link.
+ifeq ($(subdir),nscd)
+nscd-modules += atomic-ops
+endif
+ifeq ($(subdir),nss)
+libnss_compat-routines += atomic-ops
+libnss_db-routines += atomic-ops
+endif
+ifeq ($(subdir),malloc)
+libmemusage-routines += atomic-ops
+libc_malloc_debug-routines += atomic-ops
+endif
+ifeq ($(subdir),support)
+libsupport-routines += atomic-ops
+endif
+ifeq ($(subdir),nis)
+libnsl-routines += atomic-ops
+endif
+endif
+
# libgcc __divdi3 and __moddi3 uses .udiv and since it is also exported by
# libc.so linker will create PLTs for the symbol. To avoid it we strong alias
# the exported libc one to __wrap_.udiv and use linker option --wrap to make any
diff --git a/sysdeps/sparc/sparc32/atomic-ops.c b/sysdeps/sparc/sparc32/atomic-ops.c
new file mode 100644
index 00000000..c179fd27
--- /dev/null
+++ b/sysdeps/sparc/sparc32/atomic-ops.c
@@ -0,0 +1,249 @@
+/* Compiler atomic support routines needed internally by glibc. sparc32.
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+/* glibc's own out-of-line entry points for the compiler atomics a V8
+ baseline can't inline and can't reach via libatomic. Hidden, not a
+ second libatomic - see the commit message. */
+
+#include <ldsodefs.h>
+#include <libc-diag.h>
+#include <libc-symbols.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <sysdep.h>
+#include <unistd.h>
+#if !IS_IN (libc) && !IS_IN (rtld)
+# include <sys/auxv.h>
+#endif
+
+/* Only built where the compiler actually emits these calls; see
+ sysdeps/sparc/sparc32/configure.ac. */
+#ifdef SPARC32_ATOMIC_OPS
+
+/* GLRO(dl_hwcap) only works within libc.so/rtld's own compilation; the
+ other separately-linked copies of this file use getauxval() instead. */
+#if IS_IN (libc) || IS_IN (rtld)
+# define sparc32_hwcap() GLRO(dl_hwcap)
+#else
+# define sparc32_hwcap() getauxval (AT_HWCAP)
+#endif
+
+/* sparc forms trap types by adding 0x80 to the software trap number,
+ so trap type 0x91 is issued as "ta 0x11". */
+#define SPARC32_CAS_TRAP 0x11
+
+/* Emitted as its encoding, not "cas", so the object isn't marked as
+ requiring v9. No barrier: SPARC TSO already orders this against
+ plain accesses. */
+static inline uint32_t
+v9_cas (volatile uint32_t *mem, uint32_t oldval, uint32_t newval)
+{
+ register uint32_t r_new __asm ("%g6") = newval;
+ register volatile uint32_t *r_mem __asm ("%g1") = mem;
+ register uint32_t r_old __asm ("%g5") = oldval;
+
+ __asm __volatile (".word 0xcde05005" /* cas [%g1], %g5, %g6 */
+ : "+r" (r_new), "=m" (*r_mem)
+ : "r" (r_old), "m" (*r_mem), "r" (r_mem)
+ : "memory");
+ return r_new;
+}
+
+/* Must not depend on any atomic operation, directly or via a call - this
+ file is the only implementation of several on this target.
+ __libc_fatal() and abort() both fail that test; see the commit
+ message. */
+static _Noreturn void
+cas32_fatal (const char *msg, size_t len)
+{
+ INTERNAL_SYSCALL_CALL (write, STDERR_FILENO, msg, len);
+ __builtin_trap ();
+}
+
+#define CAS32_FATAL(msg) cas32_fatal (msg, sizeof (msg) - 1)
+
+/* Kernel-assisted compare-and-swap via a companion, not-yet-posted
+ kernel series - gated on HWCAP_SPARC_CASTRAP. Atomicity only, not
+ ordering; see the commit message. */
+static inline uint32_t
+kernel_cas (volatile uint32_t *mem, uint32_t oldval, uint32_t newval)
+{
+ register unsigned long o0 __asm__ ("o0") = (unsigned long) mem;
+ register unsigned long o1 __asm__ ("o1") = oldval;
+ register unsigned long o2 __asm__ ("o2") = newval;
+ unsigned long error;
+
+ /* addx captures the trap's carry bit right after "ta", the same
+ idiom add_n.S uses to save carry. See the commit message. */
+ __asm__ __volatile__ ("ta %2\n\t"
+ "addx %%g0, %%g0, %1"
+ : "+r" (o0), "=r" (error)
+ : "i" (SPARC32_CAS_TRAP), "r" (o1), "r" (o2)
+ : "memory", "cc");
+ if (__glibc_unlikely (error))
+ CAS32_FATAL ("Fatal glibc error: kernel compare-and-swap trap"
+ " reported a permanent access error\n");
+ return (uint32_t) o0;
+}
+
+/* One compare-and-swap by whichever mechanism this cpu has - unlike
+ glibc's pre-2019 lock, neither can deadlock in a signal handler. */
+static uint32_t
+cas32 (volatile uint32_t *mem, uint32_t oldval, uint32_t newval)
+{
+ unsigned long int hwcap = sparc32_hwcap ();
+
+ if (hwcap & HWCAP_SPARC_V9)
+ return v9_cas (mem, oldval, newval);
+
+ if (hwcap & HWCAP_SPARC_CASTRAP)
+ return kernel_cas (mem, oldval, newval);
+
+ /* Zero via GLRO(dl_hwcap) means still starting up; the other seven
+ copies reach hwcap via getauxval(), where that proof doesn't hold. */
+ if (__glibc_unlikely (hwcap == 0))
+ {
+ uint32_t val = *mem;
+
+ if (val == oldval)
+ *mem = newval;
+ return val;
+ }
+
+ CAS32_FATAL ("Fatal glibc error: CPU has no compare-and-swap"
+ " and the kernel provides none\n");
+}
+
+/* These declarations' argument lists differ from GCC's builtin
+ prototypes; the mismatch warning is fatal under -Werror. */
+DIAG_PUSH_NEEDS_COMMENT;
+DIAG_IGNORE_NEEDS_COMMENT (16, "-Wbuiltin-declaration-mismatch");
+
+/* Hidden - see the commit message; Makefile lists which other objects
+ compile their own copy. */
+extern bool __atomic_compare_exchange_4 (volatile void *, void *, uint32_t,
+ int, int) attribute_hidden;
+extern uint32_t __atomic_fetch_add_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_add_fetch_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_fetch_sub_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_sub_fetch_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_fetch_and_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_and_fetch_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_fetch_or_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_or_fetch_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_fetch_xor_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_xor_fetch_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_fetch_nand_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+extern uint32_t __atomic_nand_fetch_4 (volatile void *, uint32_t, int)
+ attribute_hidden;
+
+bool
+__atomic_compare_exchange_4 (volatile void *mem, void *expected,
+ uint32_t desired, int smodel, int fmodel)
+{
+ uint32_t expected_value = *(uint32_t *) expected;
+ uint32_t seen = cas32 (mem, expected_value, desired);
+
+ if (seen == expected_value)
+ return true;
+ *(uint32_t *) expected = seen; /* on failure, report what was seen */
+ return false;
+}
+
+/* fetch_OP returns the value before the operation, OP_fetch the value
+ after. Both retry until the exchange succeeds. */
+#define DEFINE_RMW(NAME, EXPR) \
+ \
+ uint32_t \
+ __atomic_fetch_##NAME##_4 (volatile void *mem, uint32_t val, int model) \
+ { \
+ volatile uint32_t *m = mem; \
+ uint32_t old, new_; \
+ \
+ do \
+ { \
+ old = *m; \
+ new_ = (EXPR); \
+ } \
+ while (cas32 (m, old, new_) != old); \
+ return old; \
+ } \
+ \
+ uint32_t \
+ __atomic_##NAME##_fetch_4 (volatile void *mem, uint32_t val, int model) \
+ { \
+ volatile uint32_t *m = mem; \
+ uint32_t old, new_; \
+ \
+ do \
+ { \
+ old = *m; \
+ new_ = (EXPR); \
+ } \
+ while (cas32 (m, old, new_) != old); \
+ return new_; \
+ }
+
+DEFINE_RMW (add, (uint32_t) (old + val))
+DEFINE_RMW (sub, (uint32_t) (old - val))
+DEFINE_RMW (and, (uint32_t) (old & val))
+DEFINE_RMW (or, (uint32_t) (old | val))
+DEFINE_RMW (xor, (uint32_t) (old ^ val))
+DEFINE_RMW (nand, (uint32_t) ~(old & val))
+
+DIAG_POP_NEEDS_COMMENT;
+
+/* Named for this file, not the compiler-runtime ABI it stands in for -
+ nothing calls it implicitly. */
+void
+__sparc32_atomic_store_4 (volatile void *mem, uint32_t val)
+{
+ volatile uint32_t *m = mem;
+ uint32_t old;
+
+ do
+ old = *m;
+ while (cas32 (m, old, val) != old);
+}
+
+/* Backs atomic_exchange_relaxed/_acquire/_release - GCC's inline swap
+ can't compose with cas32(); see the commit message. */
+uint32_t
+__sparc32_atomic_exchange_4 (volatile void *mem, uint32_t val)
+{
+ volatile uint32_t *m = mem;
+ uint32_t old;
+
+ do
+ old = *m;
+ while (cas32 (m, old, val) != old);
+ return old;
+}
+
+#endif /* SPARC32_ATOMIC_OPS */
diff --git a/sysdeps/sparc/sparc32/configure b/sysdeps/sparc/sparc32/configure
index 5d2f4864..db13440c 100644
--- a/sysdeps/sparc/sparc32/configure
+++ b/sysdeps/sparc/sparc32/configure
@@ -203,6 +203,9 @@ fi
{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_cas_uses_libatomic" >&5
printf "%s\n" "$libc_cv_cas_uses_libatomic" >&6; }
if test $libc_cv_cas_uses_libatomic = yes; then
- as_fn_error $? "external dependency of libatomic is not supported" "$LINENO" 5
+ printf "%s\n" "#define SPARC32_ATOMIC_OPS 1" >>confdefs.h
+
+ config_vars="$config_vars
+sparc32-atomic-ops = yes"
fi
diff --git a/sysdeps/sparc/sparc32/configure.ac b/sysdeps/sparc/sparc32/configure.ac
index 462a423a..69baa678 100644
--- a/sysdeps/sparc/sparc32/configure.ac
+++ b/sysdeps/sparc/sparc32/configure.ac
@@ -33,5 +33,6 @@ EOF
rm -f conftest.c conftest.s
])
if test $libc_cv_cas_uses_libatomic = yes; then
- AC_MSG_ERROR([external dependency of libatomic is not supported])
+ AC_DEFINE(SPARC32_ATOMIC_OPS)
+ LIBC_CONFIG_VAR([sparc32-atomic-ops], [yes])
fi
diff --git a/sysdeps/unix/sysv/linux/sparc/sparc32/localplt.data b/sysdeps/unix/sysv/linux/sparc/sparc32/localplt.data
index b5fc6d9c..72f5912f 100644
--- a/sysdeps/unix/sysv/linux/sparc/sparc32/localplt.data
+++ b/sysdeps/unix/sysv/linux/sparc/sparc32/localplt.data
@@ -18,3 +18,20 @@ libc.so: free
libc.so: malloc
libc.so: realloc
libm.so: matherr
+# Compiler support routines, called by their public name from wherever the
+# compiler emits them - as with the _Q_ routines above.
+libc.so: __atomic_compare_exchange_4 ?
+libc.so: __atomic_fetch_add_4 ?
+libc.so: __atomic_add_fetch_4 ?
+libc.so: __atomic_fetch_sub_4 ?
+libc.so: __atomic_sub_fetch_4 ?
+libc.so: __atomic_fetch_and_4 ?
+libc.so: __atomic_and_fetch_4 ?
+libc.so: __atomic_fetch_or_4 ?
+libc.so: __atomic_or_fetch_4 ?
+libc.so: __atomic_fetch_xor_4 ?
+libc.so: __atomic_xor_fetch_4 ?
+libc.so: __atomic_fetch_nand_4 ?
+libc.so: __atomic_nand_fetch_4 ?
+libc.so: __sparc32_atomic_store_4 ?
+libc.so: __sparc32_atomic_exchange_4 ?
--
2.43.0
More information about the Libc-alpha
mailing list