From 50cae1267e1614bb91106a9aab92f2018d8b8d4f Mon Sep 17 00:00:00 2001 From: Joshua Young Date: Sun, 14 Jun 2026 12:46:59 +1000 Subject: [PATCH] Modernize the concurrent-ruby-ext C extension Replace the per-platform CAS and memory-barrier code in `CAtomicReference`, `CAtomicBoolean`, and `CAtomicFixnum` with the helpers in `ruby/atomic.h`, and switch the three classes from the old `DATA_PTR` / `RUBY_NEVER_FREE` pattern to `TypedData_Make_Struct` with proper `dmark`, `dfree`, `dsize`, and (on Ruby >= 2.7) `dcompact`. All atomic primitives (load, set, CAS, exchange) are `static inline` in `atomic_reference.h` and shared across the three classes via a `CR_DEFINE_ATOMIC_DATA_TYPE` macro that keeps the three `rb_data_type_t` definitions in lock-step. Stores publish via `RUBY_ATOMIC_VALUE_SET` (or `__atomic_store_n` on Ruby < 4.0) so the "java volatile" semantics the docstring advertises are preserved across the modernization, and the corresponding `RB_OBJ_WRITTEN` announces the write barrier to the GC. `AtomicReference#get_and_set` now uses a single atomic exchange instead of a CAS retry loop. Each primitive prefers the `ruby/atomic.h` helper when its specific macro is defined and falls back to GCC/Clang `__atomic_*` builtins otherwise, since `RUBY_ATOMIC_PTR_LOAD` only landed in Ruby 3.3 and `RUBY_ATOMIC_VALUE_SET` in 4.0. GC compaction support is gated on `RUBY_API_VERSION_CODE >= 20700` via the `CR_GC_COMPACTION` macro; older Rubies fall back to `rb_gc_mark`. `extconf.rb` drops the `libkern/OSAtomic.h` probe and the `-march` flags, which no longer matter once we're on `ruby/atomic.h` or the compiler atomic builtins. No behavioral change to any public API; the spec suite passes unchanged, including the atomic specs under `GC.stress`. --- ext/concurrent-ruby-ext/atomic_boolean.c | 61 +++++---- ext/concurrent-ruby-ext/atomic_boolean.h | 25 ++-- ext/concurrent-ruby-ext/atomic_fixnum.c | 86 +++++++----- ext/concurrent-ruby-ext/atomic_fixnum.h | 23 ++-- ext/concurrent-ruby-ext/atomic_reference.c | 150 +++++++-------------- ext/concurrent-ruby-ext/atomic_reference.h | 108 ++++++++++++--- ext/concurrent-ruby-ext/extconf.rb | 20 +-- 7 files changed, 261 insertions(+), 212 deletions(-) diff --git a/ext/concurrent-ruby-ext/atomic_boolean.c b/ext/concurrent-ruby-ext/atomic_boolean.c index cdd684245..09d2808fe 100644 --- a/ext/concurrent-ruby-ext/atomic_boolean.c +++ b/ext/concurrent-ruby-ext/atomic_boolean.c @@ -1,54 +1,67 @@ -#include - #include "atomic_boolean.h" #include "atomic_reference.h" -void atomic_boolean_mark(void *value) { - rb_gc_mark_maybe((VALUE) value); -} - -const rb_data_type_t atomic_boolean_type = { - "Concurrent::CAtomicBoolean", - { - atomic_boolean_mark, - RUBY_NEVER_FREE, - }, -}; +CR_DEFINE_ATOMIC_DATA_TYPE(atomic_boolean_type, "Concurrent::CAtomicBoolean"); VALUE atomic_boolean_allocate(VALUE klass) { - return rb_data_typed_object_wrap(klass, (void *) Qfalse, &atomic_boolean_type); + cr_atomic_t *atomic; + VALUE obj = TypedData_Make_Struct(klass, cr_atomic_t, &atomic_boolean_type, atomic); + RB_OBJ_WRITE(obj, &atomic->value, Qfalse); + return obj; } -VALUE method_atomic_boolean_initialize(int argc, VALUE* argv, VALUE self) { +VALUE method_atomic_boolean_initialize(int argc, VALUE *argv, VALUE self) { + cr_atomic_t *atomic; VALUE value = Qfalse; + TypedData_Get_Struct(self, cr_atomic_t, &atomic_boolean_type, atomic); + rb_check_arity(argc, 0, 1); if (argc == 1) value = TRUTHY(argv[0]); - DATA_PTR(self) = (void *) value; - return(self); + RB_OBJ_WRITE(self, &atomic->value, value); + return self; } VALUE method_atomic_boolean_value(VALUE self) { - return(ir_get(self)); + cr_atomic_t *atomic; + TypedData_Get_Struct(self, cr_atomic_t, &atomic_boolean_type, atomic); + return cr_atomic_value_load(atomic); } VALUE method_atomic_boolean_value_set(VALUE self, VALUE value) { + cr_atomic_t *atomic; VALUE new_value = TRUTHY(value); - return(ir_set(self, new_value)); + TypedData_Get_Struct(self, cr_atomic_t, &atomic_boolean_type, atomic); + cr_atomic_value_set(atomic, new_value); + RB_OBJ_WRITTEN(self, Qundef, new_value); + return new_value; } VALUE method_atomic_boolean_true_question(VALUE self) { - return(method_atomic_boolean_value(self)); + return method_atomic_boolean_value(self); } VALUE method_atomic_boolean_false_question(VALUE self) { - VALUE current = method_atomic_boolean_value(self); - return(current == Qfalse ? Qtrue : Qfalse); + return method_atomic_boolean_value(self) == Qfalse ? Qtrue : Qfalse; } VALUE method_atomic_boolean_make_true(VALUE self) { - return(ir_compare_and_set(self, Qfalse, Qtrue)); + cr_atomic_t *atomic; + TypedData_Get_Struct(self, cr_atomic_t, &atomic_boolean_type, atomic); + + if (cr_atomic_value_cas(atomic, Qfalse, Qtrue) == Qfalse) { + RB_OBJ_WRITTEN(self, Qfalse, Qtrue); + return Qtrue; + } + return Qfalse; } VALUE method_atomic_boolean_make_false(VALUE self) { - return(ir_compare_and_set(self, Qtrue, Qfalse)); + cr_atomic_t *atomic; + TypedData_Get_Struct(self, cr_atomic_t, &atomic_boolean_type, atomic); + + if (cr_atomic_value_cas(atomic, Qtrue, Qfalse) == Qtrue) { + RB_OBJ_WRITTEN(self, Qtrue, Qfalse); + return Qtrue; + } + return Qfalse; } diff --git a/ext/concurrent-ruby-ext/atomic_boolean.h b/ext/concurrent-ruby-ext/atomic_boolean.h index 71e7d059e..40788325b 100644 --- a/ext/concurrent-ruby-ext/atomic_boolean.h +++ b/ext/concurrent-ruby-ext/atomic_boolean.h @@ -1,16 +1,17 @@ -#ifndef __ATOMIC_BOOLEAN_H__ -#define __ATOMIC_BOOLEAN_H__ +#ifndef CONCURRENT_RUBY_ATOMIC_BOOLEAN_H +#define CONCURRENT_RUBY_ATOMIC_BOOLEAN_H 1 -#define TRUTHY(value)(value == Qfalse || value == Qnil ? Qfalse : Qtrue) +#include -void atomic_boolean_mark(void*); -VALUE atomic_boolean_allocate(VALUE); -VALUE method_atomic_boolean_initialize(int, VALUE*, VALUE); -VALUE method_atomic_boolean_value(VALUE); -VALUE method_atomic_boolean_value_set(VALUE, VALUE); -VALUE method_atomic_boolean_true_question(VALUE); -VALUE method_atomic_boolean_false_question(VALUE); -VALUE method_atomic_boolean_make_true(VALUE); -VALUE method_atomic_boolean_make_false(VALUE); +#define TRUTHY(value) (((value) == Qfalse || (value) == Qnil) ? Qfalse : Qtrue) + +VALUE atomic_boolean_allocate(VALUE klass); +VALUE method_atomic_boolean_initialize(int argc, VALUE *argv, VALUE self); +VALUE method_atomic_boolean_value(VALUE self); +VALUE method_atomic_boolean_value_set(VALUE self, VALUE value); +VALUE method_atomic_boolean_true_question(VALUE self); +VALUE method_atomic_boolean_false_question(VALUE self); +VALUE method_atomic_boolean_make_true(VALUE self); +VALUE method_atomic_boolean_make_false(VALUE self); #endif diff --git a/ext/concurrent-ruby-ext/atomic_fixnum.c b/ext/concurrent-ruby-ext/atomic_fixnum.c index 088874006..e6e43c047 100644 --- a/ext/concurrent-ruby-ext/atomic_fixnum.c +++ b/ext/concurrent-ruby-ext/atomic_fixnum.c @@ -1,79 +1,101 @@ -#include - #include "atomic_fixnum.h" #include "atomic_reference.h" -void atomic_fixnum_mark(void *value) { - rb_gc_mark_maybe((VALUE) value); -} - -const rb_data_type_t atomic_fixnum_type = { - "Concurrent::CAtomicFixnum", - { - atomic_fixnum_mark, - RUBY_NEVER_FREE, - }, -}; +CR_DEFINE_ATOMIC_DATA_TYPE(atomic_fixnum_type, "Concurrent::CAtomicFixnum"); VALUE atomic_fixnum_allocate(VALUE klass) { - return rb_data_typed_object_wrap(klass, (void *) Qnil, &atomic_fixnum_type); + cr_atomic_t *atomic; + VALUE obj = TypedData_Make_Struct(klass, cr_atomic_t, &atomic_fixnum_type, atomic); + RB_OBJ_WRITE(obj, &atomic->value, Qnil); + return obj; } -VALUE method_atomic_fixnum_initialize(int argc, VALUE* argv, VALUE self) { +VALUE method_atomic_fixnum_initialize(int argc, VALUE *argv, VALUE self) { + cr_atomic_t *atomic; VALUE value = LL2NUM(0); + TypedData_Get_Struct(self, cr_atomic_t, &atomic_fixnum_type, atomic); + rb_check_arity(argc, 0, 1); if (argc == 1) { Check_Type(argv[0], T_FIXNUM); value = argv[0]; } - DATA_PTR(self) = (void *) value; - return(self); + RB_OBJ_WRITE(self, &atomic->value, value); + return self; } VALUE method_atomic_fixnum_value(VALUE self) { - return (VALUE) DATA_PTR(self); + cr_atomic_t *atomic; + TypedData_Get_Struct(self, cr_atomic_t, &atomic_fixnum_type, atomic); + return cr_atomic_value_load(atomic); } VALUE method_atomic_fixnum_value_set(VALUE self, VALUE value) { + cr_atomic_t *atomic; Check_Type(value, T_FIXNUM); - DATA_PTR(self) = (void *) value; - return(value); + TypedData_Get_Struct(self, cr_atomic_t, &atomic_fixnum_type, atomic); + cr_atomic_value_set(atomic, value); + RB_OBJ_WRITTEN(self, Qundef, value); + return value; } -VALUE method_atomic_fixnum_increment(int argc, VALUE* argv, VALUE self) { - long long value = NUM2LL((VALUE) DATA_PTR(self)); +VALUE method_atomic_fixnum_increment(int argc, VALUE *argv, VALUE self) { + cr_atomic_t *atomic; long long delta = 1; + VALUE new_value; + TypedData_Get_Struct(self, cr_atomic_t, &atomic_fixnum_type, atomic); + rb_check_arity(argc, 0, 1); if (argc == 1) { Check_Type(argv[0], T_FIXNUM); delta = NUM2LL(argv[0]); } - return method_atomic_fixnum_value_set(self, LL2NUM(value + delta)); + new_value = LL2NUM(NUM2LL(cr_atomic_value_load(atomic)) + delta); + cr_atomic_value_set(atomic, new_value); + RB_OBJ_WRITTEN(self, Qundef, new_value); + return new_value; } -VALUE method_atomic_fixnum_decrement(int argc, VALUE* argv, VALUE self) { - long long value = NUM2LL((VALUE) DATA_PTR(self)); +VALUE method_atomic_fixnum_decrement(int argc, VALUE *argv, VALUE self) { + cr_atomic_t *atomic; long long delta = 1; + VALUE new_value; + TypedData_Get_Struct(self, cr_atomic_t, &atomic_fixnum_type, atomic); + rb_check_arity(argc, 0, 1); if (argc == 1) { Check_Type(argv[0], T_FIXNUM); delta = NUM2LL(argv[0]); } - return method_atomic_fixnum_value_set(self, LL2NUM(value - delta)); + new_value = LL2NUM(NUM2LL(cr_atomic_value_load(atomic)) - delta); + cr_atomic_value_set(atomic, new_value); + RB_OBJ_WRITTEN(self, Qundef, new_value); + return new_value; } -VALUE method_atomic_fixnum_compare_and_set(VALUE self, VALUE rb_expect, VALUE rb_update) { - Check_Type(rb_expect, T_FIXNUM); - Check_Type(rb_update, T_FIXNUM); - return ir_compare_and_set(self, rb_expect, rb_update); +VALUE method_atomic_fixnum_compare_and_set(VALUE self, VALUE expect, VALUE update) { + cr_atomic_t *atomic; + Check_Type(expect, T_FIXNUM); + Check_Type(update, T_FIXNUM); + TypedData_Get_Struct(self, cr_atomic_t, &atomic_fixnum_type, atomic); + + if (cr_atomic_value_cas(atomic, expect, update) == expect) { + RB_OBJ_WRITTEN(self, expect, update); + return Qtrue; + } + return Qfalse; } VALUE method_atomic_fixnum_update(VALUE self) { + cr_atomic_t *atomic; VALUE old_value, new_value; + TypedData_Get_Struct(self, cr_atomic_t, &atomic_fixnum_type, atomic); + for (;;) { - old_value = method_atomic_fixnum_value(self); + old_value = cr_atomic_value_load(atomic); new_value = rb_yield(old_value); - if (ir_compare_and_set(self, old_value, new_value) == Qtrue) { + if (cr_atomic_value_cas(atomic, old_value, new_value) == old_value) { + RB_OBJ_WRITTEN(self, old_value, new_value); return new_value; } } diff --git a/ext/concurrent-ruby-ext/atomic_fixnum.h b/ext/concurrent-ruby-ext/atomic_fixnum.h index 157e14fc4..c9c671aea 100644 --- a/ext/concurrent-ruby-ext/atomic_fixnum.h +++ b/ext/concurrent-ruby-ext/atomic_fixnum.h @@ -1,14 +1,15 @@ -#ifndef __ATOMIC_FIXNUM_H__ -#define __ATOMIC_FIXNUM_H__ +#ifndef CONCURRENT_RUBY_ATOMIC_FIXNUM_H +#define CONCURRENT_RUBY_ATOMIC_FIXNUM_H 1 -void atomic_fixnum_mark(void*); -VALUE atomic_fixnum_allocate(VALUE); -VALUE method_atomic_fixnum_initialize(int, VALUE*, VALUE); -VALUE method_atomic_fixnum_value(VALUE); -VALUE method_atomic_fixnum_value_set(VALUE, VALUE); -VALUE method_atomic_fixnum_increment(int, VALUE*, VALUE); -VALUE method_atomic_fixnum_decrement(int, VALUE*, VALUE); -VALUE method_atomic_fixnum_compare_and_set(VALUE, VALUE, VALUE); -VALUE method_atomic_fixnum_update(VALUE); +#include + +VALUE atomic_fixnum_allocate(VALUE klass); +VALUE method_atomic_fixnum_initialize(int argc, VALUE *argv, VALUE self); +VALUE method_atomic_fixnum_value(VALUE self); +VALUE method_atomic_fixnum_value_set(VALUE self, VALUE value); +VALUE method_atomic_fixnum_increment(int argc, VALUE *argv, VALUE self); +VALUE method_atomic_fixnum_decrement(int argc, VALUE *argv, VALUE self); +VALUE method_atomic_fixnum_compare_and_set(VALUE self, VALUE expect, VALUE update); +VALUE method_atomic_fixnum_update(VALUE self); #endif diff --git a/ext/concurrent-ruby-ext/atomic_reference.c b/ext/concurrent-ruby-ext/atomic_reference.c index 8154cbb92..9df2edc00 100644 --- a/ext/concurrent-ruby-ext/atomic_reference.c +++ b/ext/concurrent-ruby-ext/atomic_reference.c @@ -1,131 +1,77 @@ -#include -/*#if defined(__sun)*/ -/*#include */ -/*#endif*/ - -/*#ifdef HAVE_LIBKERN_OSATOMIC_H*/ -/*#include */ -/*#endif*/ - -/* -Following the wisdom of postgres, we opt to use platform specific memory barriers when available. -These are generally more performant. In this PR, we add specific cases for i386, x86_64. - -In the future, we could look at using pg's atomics library directly: -https://github.com/postgres/postgres/tree/9d4649ca49416111aee2c84b7e4441a0b7aa2fac/src/include/port/atomics - -Point of contact @ianks -*/ - #include "atomic_reference.h" -#if (defined(__i386__) || defined(__i386)) && (defined(__GNUC__) || defined(__INTEL_COMPILER)) -#define memory_barrier() \ - __asm__ __volatile__ ("lock; addl $0,0(%%esp)" : : : "memory", "cc") -#elif defined(__x86_64__) && (defined(__GNUC__) || defined(__INTEL_COMPILER)) -#define memory_barrier() \ - __asm__ __volatile__ ("lock; addl $0,0(%%rsp)" : : : "memory", "cc") -#elif defined(HAVE_GCC__ATOMIC_INT32_CAS) -#define memory_barrier() \ - __atomic_thread_fence(__ATOMIC_SEQ_CST) -#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)) -#define memory_barrier() \ - __sync_synchronize(); -#elif defined _MSC_VER -#define memory_barrier() \ - MemoryBarrier(); -#elif __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 -#define memory_barrier() \ - OSMemoryBarrier(); +void cr_atomic_mark(void *ptr) { + cr_atomic_t *atomic = (cr_atomic_t *)ptr; +#ifdef CR_GC_COMPACTION + rb_gc_mark_movable(atomic->value); +#else + rb_gc_mark(atomic->value); #endif +} -void ir_mark(void *value) { - rb_gc_mark_maybe((VALUE) value); +size_t cr_atomic_memsize(const void *ptr) { + (void)ptr; + return sizeof(cr_atomic_t); } -const rb_data_type_t ir_type = { - "Concurrent::CAtomicReference", - { - ir_mark, - RUBY_NEVER_FREE, - }, -}; +#ifdef CR_GC_COMPACTION +void cr_atomic_compact(void *ptr) { + /* Stop-the-world; the reference identity is unchanged so neither an atomic + * store nor a write barrier is required. */ + cr_atomic_t *atomic = (cr_atomic_t *)ptr; + atomic->value = rb_gc_location(atomic->value); +} +#endif + +CR_DEFINE_ATOMIC_DATA_TYPE(ir_type, "Concurrent::CAtomicReference"); VALUE ir_alloc(VALUE klass) { - return rb_data_typed_object_wrap(klass, (void *) Qnil, &ir_type); + cr_atomic_t *atomic; + VALUE obj = TypedData_Make_Struct(klass, cr_atomic_t, &ir_type, atomic); + RB_OBJ_WRITE(obj, &atomic->value, Qnil); + return obj; } -VALUE ir_initialize(int argc, VALUE* argv, VALUE self) { +VALUE ir_initialize(int argc, VALUE *argv, VALUE self) { + cr_atomic_t *atomic; VALUE value = Qnil; - if (rb_scan_args(argc, argv, "01", &value) == 1) { - value = argv[0]; - } - DATA_PTR(self) = (void *) value; + TypedData_Get_Struct(self, cr_atomic_t, &ir_type, atomic); + + rb_scan_args(argc, argv, "01", &value); + RB_OBJ_WRITE(self, &atomic->value, value); return Qnil; } VALUE ir_get(VALUE self) { - memory_barrier(); - return (VALUE) DATA_PTR(self); + cr_atomic_t *atomic; + TypedData_Get_Struct(self, cr_atomic_t, &ir_type, atomic); + return cr_atomic_value_load(atomic); } VALUE ir_set(VALUE self, VALUE new_value) { - DATA_PTR(self) = (void *) new_value; - memory_barrier(); + cr_atomic_t *atomic; + TypedData_Get_Struct(self, cr_atomic_t, &ir_type, atomic); + cr_atomic_value_set(atomic, new_value); + RB_OBJ_WRITTEN(self, Qundef, new_value); return new_value; } VALUE ir_get_and_set(VALUE self, VALUE new_value) { + cr_atomic_t *atomic; VALUE old_value; - for (;;) { - old_value = ir_get(self); - if (ir_compare_and_set(self, old_value, new_value) == Qtrue) { - return old_value; - } - } + TypedData_Get_Struct(self, cr_atomic_t, &ir_type, atomic); + old_value = cr_atomic_value_exchange(atomic, new_value); + RB_OBJ_WRITTEN(self, old_value, new_value); + return old_value; } -VALUE ir_compare_and_set(volatile VALUE self, VALUE expect_value, VALUE new_value) { -#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200 - if (atomic_compare_exchange_strong_explicit((_Atomic uintptr_t *)&DATA_PTR(self), &expect_value, new_value, memory_order_seq_cst, memory_order_seq_cst)) { - return Qtrue; - } -#elif defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1050 -#if defined(__i386__) || defined(__ppc__) - if (OSAtomicCompareAndSwap32((int32_t) expect_value, (int32_t) new_value, (int32_t*) &DATA_PTR(self))) { - return Qtrue; - } -#else - if (OSAtomicCompareAndSwap64(expect_value, new_value, &DATA_PTR(self))) { - return Qtrue; - } -#endif -#elif defined(__sun) - /* Assuming VALUE is uintptr_t */ - /* Based on the definition of uintptr_t from /usr/include/sys/int_types.h */ -#if defined(_LP64) || defined(_I32LPx) - /* 64-bit: uintptr_t === unsigned long */ - if (atomic_cas_ulong((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) { - return Qtrue; - } -#else - /* 32-bit: uintptr_t === unsigned int */ - if (atomic_cas_uint((uintptr_t *) &DATA_PTR(self), expect_value, new_value)) { - return Qtrue; - } -#endif -#elif defined _MSC_VER && defined _M_AMD64 - if (InterlockedCompareExchange64((LONGLONG*)&DATA_PTR(self), new_value, expect_value)) { - return Qtrue; - } -#elif defined _MSC_VER && defined _M_IX86 - if (InterlockedCompareExchange((LONG*)&DATA_PTR(self), new_value, expect_value)) { - return Qtrue; - } -#else - if (__sync_bool_compare_and_swap(&DATA_PTR(self), (void *)expect_value, (void *)new_value)) { +VALUE ir_compare_and_set(VALUE self, VALUE expect_value, VALUE new_value) { + cr_atomic_t *atomic; + TypedData_Get_Struct(self, cr_atomic_t, &ir_type, atomic); + + if (cr_atomic_value_cas(atomic, expect_value, new_value) == expect_value) { + RB_OBJ_WRITTEN(self, expect_value, new_value); return Qtrue; } -#endif return Qfalse; } diff --git a/ext/concurrent-ruby-ext/atomic_reference.h b/ext/concurrent-ruby-ext/atomic_reference.h index 34136759c..b7176aacd 100644 --- a/ext/concurrent-ruby-ext/atomic_reference.h +++ b/ext/concurrent-ruby-ext/atomic_reference.h @@ -1,23 +1,101 @@ -#ifndef __ATOMIC_REFERENCE_H__ -#define __ATOMIC_REFERENCE_H__ +#ifndef CONCURRENT_RUBY_ATOMIC_REFERENCE_H +#define CONCURRENT_RUBY_ATOMIC_REFERENCE_H 1 -#if defined(__sun) -#include +#include +#include + +#ifdef HAVE_RUBY_ATOMIC_H +#include +#endif + +#if RUBY_API_VERSION_CODE >= 20700 +#define CR_GC_COMPACTION 1 +#endif + +/* Storage shared by CAtomicReference, CAtomicBoolean, and CAtomicFixnum. */ +typedef struct { + VALUE value; +} cr_atomic_t; + +void cr_atomic_mark(void *ptr); +size_t cr_atomic_memsize(const void *ptr); +#ifdef CR_GC_COMPACTION +void cr_atomic_compact(void *ptr); +#define CR_ATOMIC_DCOMPACT_INIT .dcompact = cr_atomic_compact, +#else +#define CR_ATOMIC_DCOMPACT_INIT +#endif + +#define CR_DEFINE_ATOMIC_DATA_TYPE(var, name) \ + static const rb_data_type_t var = { \ + .wrap_struct_name = (name), \ + .function = { \ + .dmark = cr_atomic_mark, \ + .dfree = RUBY_TYPED_DEFAULT_FREE, \ + .dsize = cr_atomic_memsize, \ + CR_ATOMIC_DCOMPACT_INIT \ + }, \ + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED, \ + } + +/* Each primitive prefers ruby/atomic.h's helper when defined and falls back + * to GCC/Clang __atomic_* builtins. Not every macro is available on every + * supported Ruby: RUBY_ATOMIC_PTR_LOAD landed in 3.3, RUBY_ATOMIC_VALUE_SET + * in 4.0; the others are present from 3.0. */ + +static inline VALUE cr_atomic_value_load(cr_atomic_t *atomic) { +#if defined(RUBY_ATOMIC_PTR_LOAD) + return (VALUE)RUBY_ATOMIC_PTR_LOAD(atomic->value); +#elif defined(__GNUC__) || defined(__clang__) + return __atomic_load_n(&atomic->value, __ATOMIC_SEQ_CST); +#else +#error "concurrent-ruby-ext requires RUBY_ATOMIC_PTR_LOAD or GCC/Clang atomic builtins" #endif +} + +/* The mutating helpers below do not announce a write barrier; callers must + * follow each successful store with RB_OBJ_WRITTEN(self, oldv, newv) so the + * WB_PROTECTED contract is honored. */ -#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101200 -#include +static inline void cr_atomic_value_set(cr_atomic_t *atomic, VALUE val) { +#if defined(RUBY_ATOMIC_VALUE_SET) + RUBY_ATOMIC_VALUE_SET(atomic->value, val); +#elif defined(__GNUC__) || defined(__clang__) + __atomic_store_n(&atomic->value, val, __ATOMIC_SEQ_CST); +#else +#error "concurrent-ruby-ext requires RUBY_ATOMIC_VALUE_SET or GCC/Clang atomic builtins" #endif -#ifdef HAVE_LIBKERN_OSATOMIC_H -#include +} + +/* Returns the previous slot value; the swap succeeded iff it equals oldval. */ +static inline VALUE cr_atomic_value_cas(cr_atomic_t *atomic, VALUE oldval, VALUE newval) { +#if defined(RUBY_ATOMIC_VALUE_CAS) + return RUBY_ATOMIC_VALUE_CAS(atomic->value, oldval, newval); +#elif defined(__GNUC__) || defined(__clang__) + VALUE expected = oldval; + __atomic_compare_exchange_n(&atomic->value, &expected, newval, 0, + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); + return expected; +#else +#error "concurrent-ruby-ext requires RUBY_ATOMIC_VALUE_CAS or GCC/Clang atomic builtins" +#endif +} + +static inline VALUE cr_atomic_value_exchange(cr_atomic_t *atomic, VALUE val) { +#if defined(RUBY_ATOMIC_VALUE_EXCHANGE) + return RUBY_ATOMIC_VALUE_EXCHANGE(atomic->value, val); +#elif defined(__GNUC__) || defined(__clang__) + return __atomic_exchange_n(&atomic->value, val, __ATOMIC_SEQ_CST); +#else +#error "concurrent-ruby-ext requires RUBY_ATOMIC_VALUE_EXCHANGE or GCC/Clang atomic builtins" #endif +} -void ir_mark(void*); -VALUE ir_alloc(VALUE); -VALUE ir_initialize(int, VALUE*, VALUE); -VALUE ir_get(VALUE); -VALUE ir_set(VALUE, VALUE); -VALUE ir_get_and_set(VALUE, VALUE); -VALUE ir_compare_and_set(volatile VALUE, VALUE, VALUE); +VALUE ir_alloc(VALUE klass); +VALUE ir_initialize(int argc, VALUE *argv, VALUE self); +VALUE ir_get(VALUE self); +VALUE ir_set(VALUE self, VALUE new_value); +VALUE ir_get_and_set(VALUE self, VALUE new_value); +VALUE ir_compare_and_set(VALUE self, VALUE expect_value, VALUE new_value); #endif diff --git a/ext/concurrent-ruby-ext/extconf.rb b/ext/concurrent-ruby-ext/extconf.rb index 2d2cd8290..5454c7c0b 100644 --- a/ext/concurrent-ruby-ext/extconf.rb +++ b/ext/concurrent-ruby-ext/extconf.rb @@ -1,27 +1,15 @@ -require 'fileutils' require 'mkmf' -unless RUBY_ENGINE == "ruby" - File.write("Makefile", dummy_makefile($srcdir).join("")) +unless RUBY_ENGINE == 'ruby' + File.write('Makefile', dummy_makefile($srcdir).join('')) exit end extension_name = 'concurrent_ruby_ext' dir_config(extension_name) -have_header "libkern/OSAtomic.h" -compiler_is_gcc = (CONFIG["GCC"] && !CONFIG["GCC"].empty?) || - # This could stand to be more generic... but I am afraid. - CONFIG["CC"] =~ /\bgcc\b/ - -if compiler_is_gcc - case CONFIG["arch"] - when /mswin32|mingw|solaris/ - $CFLAGS += " -march=native" - when 'i686-linux' - $CFLAGS += " -march=i686" - end -end +# ruby/atomic.h only became a public extension header in Ruby 3.0. +have_header('ruby/atomic.h') create_makefile File.join('concurrent', extension_name)