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, &current, x)) { /* nothing */ };
+  while (current < x && !mi_atomic_cas_weak_relaxed((_Atomic(int64_t)*)p, &current, 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 {