use more relaxed atomic constraints
diff --git a/include/mimalloc/alloc/page.h b/include/mimalloc/alloc/page.h index 0c0dde4..de02d5b 100644 --- a/include/mimalloc/alloc/page.h +++ b/include/mimalloc/alloc/page.h
@@ -371,7 +371,11 @@ const mi_thread_id_t tid = (theap == NULL ? MI_THREAD_ID_ABANDONED : theap->tld->thread_id); mi_assert_internal((tid & MI_PAGE_FLAG_MASK) == 0); - // we need to use an atomic cas since a concurrent thread may still set the MI_PAGE_HAS_INTERIOR_POINTERS flag (see `alloc_aligned.c`). + // Preserve flags set by concurrent aligned allocations. + if (theap == NULL) { + mi_atomic_and_release(&page->xthread_id, MI_PAGE_FLAG_MASK); + return; + } mi_thread_id_t xtid_old = mi_page_xthread_id(page); mi_thread_id_t xtid; do {
diff --git a/include/mimalloc/atomic.h b/include/mimalloc/atomic.h index f3ed906..44e07cc 100644 --- a/include/mimalloc/atomic.h +++ b/include/mimalloc/atomic.h
@@ -81,6 +81,7 @@ #define mi_atomic_cas_weak_release(p,exp,des) mi_atomic_cas_weak(p,exp,des,mi_memory_order(release),mi_memory_order(relaxed)) #define mi_atomic_cas_weak_acq_rel(p,exp,des) mi_atomic_cas_weak(p,exp,des,mi_memory_order(acq_rel),mi_memory_order(acquire)) #define mi_atomic_cas_strong_relaxed(p,exp,des) mi_atomic_cas_strong(p,exp,des,mi_memory_order(relaxed),mi_memory_order(relaxed)) +#define mi_atomic_cas_strong_acquire(p,exp,des) mi_atomic_cas_strong(p,exp,des,mi_memory_order(acquire),mi_memory_order(relaxed)) #define mi_atomic_cas_strong_release(p,exp,des) mi_atomic_cas_strong(p,exp,des,mi_memory_order(release),mi_memory_order(relaxed)) #define mi_atomic_cas_strong_acq_rel(p,exp,des) mi_atomic_cas_strong(p,exp,des,mi_memory_order(acq_rel),mi_memory_order(acquire)) @@ -89,6 +90,8 @@ #define mi_atomic_sub_relaxed(p,x) mi_atomic(fetch_sub_explicit)(p,x,mi_memory_order(relaxed)) #define mi_atomic_sub_acq_rel(p,x) mi_atomic(fetch_sub_explicit)(p,x,mi_memory_order(acq_rel)) #define mi_atomic_and_relaxed(p,x) mi_atomic(fetch_and_explicit)(p,x,mi_memory_order(relaxed)) +#define mi_atomic_and_acquire(p,x) mi_atomic(fetch_and_explicit)(p,x,mi_memory_order(acquire)) +#define mi_atomic_and_release(p,x) mi_atomic(fetch_and_explicit)(p,x,mi_memory_order(release)) #define mi_atomic_and_acq_rel(p,x) mi_atomic(fetch_and_explicit)(p,x,mi_memory_order(acq_rel)) #define mi_atomic_or_relaxed(p,x) mi_atomic(fetch_or_explicit)(p,x,mi_memory_order(relaxed)) #define mi_atomic_or_acq_rel(p,x) mi_atomic(fetch_or_explicit)(p,x,mi_memory_order(acq_rel)) @@ -148,7 +151,7 @@ } static inline void mi_atomic_volatile_maxi64_relaxed(volatile int64_t* p, int64_t x) { int64_t current = mi_atomic_load_relaxed((_Atomic(int64_t)*)p); - while (current < x && !mi_atomic_cas_weak_release((_Atomic(int64_t)*)p, ¤t, x)) { /* nothing */ }; + while (current < x && !mi_atomic_cas_weak_relaxed((_Atomic(int64_t)*)p, ¤t, x)) { /* nothing */ }; } // Used by timers @@ -158,6 +161,8 @@ #define mi_atomic_storei64_relaxed(p,x) mi_atomic(store_explicit)(p,x,mi_memory_order(relaxed)) #define mi_atomic_casi64_strong_acq_rel(p,e,d) mi_atomic_cas_strong_acq_rel(p,e,d) +#define mi_atomic_casi64_strong_relaxed(p,e,d) mi_atomic_cas_strong_relaxed(p,e,d) +#define mi_atomic_casi64_strong_release(p,e,d) mi_atomic_cas_strong_release(p,e,d) #define mi_atomic_addi64_acq_rel(p,i) mi_atomic_add_acq_rel(p,i) #define mi_atomic_addi64_relaxed(p,i) mi_atomic_add_relaxed(p,i) @@ -367,6 +372,9 @@ } } +#define mi_atomic_casi64_strong_relaxed(p,e,d) mi_atomic_casi64_strong_acq_rel(p,e,d) +#define mi_atomic_casi64_strong_release(p,e,d) mi_atomic_casi64_strong_acq_rel(p,e,d) + // The pointer macros cast to `uintptr_t`. #define mi_atomic_load_ptr_acquire(tp,p) ((tp*)mi_atomic_load_acquire((_Atomic(uintptr_t)*)(p))) #define mi_atomic_load_ptr_relaxed(tp,p) ((tp*)mi_atomic_load_relaxed((_Atomic(uintptr_t)*)(p))) @@ -417,7 +425,7 @@ #define mi_atomic_guard(guard) \ size_t _mi_guard_expected = 0; \ for(bool _mi_guard_once = true; \ - _mi_guard_once && mi_atomic_cas_strong_acq_rel(guard,&_mi_guard_expected,(size_t)1); \ + _mi_guard_once && mi_atomic_cas_strong_acquire(guard,&_mi_guard_expected,(size_t)1); \ (mi_atomic_store_release(guard,(size_t)0), _mi_guard_once = false) ) @@ -542,7 +550,7 @@ static inline bool mi_lock_try_acquire(mi_lock_t* lock) { size_t expected = 0; - return mi_atomic_cas_strong_acq_rel(&lock->mutex, &expected, (size_t)1); + return mi_atomic_cas_strong_acquire(&lock->mutex, &expected, (size_t)1); } static inline void mi_lock_acquire(mi_lock_t* lock) { for (int i = 0; i < 10000; i++) { // for at most 10000 tries?
diff --git a/src/alloc/alloc-generic.c b/src/alloc/alloc-generic.c index 33c80e7..4374dc2 100644 --- a/src/alloc/alloc-generic.c +++ b/src/alloc/alloc-generic.c
@@ -344,7 +344,7 @@ // check if the profiler is enabled (unless this theap is permanently excluded, see `mi_theap_profile_never`) if (!theap->profile_never) { mi_heap_t* const heap = mi_theap_heap(theap); - mi_profiler_t* prof = mi_atomic_load_ptr_relaxed(mi_profiler_t, &heap->profiler); + mi_profiler_t* prof = mi_atomic_load_ptr_acquire(mi_profiler_t, &heap->profiler); const bool prof_enabled = (prof!=NULL && mi_profiler_is_enabled(prof)); if (prof_enabled && theap->profile_sample_rate==0) { _mi_theap_set_profile_sample_rate(theap,mi_max(1,prof->initial_sample_rate)); // start profiling
diff --git a/src/alloc/alloc.c b/src/alloc/alloc.c index 1be26dc..02c145e 100644 --- a/src/alloc/alloc.c +++ b/src/alloc/alloc.c
@@ -736,7 +736,7 @@ static size_t mi_path_max(void) { static _Atomic(size_t) path_max; // = 0 - size_t pmax = mi_atomic_load_acquire(&path_max); + size_t pmax = mi_atomic_load_relaxed(&path_max); if (pmax == 0) { long m = 0; #ifdef _PC_PATH_MAX @@ -747,7 +747,7 @@ else if (m > 64*1024) pmax = 64*1024; // at most 64 KiB else pmax = m; size_t expected = 0; - mi_atomic_cas_strong_acq_rel(&path_max, &expected, pmax); + mi_atomic_cas_strong_relaxed(&path_max, &expected, pmax); } return pmax; }
diff --git a/src/alloc/free.c b/src/alloc/free.c index 77cfa7e..0542cbb 100644 --- a/src/alloc/free.c +++ b/src/alloc/free.c
@@ -515,7 +515,7 @@ // try to cas atomically the original free list (`mt_free`) back with the ownership cleared. mi_thread_free_t tf_expect = mi_tf_create(page_thread_free,true); mi_thread_free_t tf_new = mi_tf_set_owned(tf_expect,false); - while mi_unlikely(!mi_atomic_cas_strong_acq_rel(&page->xthread_free, &tf_expect, tf_new)) { + while mi_unlikely(!mi_atomic_cas_strong_release(&page->xthread_free, &tf_expect, tf_new)) { mi_assert_internal(mi_tf_is_owned(tf_expect)); // while the xthread_free list is not empty.. while (mi_tf_block(tf_expect) != NULL) {
diff --git a/src/alloc/page.c b/src/alloc/page.c index 90809d2..313b42f 100644 --- a/src/alloc/page.c +++ b/src/alloc/page.c
@@ -356,20 +356,14 @@ if (mi_page_all_free(page)) { _mi_page_qsbr_clear(page); } } -// Collect the local `thread_free` list using an atomic exchange. +// Collect the thread free list while preserving ownership. // Returns `true` if a non-empty thread free list was collected. static bool mi_page_thread_free_collect(mi_page_t* page) { - // atomically capture the thread free list - mi_block_t* head; - mi_thread_free_t tfreex; - mi_thread_free_t tfree = mi_atomic_load_relaxed(&page->xthread_free); - do { - head = mi_tf_block(tfree); - if mi_likely(head == NULL) return false; // return if the list is empty - tfreex = mi_tf_create(NULL,mi_tf_is_owned(tfree)); // set the thread free list to NULL - } while (!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tfree, tfreex)); // release is enough? - mi_assert_internal(head != NULL); + if mi_likely(mi_page_thread_free(page) == NULL) return false; + const mi_thread_free_t tfree = mi_atomic_and_acquire(&page->xthread_free, mi_tf_create(NULL, true)); + mi_block_t* const head = mi_tf_block(tfree); + if (head == NULL) return false; // and move it to the local list mi_page_thread_collect_to_local(page, head);
diff --git a/src/arena/arena-alloc.c b/src/arena/arena-alloc.c index 004eedc..ce823d2 100644 --- a/src/arena/arena-alloc.c +++ b/src/arena/arena-alloc.c
@@ -413,11 +413,11 @@ // schedule purge const mi_msecs_t expire = _mi_clock_now() + delay; mi_msecs_t expire0 = 0; - if (mi_atomic_casi64_strong_acq_rel(&arena->purge_expire, &expire0, expire)) { + if (mi_atomic_casi64_strong_release(&arena->purge_expire, &expire0, expire)) { // expiration was not yet set // maybe set the global arenas expire as well (if it wasn't set already) mi_assert_internal(expire0==0); - mi_atomic_casi64_strong_acq_rel(&arena->subproc->purge_expire, &expire0, expire); + mi_atomic_casi64_strong_release(&arena->subproc->purge_expire, &expire0, expire); } else { // already an expiration was set
diff --git a/src/arena/arena-page.c b/src/arena/arena-page.c index c8d397c..9b2a8b0 100644 --- a/src/arena/arena-page.c +++ b/src/arena/arena-page.c
@@ -1023,7 +1023,7 @@ } mi_assert_internal(mi_tf_block(tf_old)==NULL); tf_new = mi_tf_create(NULL, false); - } while (!mi_atomic_cas_weak_acq_rel(&page->xthread_free, &tf_old, tf_new)); + } while (!mi_atomic_cas_weak_release(&page->xthread_free, &tf_old, tf_new)); return false; }
diff --git a/src/arena/arena.c b/src/arena/arena.c index 1623403..2be0b6e 100644 --- a/src/arena/arena.c +++ b/src/arena/arena.c
@@ -476,7 +476,7 @@ // otherwise, try to allocate a fresh slot while(count<MI_MAX_ARENAS) { - if (mi_atomic_cas_strong_release(&subproc->arena_count, &count, count+1)) { + if (mi_atomic_cas_strong_relaxed(&subproc->arena_count, &count, count+1)) { arena->arena_idx = count; expected = NULL; if (mi_atomic_cas_ptr_strong_release(mi_arena_t, &subproc->arenas[count], &expected, arena)) {
diff --git a/src/arena/bitmap-chunk.c b/src/arena/bitmap-chunk.c index 32122ff..ff38c43 100644 --- a/src/arena/bitmap-chunk.c +++ b/src/arena/bitmap-chunk.c
@@ -124,7 +124,8 @@ old = mi_atomic_load_acquire(b); } } - } while (!mi_atomic_cas_weak_acq_rel(b,&old, (old&~mask))); + old = mi_atomic_and_acq_rel(b, ~mask); + } while ((old&mask)==0); mi_assert_internal((old&mask)==mask); // we should only clear when it was set }
diff --git a/src/heap/page-map.c b/src/heap/page-map.c index f5a17dd..923eaba 100644 --- a/src/heap/page-map.c +++ b/src/heap/page-map.c
@@ -219,12 +219,7 @@ _mi_warning_message("internal error: unable to extend the page map\n"); } else { - mi_submap_t expect = NULL; - if (!mi_atomic_cas_ptr_strong_acq_rel(mi_page_t*, &pmap->submaps[idx], &expect, sub)) { - // another thread already allocated it.. free and continue - _mi_os_free_with_guards(subproc, memid); - sub = expect; - } + mi_atomic_store_ptr_release(mi_page_t*, &pmap->submaps[idx], sub); // ok, since we hold the lock } } }
diff --git a/src/heap/process.c b/src/heap/process.c index 6a3bc71..a326d6f 100644 --- a/src/heap/process.c +++ b/src/heap/process.c
@@ -746,7 +746,7 @@ void mi_cdecl mi_process_done(void) mi_attr_noexcept { // we don't use `mi_atomic_do_once` here to avoid deadlocks if threads are killed due to a forced exit (issue #1377) size_t expected = 0; - if (mi_atomic_cas_strong_acq_rel(&_mi_process.process_done_called, &expected, 1)) { + if (mi_atomic_cas_strong_relaxed(&_mi_process.process_done_called, &expected, 1)) { mi_process_done_once(); } }
diff --git a/src/prim/os.c b/src/prim/os.c index 8726a02..e784653 100644 --- a/src/prim/os.c +++ b/src/prim/os.c
@@ -170,7 +170,7 @@ if (req_size > 32*MI_GiB) return NULL; // guarantee the chance of fixed valid address is at most 1/(MI_HINT_AREA / 1<<34) = 1/256 #endif - mi_addr_t hint = mi_atomic_add_acq_rel(&_mi_process.os.aligned_base, req_size); + mi_addr_t hint = mi_atomic_add_relaxed(&_mi_process.os.aligned_base, req_size); if (hint == 0 || hint > MI_HINT_MAX) { // wrap or initialize mi_addr_t init = MI_HINT_BASE; #if (MI_SECURE || !MI_DEBUG) // security: randomize start of aligned allocations unless in debug mode @@ -180,8 +180,8 @@ init = init + ((MI_AU(MI_MiB) * ((r>>17) & 0x3FFFFF)) % MI_HINT_AREA); // (randomly 22 bits)* 1MiB == 0 to 4TiB #endif mi_addr_t expected = hint + req_size; - mi_atomic_cas_strong_acq_rel(&_mi_process.os.aligned_base, &expected, init); - hint = mi_atomic_add_acq_rel(&_mi_process.os.aligned_base, req_size); // this may still give 0 or > MI_HINT_MAX but that is ok, it is a hint after all + mi_atomic_cas_strong_relaxed(&_mi_process.os.aligned_base, &expected, init); + hint = mi_atomic_add_relaxed(&_mi_process.os.aligned_base, req_size); // this may still give 0 or > MI_HINT_MAX but that is ok, it is a hint after all if (hint==0) return NULL; } const mi_addr_t hint_align = mi_addr_align_up(hint,try_alignment); @@ -812,7 +812,7 @@ #endif } end = start + size; - } while (!mi_atomic_cas_weak_acq_rel(&_mi_process.os.huge_start, &huge_start, end)); + } while (!mi_atomic_cas_weak_relaxed(&_mi_process.os.huge_start, &huge_start, end)); if (total_size != NULL) *total_size = size; return (uint8_t*)mi_ptr_from_addr(start);
diff --git a/src/prim/unix/prim.c b/src/prim/unix/prim.c index 680c521..54152d1 100644 --- a/src/prim/unix/prim.c +++ b/src/prim/unix/prim.c
@@ -438,13 +438,13 @@ // huge page allocation #if MI_OS_HAS_HUGE_PAGES if (allow_large && (large_only || (_mi_os_canuse_large_page(size, try_alignment) && mi_option_is_enabled(mi_option_allow_large_os_pages)))) { - size_t backoff = mi_atomic_load_acquire(&_mi_process.os.large_page_backoff); + size_t backoff = mi_atomic_load_relaxed(&_mi_process.os.large_page_backoff); if (!large_only && backoff > 0) { // If the OS is not configured for large OS pages, or the user does not have // enough permission, the `mmap` will always fail (but it might also fail for other reasons). // Therefore, once a large page allocation failed, we don't try again for `os_large_page_backoff` times // to avoid too many failing calls to mmap. - mi_atomic_cas_strong_acq_rel(&_mi_process.os.large_page_backoff, &backoff, backoff - 1); + mi_atomic_cas_strong_relaxed(&_mi_process.os.large_page_backoff, &backoff, backoff - 1); } else { int lflags = flags & ~MAP_NORESERVE; // using NORESERVE on huge pages seems to fail on Linux @@ -485,7 +485,7 @@ #endif if (large_only) return p; if (p == NULL) { - mi_atomic_store_release(&_mi_process.os.large_page_backoff, (size_t)8); // on error, don't try again for the next N allocations + mi_atomic_store_relaxed(&_mi_process.os.large_page_backoff, (size_t)8); // on error, don't try again for the next N allocations } } }
diff --git a/src/prim/windows/prim.c b/src/prim/windows/prim.c index 7aef3fa..be3b374 100644 --- a/src/prim/windows/prim.c +++ b/src/prim/windows/prim.c
@@ -365,11 +365,11 @@ if ((large_only || (_mi_os_canuse_large_page(size, try_alignment) && mi_option_is_enabled(mi_option_allow_large_os_pages))) && allow_large && (flags&MEM_COMMIT)!=0 && (flags&MEM_RESERVE)!=0) { - size_t backoff = mi_atomic_load_acquire(&_mi_process.os.large_page_backoff); + size_t backoff = mi_atomic_load_relaxed(&_mi_process.os.large_page_backoff); if (!large_only && backoff > 0) { // if a large page allocation fails, it seems the calls to VirtualAlloc get very expensive. // therefore, once a large page allocation failed, we don't try again for `os_large_page_backoff` times. - mi_atomic_cas_strong_acq_rel(&_mi_process.os.large_page_backoff, &backoff, backoff - 1); + mi_atomic_cas_strong_relaxed(&_mi_process.os.large_page_backoff, &backoff, backoff - 1); } else { // large OS pages must always reserve and commit. @@ -378,7 +378,7 @@ if (large_only) return p; // fall back to non-large page allocation on error (`p == NULL`). if (p == NULL) { - mi_atomic_store_release(&_mi_process.os.large_page_backoff, (size_t)10); // on error, don't try again for the next N allocations + mi_atomic_store_relaxed(&_mi_process.os.large_page_backoff, (size_t)10); // on error, don't try again for the next N allocations } } }
diff --git a/src/profile/pprof.c b/src/profile/pprof.c index 97f1687..91c1fad 100644 --- a/src/profile/pprof.c +++ b/src/profile/pprof.c
@@ -267,7 +267,7 @@ while (now >= deadline) { mi_msecs_t next_deadline = deadline + interval_msecs; while (next_deadline <= now) { next_deadline += interval_msecs; } // skip ahead if more than one interval has passed - if (mi_atomic_casi64_strong_acq_rel(time_interval_deadline, &deadline, next_deadline)) { + if (mi_atomic_casi64_strong_relaxed(time_interval_deadline, &deadline, next_deadline)) { return true; } // CAS failed: `deadline` now holds the current value; retry (another thread may have already advanced it far enough)
diff --git a/src/profile/sample-profile.c b/src/profile/sample-profile.c index 4291493..1b0c5de 100644 --- a/src/profile/sample-profile.c +++ b/src/profile/sample-profile.c
@@ -113,7 +113,7 @@ static mi_profiler_t* mi_theap_get_enabled_profiler(const mi_theap_t* theap) { mi_heap_t* const heap = mi_theap_heap(theap); - mi_profiler_t* prof = mi_atomic_load_ptr_relaxed(mi_profiler_t, &heap->profiler); + mi_profiler_t* prof = mi_atomic_load_ptr_acquire(mi_profiler_t, &heap->profiler); if (prof!=NULL && mi_profiler_is_enabled(prof) && prof->on_alloc != NULL) { return prof; } @@ -226,7 +226,7 @@ static bool mi_heap_set_profiler(mi_heap_t* heap, mi_profiler_t* profiler) { mi_profiler_t* previous = (profiler==NULL ? mi_atomic_load_ptr_acquire(mi_profiler_t,&heap->profiler) : NULL); // don't overwrite unless it is NULL - return mi_atomic_cas_ptr_strong_acq_rel(mi_profiler_t, &heap->profiler, &previous, profiler); + return mi_atomic_cas_ptr_strong_release(mi_profiler_t, &heap->profiler, &previous, profiler); } mi_decl_export bool mi_heap_profile(mi_heap_t* heap, mi_profiler_t* profiler) { @@ -245,7 +245,7 @@ if (subproc==NULL) return false; mi_profiler_stop(profiler); mi_profiler_t* previous = (profiler==NULL ? mi_atomic_load_ptr_acquire(mi_profiler_t,&subproc->profiler) : NULL); // don't overwrite unless it is NULL - if (!mi_atomic_cas_ptr_strong_acq_rel(mi_profiler_t,&subproc->profiler, &previous, profiler)) { return false; } + if (!mi_atomic_cas_ptr_strong_release(mi_profiler_t,&subproc->profiler, &previous, profiler)) { return false; } mi_lock(&subproc->heaps_lock) { for (mi_heap_t* heap = subproc->heaps; heap!=NULL; heap = heap->next) { mi_heap_set_profiler(heap,profiler);
diff --git a/src/util/libc.c b/src/util/libc.c index 5242ab1..c4e0e1e 100644 --- a/src/util/libc.c +++ b/src/util/libc.c
@@ -209,8 +209,8 @@ } mi_lock_acquire(&once->lock); - size_t expected = 0; - if (mi_atomic_cas_strong_acq_rel(&once->tid, &expected, current_tid)) { // could use atomic_load/store as well + if (mi_atomic_load_relaxed(&once->tid) == 0) { + mi_atomic_store_relaxed(&once->tid, current_tid); return true; // should execute and release } else {