From aef872e2999a78522b6189a4b96ccd484d2226d4 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 23 Mar 2026 16:21:30 +0100 Subject: [PATCH 1/6] Meson: Enable __atomic and __sync builtins also on Windows We can take advantage of __atomic and __sync builtins when compiling with GCC and CLang on Windows Unfortunately we can't use C11 atomics yet! We also use C++ on Windows, and compatibility between C _Atomic and C++ std::atomic is not guaranteed (see C++/N2741 [3]) References: 1. https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html 2. https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html 3. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2741.htm --- meson.build | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/meson.build b/meson.build index 11525ea84..c7b0223d3 100644 --- a/meson.build +++ b/meson.build @@ -762,22 +762,27 @@ endforeach extra_link_args += pthread_link_args -# Atomics are an optional feature in C11. Also need to check that C11 atomics are lock free. -# On Windows we use the Interlocked family of functions -if host_machine.system() != 'windows' - if cc.links(files('meson-cc-tests/atomic-ops-c11.c'), name: 'Atomic ops: c11') - conf.set('HAVE_C11_ATOMIC_PRIMITIVES', 1) - elif cc.links(files('meson-cc-tests/atomic-ops-cxx11.c'), name: 'Atomic ops: cxx11') - conf.set('HAVE_CXX11_ATOMIC_PRIMITIVES', 1) - elif cc.links(files('meson-cc-tests/atomic-ops-gcc-legacy.c'), name: 'Atomic ops: gcc legacy') - conf.set('HAVE_GCC_LEGACY_ATOMICS', 1) - elif cc.has_header('atomic_ops.h') - conf.set('HAVE_LIB_ATOMIC_OPS', 1) - elif cc.has_header('libkern/OSAtomic.h') - conf.set('HAVE_OS_ATOMIC_OPS', 1) - else - warning('Atomic ops not supported.') - endif +cpp_enabled = host_machine.system() == 'windows' + +if not cpp_enabled and cc.links(files('meson-cc-tests/atomic-ops-c11.c'), name: 'Atomic ops: c11') + # Currently we avoid C11 atomics when using both C and C++. The standards + # do not guarantee compatibility between C11 atomics and C++11 std::atomic + # (though effort is underway, see C++/N2741). We can enable this for selected + # compilers over time. + # + # When not using C++, check if C11 atomics are available and whether atomic + # ints and pointers are lock-free. + conf.set('HAVE_C11_ATOMIC_PRIMITIVES', 1) +elif cc.links(files('meson-cc-tests/atomic-ops-cxx11.c'), name: 'Atomic ops: cxx11') + conf.set('HAVE_CXX11_ATOMIC_PRIMITIVES', 1) +elif cc.links(files('meson-cc-tests/atomic-ops-gcc-legacy.c'), name: 'Atomic ops: gcc legacy') + conf.set('HAVE_GCC_LEGACY_ATOMICS', 1) +elif host_machine.system() != 'windows' and cc.has_header('atomic_ops.h') + conf.set('HAVE_LIB_ATOMIC_OPS', 1) +elif host_machine.system() == 'darwin' and cc.has_header('libkern/OSAtomic.h') + conf.set('HAVE_OS_ATOMIC_OPS', 1) +else + warning('Atomic ops not supported.') endif test_mkdir_c_args = [] From f6da210c49f4fdafcb25e086f41604f9bfa2e212 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 23 Mar 2026 16:24:52 +0100 Subject: [PATCH 2/6] Atomics: Make Interlocked-based atomics MSVC-specific Atomics based on Interlocked functions and MemoryBarrier are quite compiler-agnostic. However, the load / store functions use plain C reads / assignments, and those are not guaranteed to be atomic by the C/C++ standard. They are atomic on all major compilers, though ideally access should be mediated by volatile to make things work in corner cases. As such, unknown compilers should use mutex-based atomics and we should emit a warning. If issues arise, we can add compiler- specific implementations. --- meson.build | 2 +- src/cairo-atomic-private.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index c7b0223d3..5d56c87de 100644 --- a/meson.build +++ b/meson.build @@ -781,7 +781,7 @@ elif host_machine.system() != 'windows' and cc.has_header('atomic_ops.h') conf.set('HAVE_LIB_ATOMIC_OPS', 1) elif host_machine.system() == 'darwin' and cc.has_header('libkern/OSAtomic.h') conf.set('HAVE_OS_ATOMIC_OPS', 1) -else +elif not cc.has_define('_MSC_VER') warning('Atomic ops not supported.') endif diff --git a/src/cairo-atomic-private.h b/src/cairo-atomic-private.h index d8d805771..3796c331d 100644 --- a/src/cairo-atomic-private.h +++ b/src/cairo-atomic-private.h @@ -330,7 +330,7 @@ typedef intptr_t cairo_atomic_intptr_t; #endif /* HAVE_OS_ATOMIC_OPS */ -#if !defined(HAS_ATOMIC_OPS) && defined(_WIN32) +#if !defined(HAS_ATOMIC_OPS) && defined(_MSC_VER) #include #define HAS_ATOMIC_OPS 1 From 3d9e235236541f896c7ea024c66d9b07cf5fad9a Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 23 Mar 2026 16:59:15 +0100 Subject: [PATCH 3/6] Atomics: Implement _cairo_atomic_int_cmpxchg_return_old for MSVC --- src/cairo-atomic-private.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/cairo-atomic-private.h b/src/cairo-atomic-private.h index 3796c331d..eb4ca12a3 100644 --- a/src/cairo-atomic-private.h +++ b/src/cairo-atomic-private.h @@ -360,6 +360,14 @@ _cairo_atomic_int_cmpxchg (cairo_atomic_int_t *x, return InterlockedCompareExchange (x, (LONG)newv, (LONG)oldv) == oldv; } +static cairo_always_inline int +_cairo_atomic_int_cmpxchg_return_old (cairo_atomic_int_t *x, + int oldv, + int newv) +{ + return (int) InterlockedCompareExchange (x, (LONG)newv, (LONG)oldv); +} + static cairo_always_inline void * _cairo_atomic_ptr_get (cairo_atomic_intptr_t *x) { From 2acf6eaf3e5f64f3c6c183a27fa2cb9460ceaccc Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 23 Mar 2026 17:03:49 +0100 Subject: [PATCH 4/6] Atomics: Define macros to avoid fallbacks The four cmpxchg functions must be defined as macros to avoid using fallback definitons. --- src/cairo-atomic-private.h | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/cairo-atomic-private.h b/src/cairo-atomic-private.h index eb4ca12a3..6a31d0b1e 100644 --- a/src/cairo-atomic-private.h +++ b/src/cairo-atomic-private.h @@ -353,21 +353,27 @@ _cairo_atomic_int_get (cairo_atomic_int_t *x) # define _cairo_atomic_int_dec_and_test(x) (InterlockedDecrement (x) == 0) static cairo_always_inline cairo_bool_t -_cairo_atomic_int_cmpxchg (cairo_atomic_int_t *x, - int oldv, - int newv) +_cairo_atomic_int_cmpxchg_impl (cairo_atomic_int_t *x, + int oldv, + int newv) { return InterlockedCompareExchange (x, (LONG)newv, (LONG)oldv) == oldv; } +#define _cairo_atomic_int_cmpxchg(x, oldv, newv) \ + _cairo_atomic_int_cmpxchg_impl(x, oldv, newv) + static cairo_always_inline int -_cairo_atomic_int_cmpxchg_return_old (cairo_atomic_int_t *x, - int oldv, - int newv) +_cairo_atomic_int_cmpxchg_return_old_impl (cairo_atomic_int_t *x, + int oldv, + int newv) { return (int) InterlockedCompareExchange (x, (LONG)newv, (LONG)oldv); } +#define _cairo_atomic_int_cmpxchg_return_old(x, oldv, newv) \ + _cairo_atomic_int_cmpxchg_return_old_impl(x, oldv, newv) + static cairo_always_inline void * _cairo_atomic_ptr_get (cairo_atomic_intptr_t *x) { @@ -376,17 +382,23 @@ _cairo_atomic_ptr_get (cairo_atomic_intptr_t *x) } static cairo_always_inline cairo_bool_t -_cairo_atomic_ptr_cmpxchg (cairo_atomic_intptr_t *x, void *oldv, void *newv) +_cairo_atomic_ptr_cmpxchg_impl (cairo_atomic_intptr_t *x, void *oldv, void *newv) { return InterlockedCompareExchangePointer (x, newv, oldv) == oldv; } +#define _cairo_atomic_ptr_cmpxchg(x, oldv, newv) \ + _cairo_atomic_ptr_cmpxchg_impl(x, oldv, newv) + static cairo_always_inline void * -_cairo_atomic_ptr_cmpxchg_return_old (cairo_atomic_intptr_t *x, void *oldv, void *newv) +_cairo_atomic_ptr_cmpxchg_return_old_impl (cairo_atomic_intptr_t *x, void *oldv, void *newv) { return InterlockedCompareExchangePointer (x, newv, oldv); } +#define _cairo_atomic_ptr_cmpxchg_return_old(x, oldv, newv) \ + _cairo_atomic_ptr_cmpxchg_return_old_impl(x, oldv, newv) + #endif /* !defined(HAS_ATOMIC_OPS) && defined(_WIN32) */ From cd8ae260af3ab90c21e09e7760f209e4fb1860fd Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 23 Mar 2026 18:36:37 +0100 Subject: [PATCH 5/6] Atomics: Enhance libatomic_ops-based implementations --- src/cairo-atomic-private.h | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/cairo-atomic-private.h b/src/cairo-atomic-private.h index 6a31d0b1e..1f3ce1ffd 100644 --- a/src/cairo-atomic-private.h +++ b/src/cairo-atomic-private.h @@ -274,26 +274,35 @@ _cairo_atomic_ptr_get (cairo_atomic_intptr_t *x) #endif /* HAVE_GCC_LEGACY_ATOMICS */ #if HAVE_LIB_ATOMIC_OPS + #include +#include #define HAS_ATOMIC_OPS 1 -typedef AO_t cairo_atomic_int_t; +typedef int cairo_atomic_int_t; -# define _cairo_atomic_int_get(x) (AO_load_full (x)) -# define _cairo_atomic_int_get_relaxed(x) (AO_load_full (x)) -# define _cairo_atomic_int_set_relaxed(x, val) (AO_store_full ((x), (val))) +/* Casts from signed to unsigned must not change representation */ +static_assert((unsigned)-1 == UINT_MAX, + "We require two's complement representation of signed integrals"); -# define _cairo_atomic_int_inc(x) ((void) AO_fetch_and_add1_full(x)) -# define _cairo_atomic_int_dec(x) ((void) AO_fetch_and_sub1_full(x)) -# define _cairo_atomic_int_dec_and_test(x) (AO_fetch_and_sub1_full(x) == 1) -# define _cairo_atomic_int_cmpxchg(x, oldv, newv) AO_compare_and_swap_full(x, oldv, newv) +# define _cairo_atomic_int_get(x) ((int)AO_int_load_full ((unsigned *)(x))) +# define _cairo_atomic_int_get_relaxed(x) ((int)AO_int_load ((unsigned *)(x))) +# define _cairo_atomic_int_set_relaxed(x, val) (AO_int_store ((unsigned *)(x), (unsigned)(val))) -typedef intptr_t cairo_atomic_intptr_t; +# define _cairo_atomic_int_inc(x) ((void) AO_int_fetch_and_add1_full((unsigned *)(x))) +# define _cairo_atomic_int_dec(x) ((void) AO_int_fetch_and_sub1_full((unsigned *)(x))) +# define _cairo_atomic_int_dec_and_test(x) (AO_int_fetch_and_sub1_full((unsigned *)(x)) == 1U) +# define _cairo_atomic_int_cmpxchg(x, oldv, newv) AO_int_compare_and_swap_full((unsigned *)x, (unsigned)(oldv), (unsigned)(newv)) +# define _cairo_atomic_int_cmpxchg_return_old(x, oldv, newv) AO_int_fetch_compare_and_swap_full((unsigned *)x, (unsigned)(oldv), (unsigned)(newv)) + +typedef AO_t cairo_atomic_intptr_t; + +static_assert (sizeof (AO_t) >= sizeof (void *), "AO_t cannot be used for pointers"); # define _cairo_atomic_ptr_get(x) _cairo_atomic_intptr_to_voidptr (AO_load_full (x)) -# define _cairo_atomic_ptr_cmpxchg(x, oldv, newv) \ - _cairo_atomic_int_cmpxchg ((cairo_atomic_intptr_t*)(x), (cairo_atomic_intptr_t)oldv, (cairo_atomic_intptr_t)newv) +# define _cairo_atomic_ptr_cmpxchg(x, oldv, newv) AO_compare_and_swap_full(x, (AO_t)oldv, (AO_t)newv) +# define _cairo_atomic_ptr_cmpxchg_return_old(x, oldv, newv) AO_fetch_compare_and_swap_full(x, (AO_t)oldv, (AO_t)newv) #endif From 5fb0538cce4350e6410201db49babc1882a079f1 Mon Sep 17 00:00:00 2001 From: Luca Bacci Date: Mon, 23 Mar 2026 19:12:11 +0100 Subject: [PATCH 6/6] Meson: Link to libatomic_ops From https://github.com/bdwgc/libatomic_ops/blob/master/README_details.txt: Applications should include atomic_ops.h. Nearly all operations are implemented by header files included from it. It is sometimes necessary, and always recommended to also link against libatomic_ops.a --- meson.build | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 5d56c87de..4beed7888 100644 --- a/meson.build +++ b/meson.build @@ -777,7 +777,8 @@ elif cc.links(files('meson-cc-tests/atomic-ops-cxx11.c'), name: 'Atomic ops: cxx conf.set('HAVE_CXX11_ATOMIC_PRIMITIVES', 1) elif cc.links(files('meson-cc-tests/atomic-ops-gcc-legacy.c'), name: 'Atomic ops: gcc legacy') conf.set('HAVE_GCC_LEGACY_ATOMICS', 1) -elif host_machine.system() != 'windows' and cc.has_header('atomic_ops.h') +elif host_machine.system() != 'windows' and dependency('atomic_ops', required: false).found() + internal_deps += [dependency('atomic_ops')] conf.set('HAVE_LIB_ATOMIC_OPS', 1) elif host_machine.system() == 'darwin' and cc.has_header('libkern/OSAtomic.h') conf.set('HAVE_OS_ATOMIC_OPS', 1)