Skip to content

[clang][NVPTX] Emit !atomic.ignore.denormal.mode for CUDA atomics - #217587

Merged
chsigg merged 2 commits into
mainfrom
users/chsigg/clang-emit-atomic-ignore-denormal
Oct 12, 2026
Merged

chsigg merged 2 commits into
mainfrom
users/chsigg/clang-emit-atomic-ignore-denormal

Conversation

@chsigg

@chsigg chsigg commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

CUDA's atomicAdd() family is defined in terms of PTX atom.add, whose
denormal behavior is fixed by the hardware. Without any annotation the
backend has to assume the function's denormal mode must be honored and
expands these into CAS loops whenever the two disagree. Mark them with
!atomic.ignore.denormal.mode so the native instruction is used.

That covers the _nvvm_atom*_add_gen_f builtins that atomicAdd(),
atomicAdd_block() and atomicAdd_system() are written in terms of, plus
C11/C++11 atomics under -fatomic-ignore-denormal-mode and the
[[clang::atomic(ignore_denormal_mode)]] attribute, which requires
teaching the NVPTX target about AtomicOptions.

The condition for when the metadata is meaningful is now shared with the
AMDGPU and SPIR-V targets in addAtomicIgnoreDenormalModeMetadata(). It
takes an AllowHalf flag because whether f16 denormals are observable is
target specific: PTX exposes no FTZ control for f16 operations, so
atom.add.f16 never flushes and the opt-in is meaningful there, whereas
the AMDGPU backend only consults the metadata for f32.

@github-actions

github-actions Bot commented Aug 20, 2026 •

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 92626 tests passed
  • 5547 tests skipped

✅ The build succeeded and all tests passed.

@github-actions

github-actions Bot commented Aug 20, 2026 •

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 1666 tests passed
  • 1403 tests skipped

✅ The build succeeded and all tests passed.

@chsigg
chsigg force-pushed the users/chsigg/nvptx-honor-atomic-ignore-denormal branch from 2364e44 to f654917 Compare August 20, 2026 16:38
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch 2 times, most recently from 6494a10 to cbc913a Compare August 20, 2026 16:47
@chsigg
chsigg force-pushed the users/chsigg/nvptx-honor-atomic-ignore-denormal branch from f654917 to 6a5536a Compare August 20, 2026 16:47
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch from cbc913a to bafeed9 Compare August 20, 2026 17:18
@chsigg
chsigg force-pushed the users/chsigg/nvptx-honor-atomic-ignore-denormal branch 2 times, most recently from 636a3c6 to 32accb8 Compare August 20, 2026 19:44
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch from bafeed9 to 5d2cb45 Compare August 20, 2026 19:44
@chsigg
chsigg force-pushed the users/chsigg/nvptx-honor-atomic-ignore-denormal branch from 32accb8 to 4fad0ff Compare August 21, 2026 07:02
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch from 5d2cb45 to bc3d073 Compare August 21, 2026 07:02
@chsigg
chsigg force-pushed the users/chsigg/nvptx-honor-atomic-ignore-denormal branch from 4fad0ff to 1774a54 Compare August 24, 2026 10:18
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch 2 times, most recently from 7c7705b to e4aaa27 Compare August 24, 2026 12:15
@chsigg
chsigg force-pushed the users/chsigg/nvptx-honor-atomic-ignore-denormal branch 2 times, most recently from 7d9fc10 to 5a0ac93 Compare August 24, 2026 12:27
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch from e4aaa27 to 1c1276b Compare August 24, 2026 12:27
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch from 1c1276b to 5a2f006 Compare September 15, 2026 15:17
@chsigg
chsigg force-pushed the users/chsigg/nvptx-honor-atomic-ignore-denormal branch from 5a0ac93 to 5ab17f9 Compare September 15, 2026 15:17
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch from 5a2f006 to 7100407 Compare September 15, 2026 19:21
@chsigg
chsigg force-pushed the users/chsigg/nvptx-honor-atomic-ignore-denormal branch 2 times, most recently from ba5bdf2 to 9f1578b Compare September 16, 2026 07:08
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch from 7100407 to 36bae08 Compare September 16, 2026 07:08
@chsigg
chsigg force-pushed the users/chsigg/nvptx-honor-atomic-ignore-denormal branch from 9f1578b to 507f6bf Compare October 7, 2026 13:10
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch from 36bae08 to 3e509d8 Compare October 7, 2026 13:10
Base automatically changed from users/chsigg/nvptx-honor-atomic-ignore-denormal to main October 7, 2026 14:05
CUDA's atomicAdd() family is defined in terms of PTX atom.add, whose
denormal behavior is fixed by the hardware. Without any annotation the
backend has to assume the function's denormal mode must be honored and
expands these into CAS loops whenever the two disagree. Mark them with
!atomic.ignore.denormal.mode so the native instruction is used.

That covers the __nvvm_atom_*_add_gen_f builtins that atomicAdd(),
atomicAdd_block() and atomicAdd_system() are written in terms of, plus
C11/C++11 atomics under -fatomic-ignore-denormal-mode and the
[[clang::atomic(ignore_denormal_mode)]] attribute, which requires
teaching the NVPTX target about AtomicOptions.

The condition for when the metadata is meaningful is now shared with the
AMDGPU and SPIR-V targets in addAtomicIgnoreDenormalModeMetadata(). It
takes an AllowHalf flag because whether f16 denormals are observable is
target specific: PTX exposes no FTZ control for f16 operations, so
atom.add.f16 never flushes and the opt-in is meaningful there, whereas
the AMDGPU backend only consults the metadata for f32.

Co-authored-by: Artem Belevich <tra@google.com>
@chsigg
chsigg requested review from AlexMaclean and Artem-B October 7, 2026 15:04
@chsigg
chsigg marked this pull request as ready for review October 7, 2026 15:05
@chsigg
chsigg force-pushed the users/chsigg/clang-emit-atomic-ignore-denormal branch from 3e509d8 to 081fbc3 Compare October 7, 2026 15:05
@llvmorg-github-actions llvmorg-github-actions Bot added clang Clang issues not falling into any other category backend:AMDGPU clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. backend:NVPTX labels Oct 7, 2026
@llvmorg-github-actions

llvmorg-github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

