[compiler-rt] [llvm] [tsan] add fetch_max, fetch_min, fetch_umax, fetch_umin (PR #211388)

Mauri de Souza Meneguzzo via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 11 04:11:03 PDT 2026


https://github.com/mauri870 updated https://github.com/llvm/llvm-project/pull/211388

>From 44f3d1ccc01b1a0c8216df20e4a923dc3bd8e69a Mon Sep 17 00:00:00 2001
From: Mauri de Souza Meneguzzo <mauri870 at gmail.com>
Date: Wed, 22 Jul 2026 18:23:17 -0300
Subject: [PATCH 1/2] [tsan] add fetch_max, fetch_min, fetch_umax, fetch_umin

The TSan IR instrumentation pass had no handling for AtomicRMWInst::Max,
Min, UMax, and UMin opcodes. These are emitted by Clang for
std::atomic::fetch_max and fetch_min (introduced in C++26 by P0493R5, and
implemented in libc++ by #180333).

Currently, programs using these operations compiled with -fsanitize=thread
have those atomic accesses left uninstrumented, causing the race detector
to silently miss races involving them.

These max/min sanitizer functions are also needed for instrumentation of new Go
sync/atomic apis in golang/go#63999.
---
 .../include/sanitizer/tsan_interface_atomic.h |  56 +++
 .../lib/tsan/rtl/tsan_interface_atomic.cpp    | 323 ++++++++++++++++++
 .../Instrumentation/ThreadSanitizer.cpp       |   8 +
 .../Instrumentation/ThreadSanitizer/atomic.ll | 166 +++++++++
 4 files changed, 553 insertions(+)

diff --git a/compiler-rt/include/sanitizer/tsan_interface_atomic.h b/compiler-rt/include/sanitizer/tsan_interface_atomic.h
index 74ed91efade04..8f1770dd4d815 100644
--- a/compiler-rt/include/sanitizer/tsan_interface_atomic.h
+++ b/compiler-rt/include/sanitizer/tsan_interface_atomic.h
@@ -159,6 +159,62 @@ __tsan_atomic128 SANITIZER_CDECL __tsan_atomic128_fetch_nand(
     volatile __tsan_atomic128 *a, __tsan_atomic128 v, int mo);
 #endif
 
+__tsan_atomic8 SANITIZER_CDECL
+__tsan_atomic8_fetch_max(volatile __tsan_atomic8 *a, __tsan_atomic8 v, int mo);
+__tsan_atomic16 SANITIZER_CDECL __tsan_atomic16_fetch_max(
+    volatile __tsan_atomic16 *a, __tsan_atomic16 v, int mo);
+__tsan_atomic32 SANITIZER_CDECL __tsan_atomic32_fetch_max(
+    volatile __tsan_atomic32 *a, __tsan_atomic32 v, int mo);
+__tsan_atomic64 SANITIZER_CDECL __tsan_atomic64_fetch_max(
+    volatile __tsan_atomic64 *a, __tsan_atomic64 v, int mo);
+#if __TSAN_HAS_INT128
+__tsan_atomic128
+    SANITIZER_CDECL __tsan_atomic128_fetch_max(volatile __tsan_atomic128 *a,
+                                               __tsan_atomic128 v, int mo);
+#endif
+
+__tsan_atomic8 SANITIZER_CDECL
+__tsan_atomic8_fetch_min(volatile __tsan_atomic8 *a, __tsan_atomic8 v, int mo);
+__tsan_atomic16 SANITIZER_CDECL __tsan_atomic16_fetch_min(
+    volatile __tsan_atomic16 *a, __tsan_atomic16 v, int mo);
+__tsan_atomic32 SANITIZER_CDECL __tsan_atomic32_fetch_min(
+    volatile __tsan_atomic32 *a, __tsan_atomic32 v, int mo);
+__tsan_atomic64 SANITIZER_CDECL __tsan_atomic64_fetch_min(
+    volatile __tsan_atomic64 *a, __tsan_atomic64 v, int mo);
+#if __TSAN_HAS_INT128
+__tsan_atomic128
+    SANITIZER_CDECL __tsan_atomic128_fetch_min(volatile __tsan_atomic128 *a,
+                                               __tsan_atomic128 v, int mo);
+#endif
+
+__tsan_atomic8 SANITIZER_CDECL
+__tsan_atomic8_fetch_umax(volatile __tsan_atomic8 *a, __tsan_atomic8 v, int mo);
+__tsan_atomic16 SANITIZER_CDECL __tsan_atomic16_fetch_umax(
+    volatile __tsan_atomic16 *a, __tsan_atomic16 v, int mo);
+__tsan_atomic32 SANITIZER_CDECL __tsan_atomic32_fetch_umax(
+    volatile __tsan_atomic32 *a, __tsan_atomic32 v, int mo);
+__tsan_atomic64 SANITIZER_CDECL __tsan_atomic64_fetch_umax(
+    volatile __tsan_atomic64 *a, __tsan_atomic64 v, int mo);
+#if __TSAN_HAS_INT128
+__tsan_atomic128
+    SANITIZER_CDECL __tsan_atomic128_fetch_umax(volatile __tsan_atomic128 *a,
+                                                __tsan_atomic128 v, int mo);
+#endif
+
+__tsan_atomic8 SANITIZER_CDECL
+__tsan_atomic8_fetch_umin(volatile __tsan_atomic8 *a, __tsan_atomic8 v, int mo);
+__tsan_atomic16 SANITIZER_CDECL __tsan_atomic16_fetch_umin(
+    volatile __tsan_atomic16 *a, __tsan_atomic16 v, int mo);
+__tsan_atomic32 SANITIZER_CDECL __tsan_atomic32_fetch_umin(
+    volatile __tsan_atomic32 *a, __tsan_atomic32 v, int mo);
+__tsan_atomic64 SANITIZER_CDECL __tsan_atomic64_fetch_umin(
+    volatile __tsan_atomic64 *a, __tsan_atomic64 v, int mo);
+#if __TSAN_HAS_INT128
+__tsan_atomic128
+    SANITIZER_CDECL __tsan_atomic128_fetch_umin(volatile __tsan_atomic128 *a,
+                                                __tsan_atomic128 v, int mo);
+#endif
+
 int SANITIZER_CDECL __tsan_atomic8_compare_exchange_weak(
     volatile __tsan_atomic8 *a, __tsan_atomic8 *c, __tsan_atomic8 v, int mo,
     int fail_mo);
diff --git a/compiler-rt/lib/tsan/rtl/tsan_interface_atomic.cpp b/compiler-rt/lib/tsan/rtl/tsan_interface_atomic.cpp
index 6e6774eacb645..734dcff4c6b16 100644
--- a/compiler-rt/lib/tsan/rtl/tsan_interface_atomic.cpp
+++ b/compiler-rt/lib/tsan/rtl/tsan_interface_atomic.cpp
@@ -162,6 +162,40 @@ a128 func_nand(volatile a128 *v, a128 op) {
   return cmp;
 }
 
+a128 func_max(volatile a128* v, a128 op) {
+  SpinMutexLock lock(&mutex128);
+  a128 cmp = *v;
+  if (op > cmp)
+    *v = op;
+  return cmp;
+}
+
+a128 func_min(volatile a128* v, a128 op) {
+  SpinMutexLock lock(&mutex128);
+  a128 cmp = *v;
+  if (op < cmp)
+    *v = op;
+  return cmp;
+}
+
+a128 func_umax(volatile a128* v, a128 op) {
+  __extension__ typedef unsigned __int128 U;
+  SpinMutexLock lock(&mutex128);
+  a128 cmp = *v;
+  if ((U)op > (U)cmp)
+    *v = op;
+  return cmp;
+}
+
+a128 func_umin(volatile a128* v, a128 op) {
+  __extension__ typedef unsigned __int128 U;
+  SpinMutexLock lock(&mutex128);
+  a128 cmp = *v;
+  if ((U)op < (U)cmp)
+    *v = op;
+  return cmp;
+}
+
 a128 func_cas(volatile a128 *v, a128 cmp, a128 xch) {
   SpinMutexLock lock(&mutex128);
   a128 cur = *v;
@@ -171,6 +205,87 @@ a128 func_cas(volatile a128 *v, a128 cmp, a128 xch) {
 }
 #endif
 
+template <typename T>
+T func_max(volatile T* v, T op) {
+  T cmp = *v;
+  for (;;) {
+    if (cmp >= op)
+      return cmp;
+    T cur = __sync_val_compare_and_swap(v, cmp, op);
+    if (cmp == cur)
+      return cmp;
+    cmp = cur;
+  }
+}
+
+template <typename T>
+T func_min(volatile T* v, T op) {
+  T cmp = *v;
+  for (;;) {
+    if (cmp <= op)
+      return cmp;
+    T cur = __sync_val_compare_and_swap(v, cmp, op);
+    if (cmp == cur)
+      return cmp;
+    cmp = cur;
+  }
+}
+
+// Maps each signed interface type to its unsigned counterpart for use in
+// unsigned max/min comparisons.
+template <typename T>
+struct UnsignedFor;
+template <>
+struct UnsignedFor<a8> {
+  typedef unsigned char type;
+};
+template <>
+struct UnsignedFor<a16> {
+  typedef unsigned short type;
+};
+template <>
+struct UnsignedFor<a32> {
+  typedef unsigned int type;
+};
+template <>
+struct UnsignedFor<a64> {
+  typedef unsigned long type;
+};
+#if __TSAN_HAS_INT128
+template <>
+struct UnsignedFor<a128> {
+  __extension__ typedef unsigned __int128 type;
+};
+#endif
+
+template <typename T>
+T func_umax(volatile T* v, T op) {
+  typedef typename UnsignedFor<T>::type U;
+  T cmp = *v;
+  for (;;) {
+    if ((U)cmp >= (U)op)
+      return cmp;
+    T cur = __sync_val_compare_and_swap(v, cmp, op);
+    if (cmp == cur)
+      return cmp;
+    cmp = cur;
+  }
+}
+
+template <typename T>
+T func_umin(volatile T* v, T op) {
+  typedef typename UnsignedFor<T>::type U;
+  T cmp = *v;
+  for (;;) {
+    if ((U)cmp <= (U)op)
+      return cmp;
+    T cur = __sync_val_compare_and_swap(v, cmp, op);
+    if (cmp == cur)
+      return cmp;
+    cmp = cur;
+  }
+}
+
 template <typename T>
 static int AccessSize() {
   if (sizeof(T) <= 1)
@@ -410,6 +525,58 @@ struct OpFetchNand {
   }
 };
 
+struct OpFetchMax {
+  template <typename T>
+  [[maybe_unused]] static T NoTsanAtomic(morder mo, volatile T* a, T v) {
+    return func_max(a, v);
+  }
+
+  template <typename T>
+  [[maybe_unused]] static T Atomic(ThreadState* thr, uptr pc, morder mo,
+                                   volatile T* a, T v) {
+    return AtomicRMW<T, func_max>(thr, pc, a, v, mo);
+  }
+};
+
+struct OpFetchMin {
+  template <typename T>
+  [[maybe_unused]] static T NoTsanAtomic(morder mo, volatile T* a, T v) {
+    return func_min(a, v);
+  }
+
+  template <typename T>
+  [[maybe_unused]] static T Atomic(ThreadState* thr, uptr pc, morder mo,
+                                   volatile T* a, T v) {
+    return AtomicRMW<T, func_min>(thr, pc, a, v, mo);
+  }
+};
+
+struct OpFetchUMax {
+  template <typename T>
+  [[maybe_unused]] static T NoTsanAtomic(morder mo, volatile T* a, T v) {
+    return func_umax(a, v);
+  }
+
+  template <typename T>
+  [[maybe_unused]] static T Atomic(ThreadState* thr, uptr pc, morder mo,
+                                   volatile T* a, T v) {
+    return AtomicRMW<T, func_umax>(thr, pc, a, v, mo);
+  }
+};
+
+struct OpFetchUMin {
+  template <typename T>
+  [[maybe_unused]] static T NoTsanAtomic(morder mo, volatile T* a, T v) {
+    return func_umin(a, v);
+  }
+
+  template <typename T>
+  [[maybe_unused]] static T Atomic(ThreadState* thr, uptr pc, morder mo,
+                                   volatile T* a, T v) {
+    return AtomicRMW<T, func_umin>(thr, pc, a, v, mo);
+  }
+};
+
 struct OpCAS {
   template <typename T>
   static bool NoTsanAtomic(morder mo, morder fmo, volatile T *a, T *c, T v) {
@@ -787,6 +954,114 @@ a128 __tsan_atomic128_fetch_nand(volatile a128 *a, a128 v, int mo) {
 }
 #  endif
 
+SANITIZER_INTERFACE_ATTRIBUTE
+a8 __tsan_atomic8_fetch_max(volatile a8* a, a8 v, int mo) {
+  return AtomicImpl<OpFetchMax>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a16 __tsan_atomic16_fetch_max(volatile a16* a, a16 v, int mo) {
+  return AtomicImpl<OpFetchMax>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a32 __tsan_atomic32_fetch_max(volatile a32* a, a32 v, int mo) {
+  return AtomicImpl<OpFetchMax>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a64 __tsan_atomic64_fetch_max(volatile a64* a, a64 v, int mo) {
+  return AtomicImpl<OpFetchMax>(to_morder(mo), a, v);
+}
+
+#  if __TSAN_HAS_INT128
+SANITIZER_INTERFACE_ATTRIBUTE
+a128 __tsan_atomic128_fetch_max(volatile a128* a, a128 v, int mo) {
+  return AtomicImpl<OpFetchMax>(to_morder(mo), a, v);
+}
+#  endif
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a8 __tsan_atomic8_fetch_min(volatile a8* a, a8 v, int mo) {
+  return AtomicImpl<OpFetchMin>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a16 __tsan_atomic16_fetch_min(volatile a16* a, a16 v, int mo) {
+  return AtomicImpl<OpFetchMin>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a32 __tsan_atomic32_fetch_min(volatile a32* a, a32 v, int mo) {
+  return AtomicImpl<OpFetchMin>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a64 __tsan_atomic64_fetch_min(volatile a64* a, a64 v, int mo) {
+  return AtomicImpl<OpFetchMin>(to_morder(mo), a, v);
+}
+
+#  if __TSAN_HAS_INT128
+SANITIZER_INTERFACE_ATTRIBUTE
+a128 __tsan_atomic128_fetch_min(volatile a128* a, a128 v, int mo) {
+  return AtomicImpl<OpFetchMin>(to_morder(mo), a, v);
+}
+#  endif
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a8 __tsan_atomic8_fetch_umax(volatile a8* a, a8 v, int mo) {
+  return AtomicImpl<OpFetchUMax>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a16 __tsan_atomic16_fetch_umax(volatile a16* a, a16 v, int mo) {
+  return AtomicImpl<OpFetchUMax>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a32 __tsan_atomic32_fetch_umax(volatile a32* a, a32 v, int mo) {
+  return AtomicImpl<OpFetchUMax>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a64 __tsan_atomic64_fetch_umax(volatile a64* a, a64 v, int mo) {
+  return AtomicImpl<OpFetchUMax>(to_morder(mo), a, v);
+}
+
+#  if __TSAN_HAS_INT128
+SANITIZER_INTERFACE_ATTRIBUTE
+a128 __tsan_atomic128_fetch_umax(volatile a128* a, a128 v, int mo) {
+  return AtomicImpl<OpFetchUMax>(to_morder(mo), a, v);
+}
+#  endif
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a8 __tsan_atomic8_fetch_umin(volatile a8* a, a8 v, int mo) {
+  return AtomicImpl<OpFetchUMin>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a16 __tsan_atomic16_fetch_umin(volatile a16* a, a16 v, int mo) {
+  return AtomicImpl<OpFetchUMin>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a32 __tsan_atomic32_fetch_umin(volatile a32* a, a32 v, int mo) {
+  return AtomicImpl<OpFetchUMin>(to_morder(mo), a, v);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+a64 __tsan_atomic64_fetch_umin(volatile a64* a, a64 v, int mo) {
+  return AtomicImpl<OpFetchUMin>(to_morder(mo), a, v);
+}
+
+#  if __TSAN_HAS_INT128
+SANITIZER_INTERFACE_ATTRIBUTE
+a128 __tsan_atomic128_fetch_umin(volatile a128* a, a128 v, int mo) {
+  return AtomicImpl<OpFetchUMin>(to_morder(mo), a, v);
+}
+#  endif
+
 SANITIZER_INTERFACE_ATTRIBUTE
 int __tsan_atomic8_compare_exchange_strong(volatile a8 *a, a8 *c, a8 v, int mo,
                                            int fmo) {
@@ -1030,5 +1305,53 @@ void __tsan_go_atomic128_compare_exchange(ThreadState* thr, uptr cpc, uptr pc,
   *(bool*)(a + 40) = (cur == cmp);
 }
 #  endif
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tsan_go_atomic32_fetch_max(ThreadState* thr, uptr cpc, uptr pc, u8* a) {
+  *(a32*)(a + 16) = AtomicGoRet<OpFetchMax>(thr, cpc, pc, mo_acq_rel, *(a32**)a,
+                                            *(a32*)(a + 8));
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tsan_go_atomic64_fetch_max(ThreadState* thr, uptr cpc, uptr pc, u8* a) {
+  *(a64*)(a + 16) = AtomicGoRet<OpFetchMax>(thr, cpc, pc, mo_acq_rel, *(a64**)a,
+                                            *(a64*)(a + 8));
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tsan_go_atomic32_fetch_min(ThreadState* thr, uptr cpc, uptr pc, u8* a) {
+  *(a32*)(a + 16) = AtomicGoRet<OpFetchMin>(thr, cpc, pc, mo_acq_rel, *(a32**)a,
+                                            *(a32*)(a + 8));
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tsan_go_atomic64_fetch_min(ThreadState* thr, uptr cpc, uptr pc, u8* a) {
+  *(a64*)(a + 16) = AtomicGoRet<OpFetchMin>(thr, cpc, pc, mo_acq_rel, *(a64**)a,
+                                            *(a64*)(a + 8));
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tsan_go_atomic32_fetch_umax(ThreadState* thr, uptr cpc, uptr pc, u8* a) {
+  *(a32*)(a + 16) = AtomicGoRet<OpFetchUMax>(thr, cpc, pc, mo_acq_rel,
+                                             *(a32**)a, *(a32*)(a + 8));
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tsan_go_atomic64_fetch_umax(ThreadState* thr, uptr cpc, uptr pc, u8* a) {
+  *(a64*)(a + 16) = AtomicGoRet<OpFetchUMax>(thr, cpc, pc, mo_acq_rel,
+                                             *(a64**)a, *(a64*)(a + 8));
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tsan_go_atomic32_fetch_umin(ThreadState* thr, uptr cpc, uptr pc, u8* a) {
+  *(a32*)(a + 16) = AtomicGoRet<OpFetchUMin>(thr, cpc, pc, mo_acq_rel,
+                                             *(a32**)a, *(a32*)(a + 8));
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __tsan_go_atomic64_fetch_umin(ThreadState* thr, uptr cpc, uptr pc, u8* a) {
+  *(a64*)(a + 16) = AtomicGoRet<OpFetchUMin>(thr, cpc, pc, mo_acq_rel,
+                                             *(a64**)a, *(a64*)(a + 8));
+}
 }  // extern "C"
 #endif  // #if !SANITIZER_GO
diff --git a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
index f05efd863fb74..cd0c36b863d43 100644
--- a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
@@ -303,6 +303,14 @@ void ThreadSanitizer::initialize(Module &M, const TargetLibraryInfo &TLI) {
         NamePart = "_fetch_xor";
       else if (Op == AtomicRMWInst::Nand)
         NamePart = "_fetch_nand";
+      else if (Op == AtomicRMWInst::Max)
+        NamePart = "_fetch_max";
+      else if (Op == AtomicRMWInst::Min)
+        NamePart = "_fetch_min";
+      else if (Op == AtomicRMWInst::UMax)
+        NamePart = "_fetch_umax";
+      else if (Op == AtomicRMWInst::UMin)
+        NamePart = "_fetch_umin";
       else
         continue;
       SmallString<32> RMWName("__tsan_atomic" + itostr(BitSize) + NamePart);
diff --git a/llvm/test/Instrumentation/ThreadSanitizer/atomic.ll b/llvm/test/Instrumentation/ThreadSanitizer/atomic.ll
index 8b387cd496297..73c74e4f1d406 100644
--- a/llvm/test/Instrumentation/ThreadSanitizer/atomic.ll
+++ b/llvm/test/Instrumentation/ThreadSanitizer/atomic.ll
@@ -2016,6 +2016,102 @@ entry:
 ; CHECK-LABEL: atomic128_nand_seq_cst
 ; CHECK: call i128 @__tsan_atomic128_fetch_nand(ptr %a, i128 0, i32 5), !dbg
 
+define void @atomic8_max_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw max ptr %a, i8 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic8_max_monotonic
+; CHECK: call i8 @__tsan_atomic8_fetch_max(ptr %a, i8 0, i32 0), !dbg
+
+define void @atomic16_max_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw max ptr %a, i16 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic16_max_monotonic
+; CHECK: call i16 @__tsan_atomic16_fetch_max(ptr %a, i16 0, i32 0), !dbg
+
+define void @atomic32_max_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw max ptr %a, i32 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic32_max_monotonic
+; CHECK: call i32 @__tsan_atomic32_fetch_max(ptr %a, i32 0, i32 0), !dbg
+
+define void @atomic64_max_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw max ptr %a, i64 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic64_max_monotonic
+; CHECK: call i64 @__tsan_atomic64_fetch_max(ptr %a, i64 0, i32 0), !dbg
+
+define void @atomic32_max_seq_cst(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw max ptr %a, i32 0 seq_cst, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic32_max_seq_cst
+; CHECK: call i32 @__tsan_atomic32_fetch_max(ptr %a, i32 0, i32 5), !dbg
+
+define void @atomic64_max_seq_cst(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw max ptr %a, i64 0 seq_cst, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic64_max_seq_cst
+; CHECK: call i64 @__tsan_atomic64_fetch_max(ptr %a, i64 0, i32 5), !dbg
+
+define void @atomic32_min_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw min ptr %a, i32 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic32_min_monotonic
+; CHECK: call i32 @__tsan_atomic32_fetch_min(ptr %a, i32 0, i32 0), !dbg
+
+define void @atomic64_min_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw min ptr %a, i64 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic64_min_monotonic
+; CHECK: call i64 @__tsan_atomic64_fetch_min(ptr %a, i64 0, i32 0), !dbg
+
+define void @atomic32_umax_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw umax ptr %a, i32 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic32_umax_monotonic
+; CHECK: call i32 @__tsan_atomic32_fetch_umax(ptr %a, i32 0, i32 0), !dbg
+
+define void @atomic64_umax_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw umax ptr %a, i64 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic64_umax_monotonic
+; CHECK: call i64 @__tsan_atomic64_fetch_umax(ptr %a, i64 0, i32 0), !dbg
+
+define void @atomic32_umin_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw umin ptr %a, i32 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic32_umin_monotonic
+; CHECK: call i32 @__tsan_atomic32_fetch_umin(ptr %a, i32 0, i32 0), !dbg
+
+define void @atomic64_umin_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw umin ptr %a, i64 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic64_umin_monotonic
+; CHECK: call i64 @__tsan_atomic64_fetch_umin(ptr %a, i64 0, i32 0), !dbg
+
 define void @atomic128_cas_monotonic(ptr %a) nounwind uwtable {
 entry:
   cmpxchg ptr %a, i128 0, i128 1 monotonic monotonic, !dbg !7
@@ -2120,11 +2216,41 @@ entry:
 ; CHECK-LABEL: atomic_thread_fence_seq_cst
 ; CHECK: call void @__tsan_atomic_thread_fence(i32 5), !dbg
 
+; CHECK:    declare i8 @__tsan_atomic8_fetch_max(ptr, i8, i32)
+; EXT:      declare signext i8 @__tsan_atomic8_fetch_max(ptr, i8 signext, i32 signext)
+; MIPS_EXT: declare i8 @__tsan_atomic8_fetch_max(ptr, i8 signext, i32 signext)
+; LA_EXT:   declare signext i8 @__tsan_atomic8_fetch_max(ptr, i8 signext, i32 signext)
+
+; CHECK:    declare i16 @__tsan_atomic16_fetch_max(ptr, i16, i32)
+; EXT:      declare signext i16 @__tsan_atomic16_fetch_max(ptr, i16 signext, i32 signext)
+; MIPS_EXT: declare i16 @__tsan_atomic16_fetch_max(ptr, i16 signext, i32 signext)
+; LA_EXT:   declare signext i16 @__tsan_atomic16_fetch_max(ptr, i16 signext, i32 signext)
+
 ; CHECK:    declare void @__tsan_atomic32_store(ptr, i32, i32)
 ; EXT:      declare void @__tsan_atomic32_store(ptr, i32 signext, i32 signext)
 ; MIPS_EXT: declare void @__tsan_atomic32_store(ptr, i32 signext, i32 signext)
 ; LA_EXT:   declare void @__tsan_atomic32_store(ptr, i32 signext, i32 signext)
 
+; CHECK:    declare i32 @__tsan_atomic32_fetch_max(ptr, i32, i32)
+; EXT:      declare signext i32 @__tsan_atomic32_fetch_max(ptr, i32 signext, i32 signext)
+; MIPS_EXT: declare i32 @__tsan_atomic32_fetch_max(ptr, i32 signext, i32 signext)
+; LA_EXT:   declare signext i32 @__tsan_atomic32_fetch_max(ptr, i32 signext, i32 signext)
+
+; CHECK:    declare i32 @__tsan_atomic32_fetch_min(ptr, i32, i32)
+; EXT:      declare signext i32 @__tsan_atomic32_fetch_min(ptr, i32 signext, i32 signext)
+; MIPS_EXT: declare i32 @__tsan_atomic32_fetch_min(ptr, i32 signext, i32 signext)
+; LA_EXT:   declare signext i32 @__tsan_atomic32_fetch_min(ptr, i32 signext, i32 signext)
+
+; CHECK:    declare i32 @__tsan_atomic32_fetch_umax(ptr, i32, i32)
+; EXT:      declare signext i32 @__tsan_atomic32_fetch_umax(ptr, i32 signext, i32 signext)
+; MIPS_EXT: declare i32 @__tsan_atomic32_fetch_umax(ptr, i32 signext, i32 signext)
+; LA_EXT:   declare signext i32 @__tsan_atomic32_fetch_umax(ptr, i32 signext, i32 signext)
+
+; CHECK:    declare i32 @__tsan_atomic32_fetch_umin(ptr, i32, i32)
+; EXT:      declare signext i32 @__tsan_atomic32_fetch_umin(ptr, i32 signext, i32 signext)
+; MIPS_EXT: declare i32 @__tsan_atomic32_fetch_umin(ptr, i32 signext, i32 signext)
+; LA_EXT:   declare signext i32 @__tsan_atomic32_fetch_umin(ptr, i32 signext, i32 signext)
+
 ; CHECK:    declare i32 @__tsan_atomic32_compare_exchange_val(ptr, i32, i32, i32, i32)
 ; EXT:      declare signext i32 @__tsan_atomic32_compare_exchange_val(ptr, i32 signext, i32 signext, i32 signext, i32 signext)
 ; MIPS_EXT: declare i32 @__tsan_atomic32_compare_exchange_val(ptr, i32 signext, i32 signext, i32 signext, i32 signext)
@@ -2145,11 +2271,51 @@ entry:
 ; MIPS_EXT: declare i64 @__tsan_atomic64_fetch_add(ptr, i64, i32 signext)
 ; LA_EXT:   declare i64 @__tsan_atomic64_fetch_add(ptr, i64, i32 signext)
 
+; CHECK:    declare i64 @__tsan_atomic64_fetch_max(ptr, i64, i32)
+; EXT:      declare i64 @__tsan_atomic64_fetch_max(ptr, i64, i32 signext)
+; MIPS_EXT: declare i64 @__tsan_atomic64_fetch_max(ptr, i64, i32 signext)
+; LA_EXT:   declare i64 @__tsan_atomic64_fetch_max(ptr, i64, i32 signext)
+
+; CHECK:    declare i64 @__tsan_atomic64_fetch_min(ptr, i64, i32)
+; EXT:      declare i64 @__tsan_atomic64_fetch_min(ptr, i64, i32 signext)
+; MIPS_EXT: declare i64 @__tsan_atomic64_fetch_min(ptr, i64, i32 signext)
+; LA_EXT:   declare i64 @__tsan_atomic64_fetch_min(ptr, i64, i32 signext)
+
+; CHECK:    declare i64 @__tsan_atomic64_fetch_umax(ptr, i64, i32)
+; EXT:      declare i64 @__tsan_atomic64_fetch_umax(ptr, i64, i32 signext)
+; MIPS_EXT: declare i64 @__tsan_atomic64_fetch_umax(ptr, i64, i32 signext)
+; LA_EXT:   declare i64 @__tsan_atomic64_fetch_umax(ptr, i64, i32 signext)
+
+; CHECK:    declare i64 @__tsan_atomic64_fetch_umin(ptr, i64, i32)
+; EXT:      declare i64 @__tsan_atomic64_fetch_umin(ptr, i64, i32 signext)
+; MIPS_EXT: declare i64 @__tsan_atomic64_fetch_umin(ptr, i64, i32 signext)
+; LA_EXT:   declare i64 @__tsan_atomic64_fetch_umin(ptr, i64, i32 signext)
+
 ; CHECK:    declare i64 @__tsan_atomic64_compare_exchange_val(ptr, i64, i64, i32, i32)
 ; EXT:      declare i64 @__tsan_atomic64_compare_exchange_val(ptr, i64, i64, i32 signext, i32 signext)
 ; MIPS_EXT: declare i64 @__tsan_atomic64_compare_exchange_val(ptr, i64, i64, i32 signext, i32 signext)
 ; LA_EXT:   declare i64 @__tsan_atomic64_compare_exchange_val(ptr, i64, i64, i32 signext, i32 signext)
 
+; CHECK:    declare i128 @__tsan_atomic128_fetch_max(ptr, i128, i32)
+; EXT:      declare i128 @__tsan_atomic128_fetch_max(ptr, i128, i32 signext)
+; MIPS_EXT: declare i128 @__tsan_atomic128_fetch_max(ptr, i128, i32 signext)
+; LA_EXT:   declare i128 @__tsan_atomic128_fetch_max(ptr, i128, i32 signext)
+
+; CHECK:    declare i128 @__tsan_atomic128_fetch_min(ptr, i128, i32)
+; EXT:      declare i128 @__tsan_atomic128_fetch_min(ptr, i128, i32 signext)
+; MIPS_EXT: declare i128 @__tsan_atomic128_fetch_min(ptr, i128, i32 signext)
+; LA_EXT:   declare i128 @__tsan_atomic128_fetch_min(ptr, i128, i32 signext)
+
+; CHECK:    declare i128 @__tsan_atomic128_fetch_umax(ptr, i128, i32)
+; EXT:      declare i128 @__tsan_atomic128_fetch_umax(ptr, i128, i32 signext)
+; MIPS_EXT: declare i128 @__tsan_atomic128_fetch_umax(ptr, i128, i32 signext)
+; LA_EXT:   declare i128 @__tsan_atomic128_fetch_umax(ptr, i128, i32 signext)
+
+; CHECK:    declare i128 @__tsan_atomic128_fetch_umin(ptr, i128, i32)
+; EXT:      declare i128 @__tsan_atomic128_fetch_umin(ptr, i128, i32 signext)
+; MIPS_EXT: declare i128 @__tsan_atomic128_fetch_umin(ptr, i128, i32 signext)
+; LA_EXT:   declare i128 @__tsan_atomic128_fetch_umin(ptr, i128, i32 signext)
+
 ; CHECK:    declare void @__tsan_atomic_thread_fence(i32)
 ; EXT:      declare void @__tsan_atomic_thread_fence(i32 signext)
 ; MIPS_EXT: declare void @__tsan_atomic_thread_fence(i32 signext)

>From b4c5b3ec564528c814b16f283ea1865c47b0de98 Mon Sep 17 00:00:00 2001
From: Mauri de Souza Meneguzzo <mauri870 at gmail.com>
Date: Fri, 24 Jul 2026 16:55:21 -0300
Subject: [PATCH 2/2] add missing atomic128 tests

---
 .../Instrumentation/ThreadSanitizer/atomic.ll | 32 +++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/llvm/test/Instrumentation/ThreadSanitizer/atomic.ll b/llvm/test/Instrumentation/ThreadSanitizer/atomic.ll
index 73c74e4f1d406..d3b258e55c48f 100644
--- a/llvm/test/Instrumentation/ThreadSanitizer/atomic.ll
+++ b/llvm/test/Instrumentation/ThreadSanitizer/atomic.ll
@@ -2112,6 +2112,38 @@ entry:
 ; CHECK-LABEL: atomic64_umin_monotonic
 ; CHECK: call i64 @__tsan_atomic64_fetch_umin(ptr %a, i64 0, i32 0), !dbg
 
+define void @atomic128_max_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw max ptr %a, i128 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic128_max_monotonic
+; CHECK: call i128 @__tsan_atomic128_fetch_max(ptr %a, i128 0, i32 0), !dbg
+
+define void @atomic128_min_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw min ptr %a, i128 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic128_min_monotonic
+; CHECK: call i128 @__tsan_atomic128_fetch_min(ptr %a, i128 0, i32 0), !dbg
+
+define void @atomic128_umax_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw umax ptr %a, i128 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic128_umax_monotonic
+; CHECK: call i128 @__tsan_atomic128_fetch_umax(ptr %a, i128 0, i32 0), !dbg
+
+define void @atomic128_umin_monotonic(ptr %a) nounwind uwtable {
+entry:
+  atomicrmw umin ptr %a, i128 0 monotonic, !dbg !7
+  ret void, !dbg !7
+}
+; CHECK-LABEL: atomic128_umin_monotonic
+; CHECK: call i128 @__tsan_atomic128_fetch_umin(ptr %a, i128 0, i32 0), !dbg
+
 define void @atomic128_cas_monotonic(ptr %a) nounwind uwtable {
 entry:
   cmpxchg ptr %a, i128 0, i128 1 monotonic monotonic, !dbg !7



More information about the llvm-commits mailing list