[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