@llvm/pr-subscribers-clang
@llvm/pr-subscribers-backend-nvptx
@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-backend-amdgpu

Author: Christian Sigg (chsigg)

Changes

CUDA's atomicAdd() family is defined in terms of PTX atom.add, whose
denormal behavior is fixed by the hardware. Without any annotation the
backend has to assume the function's denormal mode must be honored and
expands these into CAS loops whenever the two disagree. Mark them with
!atomic.ignore.denormal.mode so the native instruction is used.

That covers the _nvvm_atom*_add_gen_f builtins that atomicAdd(),
atomicAdd_block() and atomicAdd_system() are written in terms of, plus
C11/C++11 atomics under -fatomic-ignore-denormal-mode and the
[[clang::atomic(ignore_denormal_mode)]] attribute, which requires
teaching the NVPTX target about AtomicOptions.

The condition for when the metadata is meaningful is now shared with the
AMDGPU and SPIR-V targets in addAtomicIgnoreDenormalModeMetadata(). It
takes an AllowHalf flag because whether f16 denormals are observable is
target specific: PTX exposes no FTZ control for f16 operations, so
atom.add.f16 never flushes and the opt-in is meaningful there, whereas
the AMDGPU backend only consults the metadata for f32.


Patch is 28.40 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/217587.diff

11 Files Affected:

  • (modified) clang/lib/Basic/Targets/NVPTX.cpp (+6)
  • (modified) clang/lib/Basic/Targets/NVPTX.h (+2)
  • (modified) clang/lib/CodeGen/TargetBuiltins/AMDGPU.cpp (+1-4)
  • (modified) clang/lib/CodeGen/TargetBuiltins/NVPTX.cpp (+19-4)
  • (modified) clang/lib/CodeGen/TargetInfo.cpp (+15)
  • (modified) clang/lib/CodeGen/TargetInfo.h (+18)
  • (modified) clang/lib/CodeGen/Targets/AMDGPU.cpp (+2-4)
  • (modified) clang/lib/CodeGen/Targets/NVPTX.cpp (+12)
  • (modified) clang/lib/CodeGen/Targets/SPIR.cpp (+2-4)
  • (modified) clang/test/CodeGen/builtins-nvptx.c (+4-1)
  • (added) clang/test/CodeGenCUDA/atomic-ignore-denormal-mode-nvptx.cu (+321)
diff --git a/clang/lib/Basic/Targets/NVPTX.cpp b/clang/lib/Basic/Targets/NVPTX.cpp
index de99a6718f26c..61efae2ccae87 100644
--- a/clang/lib/Basic/Targets/NVPTX.cpp
+++ b/clang/lib/Basic/Targets/NVPTX.cpp
@@ -206,6 +206,12 @@ void NVPTXTargetInfo::getTargetDefines(const LangOptions &Opts,
   }
 }
 
