From 48251bb97220404d82a939d9c84ca7f0c1f0cf46 Mon Sep 17 00:00:00 2001 From: Greg Burd Date: Wed, 24 Jun 2026 07:52:06 -0400 Subject: [PATCH] os(atomic): add portable pointer-width CAS __os_atomic_cas_ptr Add a pointer-width compare-and-swap to the atomic layer, which until now exposed only 32-bit and 64-bit integer CAS. Native implementations in the GCC __atomic and legacy __sync builtin tiers (over intptr_t, whose codegen is more uniform than pointer-typed C11 atomics and is value-preserving for object pointers). A generic fallback synthesizes it from the tier's sized CAS (64-bit when pointers are 8 bytes, else 32-bit), the mutex-emulated atomic-pool mutex when there are no native atomics, or a plain store in the single-threaded build. This is foundational for lock-free/RCU data structures (Treiber stacks, epoch reclamation) used in the multi-core scaling work. No current caller; added as the substrate the RCU-for-shared-structures track builds on. --- src/dbinc_auto/os_ext.h | 9 +++ src/os/os_atomic.c | 119 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/src/dbinc_auto/os_ext.h b/src/dbinc_auto/os_ext.h index 5adc44525..b618605fa 100644 --- a/src/dbinc_auto/os_ext.h +++ b/src/dbinc_auto/os_ext.h @@ -42,6 +42,9 @@ atomic_value_t __os_atomic_dec __P((ENV *, db_atomic_t *)); int __os_atomic_cas __P((ENV *, db_atomic_t *, atomic_value_t, atomic_value_t)); #endif #if defined(HAVE_ATOMIC_GCC_BUILTIN) && defined(HAVE_ATOMIC_SUPPORT) +int __os_atomic_cas_ptr __P((ENV *, void *volatile *, void *, void *)); +#endif +#if defined(HAVE_ATOMIC_GCC_BUILTIN) && defined(HAVE_ATOMIC_SUPPORT) atomic_value_t __os_atomic_add __P((ENV *, db_atomic_t *, atomic_value_t)); #endif #if defined(HAVE_ATOMIC_GCC_BUILTIN) && defined(HAVE_ATOMIC_SUPPORT) @@ -75,6 +78,9 @@ atomic_value_t __os_atomic_dec __P((ENV *, db_atomic_t *)); int __os_atomic_cas __P((ENV *, db_atomic_t *, atomic_value_t, atomic_value_t)); #endif #if defined(HAVE_ATOMIC_SYNC_BUILTIN) && defined(HAVE_ATOMIC_SUPPORT) +int __os_atomic_cas_ptr __P((ENV *, void *volatile *, void *, void *)); +#endif +#if defined(HAVE_ATOMIC_SYNC_BUILTIN) && defined(HAVE_ATOMIC_SUPPORT) atomic_value_t __os_atomic_add __P((ENV *, db_atomic_t *, atomic_value_t)); #endif #if defined(HAVE_ATOMIC_SYNC_BUILTIN) && defined(HAVE_ATOMIC_SUPPORT) @@ -242,6 +248,9 @@ atomic_value_t __os_atomic_exchange __P((ENV *, db_atomic_t *, atomic_value_t)); #if !defined(HAVE_ATOMIC_SUPPORT) && !defined(HAVE_MUTEX_SUPPORT) void __os_atomic_thread_fence __P((void)); #endif +#if !defined(HAVE_ATOMIC_GCC_BUILTIN) && !defined(HAVE_ATOMIC_SYNC_BUILTIN) +int __os_atomic_cas_ptr __P((ENV *, void *volatile *, void *, void *)); +#endif void __os_gettime __P((ENV *, db_timespec *, int)); int __os_fs_notzero __P((void)); int __os_support_direct_io __P((void)); diff --git a/src/os/os_atomic.c b/src/os/os_atomic.c index 1e809f68a..cadad2281 100644 --- a/src/os/os_atomic.c +++ b/src/os/os_atomic.c @@ -213,6 +213,36 @@ __os_atomic_cas(env, p, oldval, newval) 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)); } +/* + * __os_atomic_cas_ptr -- + * Atomic pointer-width compare-and-swap. If *p equals oldval, set it to + * newval; returns 1 on success, 0 on failure. Implemented over intptr_t + * integer atomics rather than pointer-typed C11 atomics, which have less + * uniform codegen across compilers; the round-trip through intptr_t is + * value-preserving for object pointers. Used by the lock-free cursor + * recycle pool (Treiber stack) in db_am.c. + * + * PUBLIC: #if defined(HAVE_ATOMIC_GCC_BUILTIN) && defined(HAVE_ATOMIC_SUPPORT) + * PUBLIC: int __os_atomic_cas_ptr + * PUBLIC: __P((ENV *, void *volatile *, void *, void *)); + * PUBLIC: #endif + */ +int +__os_atomic_cas_ptr(env, p, oldval, newval) + ENV *env; + void *volatile *p; + void *oldval; + void *newval; +{ + intptr_t expected; + + COMPQUIET(env, NULL); + expected = (intptr_t)oldval; + return (__atomic_compare_exchange_n( + (volatile intptr_t *)p, &expected, (intptr_t)newval, + 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)); +} + /* * __os_atomic_add -- * Atomically add a value, return the new value. @@ -452,6 +482,29 @@ __os_atomic_cas(env, p, oldval, newval) return (__sync_bool_compare_and_swap(&p->value, oldval, newval)); } +/* + * __os_atomic_cas_ptr -- + * Atomic pointer-width compare-and-swap. Returns 1 on success, 0 on + * failure. See the GCC-builtin tier for the intptr_t rationale. + * + * PUBLIC: #if defined(HAVE_ATOMIC_SYNC_BUILTIN) && \ + * PUBLIC: defined(HAVE_ATOMIC_SUPPORT) + * PUBLIC: int __os_atomic_cas_ptr + * PUBLIC: __P((ENV *, void *volatile *, void *, void *)); + * PUBLIC: #endif + */ +int +__os_atomic_cas_ptr(env, p, oldval, newval) + ENV *env; + void *volatile *p; + void *oldval; + void *newval; +{ + COMPQUIET(env, NULL); + return (__sync_bool_compare_and_swap( + (volatile intptr_t *)p, (intptr_t)oldval, (intptr_t)newval)); +} + /* * __os_atomic_add -- * Atomically add a value, return the new value. @@ -2505,3 +2558,69 @@ __os_atomic_thread_fence() } #endif /* !HAVE_ATOMIC_SUPPORT && !HAVE_MUTEX_SUPPORT */ + + +/* + * __os_atomic_cas_ptr -- generic fallback. + * + * The GCC __atomic and legacy __sync tiers above define their own native + * __os_atomic_cas_ptr. For every other tier (x86/ARM inline assembly, + * Solaris, Windows, mutex-emulated, and single-threaded) we synthesize a + * pointer-width CAS from the sized CAS that tier already implements -- + * the 64-bit __os_atomic_cas_64 when pointers are 64 bits wide, otherwise + * the 32-bit __os_atomic_cas. This avoids hand-porting a pointer CAS into + * each of those tiers while still using each platform's native primitive. + * + * PUBLIC: #if !(defined(HAVE_ATOMIC_GCC_BUILTIN) && \ + * PUBLIC: defined(HAVE_ATOMIC_SUPPORT)) && \ + * PUBLIC: !(defined(HAVE_ATOMIC_SYNC_BUILTIN) && \ + * PUBLIC: defined(HAVE_ATOMIC_SUPPORT)) + * PUBLIC: int __os_atomic_cas_ptr + * PUBLIC: __P((ENV *, void *volatile *, void *, void *)); + * PUBLIC: #endif + */ +#if !(defined(HAVE_ATOMIC_GCC_BUILTIN) && defined(HAVE_ATOMIC_SUPPORT)) && \ + !(defined(HAVE_ATOMIC_SYNC_BUILTIN) && defined(HAVE_ATOMIC_SUPPORT)) +int +__os_atomic_cas_ptr(env, p, oldval, newval) + ENV *env; + void *volatile *p; + void *oldval; + void *newval; +{ +#if defined(HAVE_ATOMIC_SUPPORT) && defined(HAVE_64BIT_TYPES) && \ + SIZEOF_CHAR_P == 8 + /* Native 64-bit CAS (x86/ARM asm, Solaris, Windows tiers). */ + return (__os_atomic_cas_64(env, (volatile int64_t *)(void *)p, + (int64_t)(intptr_t)oldval, (int64_t)(intptr_t)newval)); +#elif defined(HAVE_ATOMIC_SUPPORT) && SIZEOF_CHAR_P == 4 + /* + * 32-bit pointers via the native 32-bit CAS. db_atomic_t's only + * member `value` is at offset 0, so the head word aliases it safely. + */ + return (__os_atomic_cas(env, (db_atomic_t *)(void *)p, + (atomic_value_t)(intptr_t)oldval, (atomic_value_t)(intptr_t)newval)); +#elif defined(HAVE_MUTEX_SUPPORT) + /* Mutex-emulated CAS: hash the address to an atomic-pool mutex. */ + db_mutex_t mtx; + int ret; + + mtx = __os_atomic_get_mutex(env, (db_atomic_t *)(void *)p); + MUTEX_LOCK(env, mtx); + if (*p == oldval) { + *p = newval; + ret = 1; + } else + ret = 0; + MUTEX_UNLOCK(env, mtx); + return (ret); +#else + /* No atomics and no mutex emulation: single-threaded only. */ + COMPQUIET(env, NULL); + if (*p != oldval) + return (0); + *p = newval; + return (1); +#endif +} +#endif /* fallback __os_atomic_cas_ptr */