+void NVPTXTargetInfo::adjust(DiagnosticsEngine &Diags, LangOptions &Opts,
+                             const TargetInfo *Aux) {
+  TargetInfo::adjust(Diags, Opts, Aux);
+  AtomicOpts = AtomicOptions(Opts);
+}
+
 llvm::SmallVector<Builtin::InfosShard>
 NVPTXTargetInfo::getTargetBuiltins() const {
   return {{&BuiltinStrings, BuiltinInfos}};
diff --git a/clang/lib/Basic/Targets/NVPTX.h b/clang/lib/Basic/Targets/NVPTX.h
index b6d2bfd320103..e55fe89712385 100644
--- a/clang/lib/Basic/Targets/NVPTX.h
+++ b/clang/lib/Basic/Targets/NVPTX.h
@@ -147,6 +147,8 @@ class LLVM_LIBRARY_VISIBILITY NVPTXTargetInfo : public TargetInfo {
     GPU = StringToOffloadArch(Name);
     return !GPU.isUnknown();
   }
+  void adjust(DiagnosticsEngine &Diags, LangOptions &Opts,
+              const TargetInfo *Aux) override;
 
   void setSupportedOpenCLOpts() override {
     auto &Opts = getSupportedOpenCLOpts();
diff --git a/clang/lib/CodeGen/TargetBuiltins/AMDGPU.cpp b/clang/lib/CodeGen/TargetBuiltins/AMDGPU.cpp
index f3bf71dddc341..2f29e9557ba56 100644
--- a/clang/lib/CodeGen/TargetBuiltins/AMDGPU.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/AMDGPU.cpp
@@ -21,7 +21,6 @@
 #include "llvm/IR/IntrinsicsAMDGPU.h"
 #include "llvm/IR/IntrinsicsR600.h"
 #include "llvm/IR/IntrinsicsSPIRV.h"
-#include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/MemoryModelRelaxationAnnotations.h"
 #include "llvm/Support/AMDGPUAddrSpace.h"
 #include "llvm/Support/AtomicOrdering.h"
@@ -2047,9 +2046,7 @@ Value *CodeGenFunction::EmitAMDGPUBuiltinExpr(unsigned BuiltinID,
 
       // Most targets require "atomic.ignore.denormal.mode" to emit the native
       // instruction, but this only matters for float fadd.
-      if (BinOp == llvm::AtomicRMWInst::FAdd && Val->getType()->isFloatTy())
-        RMW->setMetadata(llvm::LLVMContext::MD_atomic_ignore_denormal_mode,
-                         EmptyMD);
+      addAtomicIgnoreDenormalModeMetadata(*this, *RMW);
     }
 
     return Builder.CreateBitCast(RMW, OrigTy);
diff --git a/clang/lib/CodeGen/TargetBuiltins/NVPTX.cpp b/clang/lib/CodeGen/TargetBuiltins/NVPTX.cpp
index e304eed8a1aab..2781d6b1dbd9b 100644
--- a/clang/lib/CodeGen/TargetBuiltins/NVPTX.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/NVPTX.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CGBuiltin.h"
+#include "TargetInfo.h"
 #include "clang/Basic/TargetBuiltins.h"
 #include "llvm/IR/IntrinsicsNVPTX.h"
 #include "llvm/TargetParser/AtomicScope.h"
@@ -362,8 +363,15 @@ static Value *MakeScopedAtomicRMW(CodeGenFunction &CGF, const CallExpr *E,
   Value *Val = CGF.EmitScalarExpr(E->getArg(1));
   llvm::SyncScope::ID SSID = CGF.getLLVMContext().getOrInsertSyncScopeID(
       *llvm::getAtomicScopeIRString(CGF.getTarget().getTriple(), Scope));
-  return CGF.Builder.CreateAtomicRMW(Kind, Ptr, Val,
-                                     llvm::AtomicOrdering::Monotonic, SSID);
+  llvm::AtomicRMWInst *RMW = CGF.Builder.CreateAtomicRMW(
+      Kind, Ptr, Val, llvm::AtomicOrdering::Monotonic, SSID);
+
+  // CUDA's atomicAdd_block()/atomicAdd_system() reach this through the scoped
+  // add builtins, and are defined in terms of the native atom.add just as the
+  // unscoped atomicAdd() is. The helper ignores everything that is not a
+  // floating-point add, so the other scoped operations are unaffected.
+  addAtomicIgnoreDenormalModeMetadata(CGF, *RMW, /*AllowHalf=*/true);
+  return RMW;
 }
 
 // `Scope` is AtomicScope::Workgroup for _cta builtins and AtomicScope::System
@@ -529,8 +537,15 @@ Value *CodeGenFunction::EmitNVPTXBuiltinExpr(unsigned BuiltinID,
     Address DestAddr = EmitPointerWithAlignment(E->getArg(0));
     Value *Val = EmitScalarExpr(E->getArg(1));
 
-    return Builder.CreateAtomicRMW(llvm::AtomicRMWInst::FAdd, DestAddr, Val,
-                                   AtomicOrdering::Monotonic);
+    llvm::AtomicRMWInst *RMW = Builder.CreateAtomicRMW(
+        llvm::AtomicRMWInst::FAdd, DestAddr, Val, AtomicOrdering::Monotonic);
+
+    // CUDA's atomicAdd() is defined in terms of the native atom.add
+    // instruction, whose denormal behavior is fixed by the hardware and need
+    // not agree with the function's denormal mode. Without this the atomic is
+    // expanded into a CAS loop whenever the two disagree.
+    addAtomicIgnoreDenormalModeMetadata(*this, *RMW, /*AllowHalf=*/true);
+    return RMW;
   }
 
   case NVPTX::BI__nvvm_atom_inc_gen_ui:
diff --git a/clang/lib/CodeGen/TargetInfo.cpp b/clang/lib/CodeGen/TargetInfo.cpp
index 2f89154c2d2d9..9786077f9062c 100644
--- a/clang/lib/CodeGen/TargetInfo.cpp
+++ b/clang/lib/CodeGen/TargetInfo.cpp
@@ -160,6 +160,21 @@ TargetCodeGenInfo::getLLVMSyncScopeID(const LangOptions &LangOpts,
       getLLVMSyncScopeStr(LangOpts, Scope, Ordering));
 }
 
+void CodeGen::addAtomicIgnoreDenormalModeMetadata(CodeGenFunction &CGF,
+                                                  llvm::Instruction &AtomicInst,
+                                                  bool AllowHalf) {
+  auto *RMW = dyn_cast<llvm::AtomicRMWInst>(&AtomicInst);
+  if (!RMW || RMW->getOperation() != llvm::AtomicRMWInst::FAdd)
+    return;
+
+  llvm::Type *Ty = RMW->getType();
+  if (!Ty->isFloatTy() && !(AllowHalf && Ty->isHalfTy()))
+    return;
+
+  RMW->setMetadata(llvm::LLVMContext::MD_atomic_ignore_denormal_mode,
+                   llvm::MDNode::get(CGF.getLLVMContext(), {}));
+}
+
 void TargetCodeGenInfo::addStackProbeTargetAttributes(
     const Decl *D, llvm::GlobalValue *GV, CodeGen::CodeGenModule &CGM) const {
   if (llvm::Function *Fn = dyn_cast_or_null<llvm::Function>(GV)) {
diff --git a/clang/lib/CodeGen/TargetInfo.h b/clang/lib/CodeGen/TargetInfo.h
index fae32068b1570..3b1dad75f88df 100644
--- a/clang/lib/CodeGen/TargetInfo.h
+++ b/clang/lib/CodeGen/TargetInfo.h
@@ -499,6 +499,24 @@ class TargetCodeGenInfo {
                                      CodeGen::CodeGenModule &CGM) const;
 };
 
+/// Attach `!atomic.ignore.denormal.mode` to \p AtomicInst, if it is an
+/// `atomicrmw` the metadata is meaningful for, i.e. an `fadd` on a type whose
+/// native atomic has a fixed denormal behavior that may disagree with the
+/// function's denormal mode.
+///
+/// `float` always qualifies. `half` only does when \p AllowHalf is set, since
+/// whether f16 denormals are observable is target specific: NVPTX exposes no
+/// FTZ control for f16 operations, so atom.add.f16 never flushes and the
+/// per-instruction opt-in is meaningful, whereas the AMDGPU backend only
+/// consults the metadata for f32.
+///
+/// This is shared by the targets whose backends honor the metadata; it does
+/// not itself check whether the user asked for it (see
+/// AtomicOptionKind::IgnoreDenormalMode).
+void addAtomicIgnoreDenormalModeMetadata(CodeGenFunction &CGF,
+                                         llvm::Instruction &AtomicInst,
+                                         bool AllowHalf = false);
+
 std::unique_ptr<TargetCodeGenInfo>
 createDefaultTargetCodeGenInfo(CodeGenModule &CGM);
 
diff --git a/clang/lib/CodeGen/Targets/AMDGPU.cpp b/clang/lib/CodeGen/Targets/AMDGPU.cpp
index 0b5ed1898f138..a74f0cf22d8e5 100644
--- a/clang/lib/CodeGen/Targets/AMDGPU.cpp
+++ b/clang/lib/CodeGen/Targets/AMDGPU.cpp
@@ -581,10 +581,8 @@ void AMDGPUTargetCodeGenInfo::setTargetAtomicMetadata(
     RMW->setMetadata("amdgpu.no.fine.grained.memory", Empty);
   if (!AO.getOption(clang::AtomicOptionKind::RemoteMemory))
     RMW->setMetadata("amdgpu.no.remote.memory", Empty);
-  if (AO.getOption(clang::AtomicOptionKind::IgnoreDenormalMode) &&
-      RMW->getOperation() == llvm::AtomicRMWInst::FAdd &&
-      RMW->getType()->isFloatTy())
-    RMW->setMetadata(llvm::LLVMContext::MD_atomic_ignore_denormal_mode, Empty);
+  if (AO.getOption(clang::AtomicOptionKind::IgnoreDenormalMode))
+    addAtomicIgnoreDenormalModeMetadata(CGF, *RMW);
 }
 
 bool AMDGPUTargetCodeGenInfo::shouldEmitStaticExternCAliases() const {
diff --git a/clang/lib/CodeGen/Targets/NVPTX.cpp b/clang/lib/CodeGen/Targets/NVPTX.cpp
index 00fe271c85b0b..0229389fe1716 100644
--- a/clang/lib/CodeGen/Targets/NVPTX.cpp
+++ b/clang/lib/CodeGen/Targets/NVPTX.cpp
@@ -13,6 +13,7 @@
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/IR/CallingConv.h"
 #include "llvm/IR/IntrinsicsNVPTX.h"
+#include "llvm/IR/LLVMContext.h"
 #include "llvm/Support/NVVMAttributes.h"
 
 using namespace clang;
@@ -50,6 +51,9 @@ class NVPTXTargetCodeGenInfo : public TargetCodeGenInfo {
 
   void setTargetAttributes(const Decl *D, llvm::GlobalValue *GV,
                            CodeGen::CodeGenModule &M) const override;
+  void setTargetAtomicMetadata(CodeGenFunction &CGF,
+                               llvm::Instruction &AtomicInst,
+                               const AtomicExpr *AE) const override;
   bool shouldEmitStaticExternCAliases() const override;
 
   StringRef getLLVMSyncScopeStr(const LangOptions &LangOpts, SyncScope Scope,
@@ -304,6 +308,14 @@ bool NVPTXTargetCodeGenInfo::shouldEmitStaticExternCAliases() const {
   return false;
 }
 
+void NVPTXTargetCodeGenInfo::setTargetAtomicMetadata(
+    CodeGenFunction &CGF, llvm::Instruction &AtomicInst,
+    const AtomicExpr *AE) const {
+  AtomicOptions AO = CGF.CGM.getAtomicOpts();
+  if (AO.getOption(clang::AtomicOptionKind::IgnoreDenormalMode))
+    addAtomicIgnoreDenormalModeMetadata(CGF, AtomicInst, /*AllowHalf=*/true);
+}
+
 StringRef NVPTXTargetCodeGenInfo::getLLVMSyncScopeStr(
     const LangOptions &LangOpts, SyncScope Scope,
     llvm::AtomicOrdering Ordering) const {
diff --git a/clang/lib/CodeGen/Targets/SPIR.cpp b/clang/lib/CodeGen/Targets/SPIR.cpp
index 244dd10d1f7ca..de7880c641db3 100644
--- a/clang/lib/CodeGen/Targets/SPIR.cpp
+++ b/clang/lib/CodeGen/Targets/SPIR.cpp
@@ -588,10 +588,8 @@ void SPIRVTargetCodeGenInfo::setTargetAtomicMetadata(
     RMW->setMetadata("amdgpu.no.fine.grained.memory", Empty);
   if (!AO.getOption(clang::AtomicOptionKind::RemoteMemory))
     RMW->setMetadata("amdgpu.no.remote.memory", Empty);
-  if (AO.getOption(clang::AtomicOptionKind::IgnoreDenormalMode) &&
-      RMW->getOperation() == llvm::AtomicRMWInst::FAdd &&
-      RMW->getType()->isFloatTy())
-    RMW->setMetadata(llvm::LLVMContext::MD_atomic_ignore_denormal_mode, Empty);
+  if (AO.getOption(clang::AtomicOptionKind::IgnoreDenormalMode))
+    addAtomicIgnoreDenormalModeMetadata(CGF, *RMW);
 }
 
 /// Construct a SPIR-V target extension type for the given OpenCL image type.
diff --git a/clang/test/CodeGen/builtins-nvptx.c b/clang/test/CodeGen/builtins-nvptx.c
index acd7d40277bfa..25dc7e15bf455 100644
--- a/clang/test/CodeGen/builtins-nvptx.c
+++ b/clang/test/CodeGen/builtins-nvptx.c
@@ -417,7 +417,10 @@ __device__ void nvvm_atom(float *fp, float f, double *dfp, double df,
   // CHECK-NEXT: extractvalue { i64, i1 } {{%[0-9]+}}, 0
   __nvvm_atom_cas_gen_ll(&sll, 0, ll);
 
-  // CHECK: atomicrmw fadd ptr {{.*}} monotonic, align 4
+  // CUDA's atomicAdd() lowers to this builtin. The native atom.add has a fixed
+  // denormal behavior, so the atomic is marked to keep it from being expanded
+  // into a CAS loop when the function's denormal mode disagrees.
+  // CHECK: atomicrmw fadd ptr {{.*}} monotonic, align 4, !atomic.ignore.denormal.mode
   __nvvm_atom_add_gen_f(fp, f);
 
   // CHECK: atomicrmw uinc_wrap ptr {{.*}} monotonic, align 4
diff --git a/clang/test/CodeGenCUDA/atomic-ignore-denormal-mode-nvptx.cu b/clang/test/CodeGenCUDA/atomic-ignore-denormal-mode-nvptx.cu
new file mode 100644
index 0000000000000..e3ae34322a83d
--- /dev/null
+++ b/clang/test/CodeGenCUDA/atomic-ignore-denormal-mode-nvptx.cu
@@ -0,0 +1,321 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6
+// REQUIRES: nvptx-registered-target
+// RUN: %clang_cc1 -triple nvptx64-nvidia-cuda -target-cpu sm_90 \
+// RUN:   -target-feature +ptx87 -fcuda-is-device \
+// RUN:   -emit-llvm -o - %s | FileCheck --check-prefix=DEV %s
+// RUN: %clang_cc1 -triple nvptx64-nvidia-cuda -target-cpu sm_90 \
+// RUN:   -target-feature +ptx87 -fcuda-is-device \
+// RUN:   -fatomic-ignore-denormal-mode \
+// RUN:   -emit-llvm -o - %s | FileCheck --check-prefix=OPT %s
+
+#include "Inputs/cuda.h"
+
+// -fatomic-ignore-denormal-mode reaches NVPTX, and only marks float fadd --
+// the one case where the native instruction's fixed denormal behavior is
+// observable.
+
+// DEV-LABEL: define dso_local void @_Z10test_floatPf(
+// DEV-SAME: ptr noundef [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+// DEV-NEXT:  [[ENTRY:.*:]]
+// DEV-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// DEV-NEXT:    [[DOTATOMICTMP:%.*]] = alloca float, align 4
+// DEV-NEXT:    [[ATOMIC_TEMP:%.*]] = alloca float, align 4
+// DEV-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// DEV-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// DEV-NEXT:    store float 1.000000e+00, ptr [[DOTATOMICTMP]], align 4
+// DEV-NEXT:    [[TMP1:%.*]] = load float, ptr [[DOTATOMICTMP]], align 4
+// DEV-NEXT:    [[TMP2:%.*]] = atomicrmw fadd ptr [[TMP0]], float [[TMP1]] monotonic, align 4
+// DEV-NEXT:    store float [[TMP2]], ptr [[ATOMIC_TEMP]], align 4
+// DEV-NEXT:    [[TMP3:%.*]] = load float, ptr [[ATOMIC_TEMP]], align 4
+// DEV-NEXT:    ret void
+//
+// OPT-LABEL: define dso_local void @_Z10test_floatPf(
+// OPT-SAME: ptr noundef [[A:%.*]]) #[[ATTR0:[0-9]+]] {
+// OPT-NEXT:  [[ENTRY:.*:]]
+// OPT-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// OPT-NEXT:    [[DOTATOMICTMP:%.*]] = alloca float, align 4
+// OPT-NEXT:    [[ATOMIC_TEMP:%.*]] = alloca float, align 4
+// OPT-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// OPT-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// OPT-NEXT:    store float 1.000000e+00, ptr [[DOTATOMICTMP]], align 4
+// OPT-NEXT:    [[TMP1:%.*]] = load float, ptr [[DOTATOMICTMP]], align 4
+// OPT-NEXT:    [[TMP2:%.*]] = atomicrmw fadd ptr [[TMP0]], float [[TMP1]] monotonic, align 4, !atomic.ignore.denormal.mode [[META2:![0-9]+]]
+// OPT-NEXT:    store float [[TMP2]], ptr [[ATOMIC_TEMP]], align 4
+// OPT-NEXT:    [[TMP3:%.*]] = load float, ptr [[ATOMIC_TEMP]], align 4
+// OPT-NEXT:    ret void
+//
+__device__ void test_float(float *a) {
+  __scoped_atomic_fetch_add(a, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+}
+
+// DEV-LABEL: define dso_local void @_Z11test_doublePd(
+// DEV-SAME: ptr noundef [[A:%.*]]) #[[ATTR0]] {
+// DEV-NEXT:  [[ENTRY:.*:]]
+// DEV-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// DEV-NEXT:    [[DOTATOMICTMP:%.*]] = alloca double, align 8
+// DEV-NEXT:    [[ATOMIC_TEMP:%.*]] = alloca double, align 8
+// DEV-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// DEV-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// DEV-NEXT:    store double 1.000000e+00, ptr [[DOTATOMICTMP]], align 8
+// DEV-NEXT:    [[TMP1:%.*]] = load double, ptr [[DOTATOMICTMP]], align 8
+// DEV-NEXT:    [[TMP2:%.*]] = atomicrmw fadd ptr [[TMP0]], double [[TMP1]] monotonic, align 8
+// DEV-NEXT:    store double [[TMP2]], ptr [[ATOMIC_TEMP]], align 8
+// DEV-NEXT:    [[TMP3:%.*]] = load double, ptr [[ATOMIC_TEMP]], align 8
+// DEV-NEXT:    ret void
+//
+// OPT-LABEL: define dso_local void @_Z11test_doublePd(
+// OPT-SAME: ptr noundef [[A:%.*]]) #[[ATTR0]] {
+// OPT-NEXT:  [[ENTRY:.*:]]
+// OPT-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// OPT-NEXT:    [[DOTATOMICTMP:%.*]] = alloca double, align 8
+// OPT-NEXT:    [[ATOMIC_TEMP:%.*]] = alloca double, align 8
+// OPT-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// OPT-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// OPT-NEXT:    store double 1.000000e+00, ptr [[DOTATOMICTMP]], align 8
+// OPT-NEXT:    [[TMP1:%.*]] = load double, ptr [[DOTATOMICTMP]], align 8
+// OPT-NEXT:    [[TMP2:%.*]] = atomicrmw fadd ptr [[TMP0]], double [[TMP1]] monotonic, align 8
+// OPT-NEXT:    store double [[TMP2]], ptr [[ATOMIC_TEMP]], align 8
+// OPT-NEXT:    [[TMP3:%.*]] = load double, ptr [[ATOMIC_TEMP]], align 8
+// OPT-NEXT:    ret void
+//
+__device__ void test_double(double *a) {
+  __scoped_atomic_fetch_add(a, 1.0, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+}
+
+// The scoped attribute overrides the command line in both directions.
+
+// DEV-LABEL: define dso_local void @_Z12test_attr_onPf(
+// DEV-SAME: ptr noundef [[A:%.*]]) #[[ATTR0]] {
+// DEV-NEXT:  [[ENTRY:.*:]]
+// DEV-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// DEV-NEXT:    [[DOTATOMICTMP:%.*]] = alloca float, align 4
+// DEV-NEXT:    [[ATOMIC_TEMP:%.*]] = alloca float, align 4
+// DEV-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// DEV-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// DEV-NEXT:    store float 1.000000e+00, ptr [[DOTATOMICTMP]], align 4
+// DEV-NEXT:    [[TMP1:%.*]] = load float, ptr [[DOTATOMICTMP]], align 4
+// DEV-NEXT:    [[TMP2:%.*]] = atomicrmw fadd ptr [[TMP0]], float [[TMP1]] monotonic, align 4, !atomic.ignore.denormal.mode [[META2:![0-9]+]]
+// DEV-NEXT:    store float [[TMP2]], ptr [[ATOMIC_TEMP]], align 4
+// DEV-NEXT:    [[TMP3:%.*]] = load float, ptr [[ATOMIC_TEMP]], align 4
+// DEV-NEXT:    ret void
+//
+// OPT-LABEL: define dso_local void @_Z12test_attr_onPf(
+// OPT-SAME: ptr noundef [[A:%.*]]) #[[ATTR0]] {
+// OPT-NEXT:  [[ENTRY:.*:]]
+// OPT-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// OPT-NEXT:    [[DOTATOMICTMP:%.*]] = alloca float, align 4
+// OPT-NEXT:    [[ATOMIC_TEMP:%.*]] = alloca float, align 4
+// OPT-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// OPT-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// OPT-NEXT:    store float 1.000000e+00, ptr [[DOTATOMICTMP]], align 4
+// OPT-NEXT:    [[TMP1:%.*]] = load float, ptr [[DOTATOMICTMP]], align 4
+// OPT-NEXT:    [[TMP2:%.*]] = atomicrmw fadd ptr [[TMP0]], float [[TMP1]] monotonic, align 4, !atomic.ignore.denormal.mode [[META2]]
+// OPT-NEXT:    store float [[TMP2]], ptr [[ATOMIC_TEMP]], align 4
+// OPT-NEXT:    [[TMP3:%.*]] = load float, ptr [[ATOMIC_TEMP]], align 4
+// OPT-NEXT:    ret void
+//
+__device__ void test_attr_on(float *a) {
+  [[clang::atomic(ignore_denormal_mode)]] {
+    __scoped_atomic_fetch_add(a, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+  }
+}
+
+// DEV-LABEL: define dso_local void @_Z13test_attr_offPf(
+// DEV-SAME: ptr noundef [[A:%.*]]) #[[ATTR0]] {
+// DEV-NEXT:  [[ENTRY:.*:]]
+// DEV-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// DEV-NEXT:    [[DOTATOMICTMP:%.*]] = alloca float, align 4
+// DEV-NEXT:    [[ATOMIC_TEMP:%.*]] = alloca float, align 4
+// DEV-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// DEV-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// DEV-NEXT:    store float 1.000000e+00, ptr [[DOTATOMICTMP]], align 4
+// DEV-NEXT:    [[TMP1:%.*]] = load float, ptr [[DOTATOMICTMP]], align 4
+// DEV-NEXT:    [[TMP2:%.*]] = atomicrmw fadd ptr [[TMP0]], float [[TMP1]] monotonic, align 4
+// DEV-NEXT:    store float [[TMP2]], ptr [[ATOMIC_TEMP]], align 4
+// DEV-NEXT:    [[TMP3:%.*]] = load float, ptr [[ATOMIC_TEMP]], align 4
+// DEV-NEXT:    ret void
+//
+// OPT-LABEL: define dso_local void @_Z13test_attr_offPf(
+// OPT-SAME: ptr noundef [[A:%.*]]) #[[ATTR0]] {
+// OPT-NEXT:  [[ENTRY:.*:]]
+// OPT-NEXT:    [[A_ADDR:%.*]] = alloca ptr, align 8
+// OPT-NEXT:    [[DOTATOMICTMP:%.*]] = alloca float, align 4
+// OPT-NEXT:    [[ATOMIC_TEMP:%.*]] = alloca float, align 4
+// OPT-NEXT:    store ptr [[A]], ptr [[A_ADDR]], align 8
+// OPT-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// OPT-NEXT:    store float 1.000000e+00, ptr [[DOTATOMICTMP]], align 4
+// OPT-NEXT:    [[TMP1:%.*]] = load float, ptr [[DOTATOMICTMP]], align 4
+// OPT-NEXT:    [[TMP2:%.*]] = atomicrmw fadd ptr [[TMP0]], float [[TMP1]] monotonic, align 4
+// OPT-NEXT:    store float [[TMP2]], ptr [[ATOMIC_TEMP]], align 4
+// OPT-NEXT:    [[TMP3:%.*]] = load float, ptr [[ATOMIC_TEMP]], align 4
+// OPT-NEXT:    ret void
+//
+__device__ void test_attr_off(float *a) {
+  [[clang::atomic(no_ignore_denormal_mode)]] {
+    __scoped_atomic_fetch_add(a, 1.0f, __ATOMIC_RELAXED, __MEMORY_SCOPE_SYSTEM);
+  }
+}
+
+// CUDA's atomicAdd() lowers to __nvvm_atom_add_gen_f rather than going through
+// the C11 atomic path, so it is marked unconditionally: the native atom.add is
+// wha...
[truncated]

@AlexMaclean AlexMaclean left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@chsigg
chsigg merged commit e725ea1 into main Oct 12, 2026
10 checks passed
@chsigg
chsigg deleted the users/chsigg/clang-emit-atomic-ignore-denormal branch October 12, 2026 05:14
@llvm-ci

llvm-ci commented Oct 12, 2026

Copy link
Copy Markdown

LLVM Buildbot has detected a new failure on builder sanitizer-ppc64le-linux running on ppc64le-sanitizer while building clang at step 2 "annotate".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/72/builds/778

Here is the relevant piece of the build log for the reference
Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure)
...
UNSUPPORTED: SanitizerCommon-msan-powerpc64le-Linux :: Posix/fpe.cpp (1640 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
XFAIL: MemorySanitizer-POWERPC64LE :: vararg_shadow.cpp (1641 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
UNSUPPORTED: MemorySanitizer-POWERPC64LE :: vector_cvt.cpp (1642 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
UNSUPPORTED: SanitizerCommon-msan-powerpc64le-Linux :: Posix/fseek.cpp (1643 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
UNSUPPORTED: SanitizerCommon-msan-powerpc64le-Linux :: Posix/fts.cpp (1644 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
FAIL: MemorySanitizer-POWERPC64LE :: release_origin.c (1645 of 2872)
******************** TEST 'MemorySanitizer-POWERPC64LE :: release_origin.c' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/./bin/clang  -fsanitize=memory -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls  -m64 -fno-function-sections  -gline-tables-only -fsanitize-memory-track-origins=0 -O0 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp && env MSAN_OPTIONS=soft_rss_limit_mb=60:verbosity=1:allocator_may_return_null=1  /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp 2>&1 | FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -implicit-check-not="soft rss limit" -check-prefixes=CHECK,NOORIG
# executed command: /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/./bin/clang -fsanitize=memory -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -m64 -fno-function-sections -gline-tables-only -fsanitize-memory-track-origins=0 -O0 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp
# note: command had no output on stdout or stderr
# executed command: env MSAN_OPTIONS=soft_rss_limit_mb=60:verbosity=1:allocator_may_return_null=1 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp
# note: command had no output on stdout or stderr
# executed command: FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c '-implicit-check-not=soft rss limit' -check-prefixes=CHECK,NOORIG
# note: command had no output on stdout or stderr
# RUN: at line 2
/home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/./bin/clang  -fsanitize=memory -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls  -m64 -fno-function-sections  -gline-tables-only -fsanitize-memory-track-origins=2 -O0 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp && env MSAN_OPTIONS=soft_rss_limit_mb=150:verbosity=1:allocator_may_return_null=1  /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp 2>&1 | FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -implicit-check-not="soft rss limit" -check-prefixes=CHECK,ORIGIN
# executed command: /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/./bin/clang -fsanitize=memory -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -m64 -fno-function-sections -gline-tables-only -fsanitize-memory-track-origins=2 -O0 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp
# note: command had no output on stdout or stderr
# executed command: env MSAN_OPTIONS=soft_rss_limit_mb=150:verbosity=1:allocator_may_return_null=1 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp
# note: command had no output on stdout or stderr
# executed command: FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c '-implicit-check-not=soft rss limit' -check-prefixes=CHECK,ORIGIN
# .---command stderr------------
# | /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c:31:11: error: CHECK: expected string not found in input
# | // CHECK: soft rss limit exhausted
# |           ^
# | <stdin>:108:7: note: scanning from here
# | malloc
# |       ^
# | <stdin>:111:34: note: possible intended match here
# | MemorySanitizer: StackDepot: 3 ids; 9M allocated
# |                                  ^
# | command line:1:22: error: IMPLICIT-CHECK-NOT: excluded string found in input
# | -implicit-check-not='soft rss limit'
# |                      ^
# | <stdin>:114:29: note: found here
# | ==3517961==MemorySanitizer: soft rss limit exhausted (150Mb vs 240Mb)
# |                             ^~~~~~~~~~~~~~
# | 
# | Input file: <stdin>
Step 9 (test compiler-rt debug) failure: test compiler-rt debug (failure)
...
UNSUPPORTED: SanitizerCommon-msan-powerpc64le-Linux :: Posix/fpe.cpp (1640 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
XFAIL: MemorySanitizer-POWERPC64LE :: vararg_shadow.cpp (1641 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
UNSUPPORTED: MemorySanitizer-POWERPC64LE :: vector_cvt.cpp (1642 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
UNSUPPORTED: SanitizerCommon-msan-powerpc64le-Linux :: Posix/fseek.cpp (1643 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
UNSUPPORTED: SanitizerCommon-msan-powerpc64le-Linux :: Posix/fts.cpp (1644 of 2872)
Testing:  0.. 10.. 20.. 30.. 40.. 50.
FAIL: MemorySanitizer-POWERPC64LE :: release_origin.c (1645 of 2872)
******************** TEST 'MemorySanitizer-POWERPC64LE :: release_origin.c' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/./bin/clang  -fsanitize=memory -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls  -m64 -fno-function-sections  -gline-tables-only -fsanitize-memory-track-origins=0 -O0 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp && env MSAN_OPTIONS=soft_rss_limit_mb=60:verbosity=1:allocator_may_return_null=1  /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp 2>&1 | FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -implicit-check-not="soft rss limit" -check-prefixes=CHECK,NOORIG
# executed command: /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/./bin/clang -fsanitize=memory -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -m64 -fno-function-sections -gline-tables-only -fsanitize-memory-track-origins=0 -O0 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp
# note: command had no output on stdout or stderr
# executed command: env MSAN_OPTIONS=soft_rss_limit_mb=60:verbosity=1:allocator_may_return_null=1 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp
# note: command had no output on stdout or stderr
# executed command: FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c '-implicit-check-not=soft rss limit' -check-prefixes=CHECK,NOORIG
# note: command had no output on stdout or stderr
# RUN: at line 2
/home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/./bin/clang  -fsanitize=memory -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls  -m64 -fno-function-sections  -gline-tables-only -fsanitize-memory-track-origins=2 -O0 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp && env MSAN_OPTIONS=soft_rss_limit_mb=150:verbosity=1:allocator_may_return_null=1  /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp 2>&1 | FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -implicit-check-not="soft rss limit" -check-prefixes=CHECK,ORIGIN
# executed command: /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/./bin/clang -fsanitize=memory -mno-omit-leaf-frame-pointer -fno-omit-frame-pointer -fno-optimize-sibling-calls -m64 -fno-function-sections -gline-tables-only -fsanitize-memory-track-origins=2 -O0 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c -o /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp
# note: command had no output on stdout or stderr
# executed command: env MSAN_OPTIONS=soft_rss_limit_mb=150:verbosity=1:allocator_may_return_null=1 /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/build_default/runtimes/runtimes-bins/compiler-rt/test/msan/POWERPC64LE/Output/release_origin.c.tmp
# note: command had no output on stdout or stderr
# executed command: FileCheck /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c '-implicit-check-not=soft rss limit' -check-prefixes=CHECK,ORIGIN
# .---command stderr------------
# | /home/buildbots/llvm-external-buildbots/workers/ppc64le-sanitizer/sanitizer-ppc64le/build/llvm-project/compiler-rt/test/msan/release_origin.c:31:11: error: CHECK: expected string not found in input
# | // CHECK: soft rss limit exhausted
# |           ^
# | <stdin>:108:7: note: scanning from here
# | malloc
# |       ^
# | <stdin>:111:34: note: possible intended match here
# | MemorySanitizer: StackDepot: 3 ids; 9M allocated
# |                                  ^
# | command line:1:22: error: IMPLICIT-CHECK-NOT: excluded string found in input
# | -implicit-check-not='soft rss limit'
# |                      ^
# | <stdin>:114:29: note: found here
# | ==3517961==MemorySanitizer: soft rss limit exhausted (150Mb vs 240Mb)
# |                             ^~~~~~~~~~~~~~
# | 
# | Input file: <stdin>

ram-NK pushed a commit to ram-NK/llvm-project that referenced this pull request Oct 14, 2026
…vm#217587)

CUDA's atomicAdd() family is defined in terms of PTX atom.add, whose
denormal behavior is fixed by the hardware. Without any annotation the
backend has to assume the function's denormal mode must be honored and
expands these into CAS loops whenever the two disagree. Mark them with
!atomic.ignore.denormal.mode so the native instruction is used.

That covers the __nvvm_atom_*_add_gen_f builtins that atomicAdd(),
atomicAdd_block() and atomicAdd_system() are written in terms of, plus
C11/C++11 atomics under -fatomic-ignore-denormal-mode and the
[[clang::atomic(ignore_denormal_mode)]] attribute, which requires
teaching the NVPTX target about AtomicOptions.

The condition for when the metadata is meaningful is now shared with the
AMDGPU and SPIR-V targets in addAtomicIgnoreDenormalModeMetadata(). It
takes an AllowHalf flag because whether f16 denormals are observable is
target specific: PTX exposes no FTZ control for f16 operations, so
atom.add.f16 never flushes and the opt-in is meaningful there, whereas
the AMDGPU backend only consults the metadata for f32.

---------

Co-authored-by: Artem Belevich <tra@google.com>
MazenEwiss pushed a commit to MazenEwiss/llvm-project that referenced this pull request Oct 14, 2026
…vm#217587)

CUDA's atomicAdd() family is defined in terms of PTX atom.add, whose
denormal behavior is fixed by the hardware. Without any annotation the
backend has to assume the function's denormal mode must be honored and
expands these into CAS loops whenever the two disagree. Mark them with
!atomic.ignore.denormal.mode so the native instruction is used.

That covers the __nvvm_atom_*_add_gen_f builtins that atomicAdd(),
atomicAdd_block() and atomicAdd_system() are written in terms of, plus
C11/C++11 atomics under -fatomic-ignore-denormal-mode and the
[[clang::atomic(ignore_denormal_mode)]] attribute, which requires
teaching the NVPTX target about AtomicOptions.

The condition for when the metadata is meaningful is now shared with the
AMDGPU and SPIR-V targets in addAtomicIgnoreDenormalModeMetadata(). It
takes an AllowHalf flag because whether f16 denormals are observable is
target specific: PTX exposes no FTZ control for f16 operations, so
atom.add.f16 never flushes and the opt-in is meaningful there, whereas
the AMDGPU backend only consults the metadata for f32.

---------

Co-authored-by: Artem Belevich <tra@google.com>
StarOne01 pushed a commit to StarOne01/llvm-project that referenced this pull request Oct 15, 2026
…vm#217587)

CUDA's atomicAdd() family is defined in terms of PTX atom.add, whose
denormal behavior is fixed by the hardware. Without any annotation the
backend has to assume the function's denormal mode must be honored and
expands these into CAS loops whenever the two disagree. Mark them with
!atomic.ignore.denormal.mode so the native instruction is used.

That covers the __nvvm_atom_*_add_gen_f builtins that atomicAdd(),
atomicAdd_block() and atomicAdd_system() are written in terms of, plus
C11/C++11 atomics under -fatomic-ignore-denormal-mode and the
[[clang::atomic(ignore_denormal_mode)]] attribute, which requires
teaching the NVPTX target about AtomicOptions.

The condition for when the metadata is meaningful is now shared with the
AMDGPU and SPIR-V targets in addAtomicIgnoreDenormalModeMetadata(). It
takes an AllowHalf flag because whether f16 denormals are observable is
target specific: PTX exposes no FTZ control for f16 operations, so
atom.add.f16 never flushes and the opt-in is meaningful there, whereas
the AMDGPU backend only consults the metadata for f32.

---------

Co-authored-by: Artem Belevich <tra@google.com>
chsigg added a commit that referenced this pull request Oct 17, 2026
…M atomic add intrinsics (#232725)

Legacy NVVM floating-point atomic add intrinsics
(`@llvm.nvvm.atomic.load.add.f32.p*`,
`@llvm.nvvm.atomic.load.add.f64.p*`, and
`@llvm.nvvm.atomic.add.gen.f.*`) specifically targeted the PTX
`atom.add` instruction, which flushes f32 denormals to zero on global
memory.

Attach `!atomic.ignore.denormal.mode` metadata when auto-upgrading these
intrinsics to `atomicrmw fadd` so that they continue to lower to native
`atom.add` instructions when `-nvptx-allow-ftz-atomics` defaults to
`false` (#231429), matching the Clang lowering of
`__nvvm_atom_add_gen_f` (#217587).
chsigg added a commit that referenced this pull request Oct 18, 2026
…232726)

CUDA Fortran `atomicadd` has the same device semantics as CUDA C++
`atomicAdd()`, targeting PTX `atom.add` (which flushes f32 denormals to
zero on global memory).

Set `ignore_denormal_mode` on `mlir::LLVM::AtomicRMWOp` when lowering
CUDA Fortran `atomicadd` for floating-point types in `genAtomBinOp`,
matching Clang's CUDA/HIP `atomicAdd` lowering (#217587) so it continues
to emit native `atom.add.f32` when `-nvptx-allow-ftz-atomics` defaults
to `false` (#231429).
chsigg added a commit that referenced this pull request Oct 18, 2026
This restores the strict FTZ default originally introduced in #200732
and temporarily relaxed in #206154.

Frontends can now explicitly opt in to native FTZ-mismatched atomics on a
per-instruction basis using `!atomic.ignore.denormal.mode` metadata (#217585,
#217586, #217587, #217588, #232725, #232726), which is supported in Clang,
Flang, MLIR, and XLA. Users who need the previous behavior globally can still
pass `-nvptx-allow-ftz-atomics=true`.

Update test cases to verify the new default disallow behavior.
alanzhao1 pushed a commit to alanzhao1/llvm-project that referenced this pull request Oct 25, 2026
…31429)

This restores the strict FTZ default originally introduced in llvm#200732 and temporarily relaxed in llvm#206154.

Frontends can now explicitly opt in to native FTZ-mismatched atomics on a per-instruction basis using `!atomic.ignore.denormal.mode` metadata (llvm#217585, llvm#217586, llvm#217587, llvm#217588, llvm#232725, llvm#232726), which is supported in Clang, Flang, MLIR, and XLA. Users who need the previous behavior globally can still pass `-nvptx-allow-ftz-atomics=true`.

Update test cases to verify the new default disallow behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:AMDGPU backend:NVPTX clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants