[clang] [Clang] Correctly handle UBSan libraries for the GPU (PR #188290)

Joseph Huber via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 25 06:48:55 PDT 2026


https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/188290

>From 5a5209507fff2d9c3aa4686ad782b0f764600c55 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Tue, 24 Mar 2026 11:14:15 -0500
Subject: [PATCH 1/2] [Clang] Correctly handle UBSan libraries for the GPU

Summary:
This PR adds the necessary clang driver plumbing to forward UBSan
arguments on the GPU targets. These are currently only forwarded via the
offloading languages if the user has the relevant library installed.

Enables the support in https://github.com/llvm/llvm-project/pull/188289
---
 clang/lib/Driver/ToolChains/AMDGPU.cpp        |  1 +
 clang/lib/Driver/ToolChains/AMDGPU.h          | 18 ++++++++++------
 clang/lib/Driver/ToolChains/Clang.cpp         | 20 +++++++++++++++++-
 clang/lib/Driver/ToolChains/CommonArgs.cpp    |  2 +-
 clang/lib/Driver/ToolChains/Cuda.cpp          |  1 +
 .../libclang_rt.ubsan_minimal.a               |  0
 .../libclang_rt.ubsan_minimal.a               |  0
 clang/test/Driver/amdgpu-toolchain.c          |  7 +++++++
 clang/test/Driver/cuda-cross-compiling.c      |  7 +++++++
 clang/test/Driver/openmp-offload-gpu.c        | 21 +++++++++++++++++++
 10 files changed, 69 insertions(+), 8 deletions(-)
 create mode 100644 clang/test/Driver/Inputs/resource_dir_with_per_target_subdir/lib/amdgcn-amd-amdhsa/libclang_rt.ubsan_minimal.a
 create mode 100644 clang/test/Driver/Inputs/resource_dir_with_per_target_subdir/lib/nvptx64-nvidia-cuda/libclang_rt.ubsan_minimal.a

diff --git a/clang/lib/Driver/ToolChains/AMDGPU.cpp b/clang/lib/Driver/ToolChains/AMDGPU.cpp
index 03bd88f0d4f47..af44448240455 100644
--- a/clang/lib/Driver/ToolChains/AMDGPU.cpp
+++ b/clang/lib/Driver/ToolChains/AMDGPU.cpp
@@ -633,6 +633,7 @@ void amdgpu::Linker::ConstructJob(Compilation &C, const JobAction &JA,
   }
 
   getToolChain().addProfileRTLibs(Args, CmdArgs);
+  addSanitizerRuntimes(getToolChain(), Args, CmdArgs);
 
   if (Args.hasArg(options::OPT_stdlib))
     CmdArgs.append({"-lc", "-lm"});
diff --git a/clang/lib/Driver/ToolChains/AMDGPU.h b/clang/lib/Driver/ToolChains/AMDGPU.h
index 4dd8188842f83..93eb441385949 100644
--- a/clang/lib/Driver/ToolChains/AMDGPU.h
+++ b/clang/lib/Driver/ToolChains/AMDGPU.h
@@ -152,7 +152,8 @@ class LLVM_LIBRARY_VISIBILITY ROCMToolChain : public AMDGPUToolChain {
                           Action::OffloadKind DeviceOffloadingKind) const;
 
   SanitizerMask getSupportedSanitizers() const override {
-    return SanitizerKind::Address;
+    return SanitizerKind::Address | SanitizerKind::Undefined |
+           SanitizerKind::UndefinedGroup;
   }
 
   bool diagnoseUnsupportedOption(const llvm::opt::Arg *A,
@@ -202,12 +203,16 @@ class LLVM_LIBRARY_VISIBILITY ROCMToolChain : public AMDGPUToolChain {
     SmallVector<const char *, 4> SupportedSanitizers;
     SmallVector<const char *, 4> UnSupportedSanitizers;
 
+    SanitizerMask Supported = ROCMToolChain::getSupportedSanitizers();
+    SanitizerMask SupportedMask;
     for (const char *Value : A->getValues()) {
-      SanitizerMask K = parseSanitizerValue(Value, /*Allow Groups*/ false);
-      if (K & ROCMToolChain::getSupportedSanitizers())
+      SanitizerMask K = parseSanitizerValue(Value, /*Allow Groups*/ true);
+      if (K & Supported) {
         SupportedSanitizers.push_back(Value);
-      else
+        SupportedMask |= K;
+      } else {
         UnSupportedSanitizers.push_back(Value);
+      }
     }
 
     // If there are no supported sanitizers, drop the whole argument.
@@ -221,8 +226,9 @@ class LLVM_LIBRARY_VISIBILITY ROCMToolChain : public AMDGPUToolChain {
         diagnoseUnsupportedOption(A, DAL, DriverArgs, Value);
       }
     }
-    // If we know the target arch, check if the sanitizer is supported for it.
-    if (shouldSkipSanitizeOption(TC, DriverArgs, TargetID, A))
+    // The xnack+ feature is only required for ASan on AMDGPU.
+    if ((SupportedMask & SanitizerKind::Address) &&
+        shouldSkipSanitizeOption(TC, DriverArgs, TargetID, A))
       return true;
 
     // Add a new argument with only the supported sanitizers.
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 6416baf9126ff..d437939db67b5 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -9361,7 +9361,13 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
       OPT_fprofile_generate,
       OPT_fprofile_generate_EQ,
       OPT_fprofile_instr_generate,
-      OPT_fprofile_instr_generate_EQ};
+      OPT_fprofile_instr_generate_EQ,
+      OPT_fsanitize_EQ,
+      OPT_fno_sanitize_EQ,
+      OPT_fsanitize_minimal_runtime,
+      OPT_fno_sanitize_minimal_runtime,
+      OPT_fsanitize_trap_EQ,
+      OPT_fno_sanitize_trap_EQ};
   const llvm::DenseSet<unsigned> LinkerOptions{OPT_mllvm, OPT_Zlinker_input};
   auto ShouldForwardForToolChain = [&](Arg *A, const ToolChain &TC) {
     auto HasProfileRT = TC.getVFS().exists(
@@ -9374,6 +9380,18 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
          A->getOption().matches(OPT_fprofile_instr_generate) ||
          A->getOption().matches(OPT_fprofile_instr_generate_EQ)))
       return false;
+    auto HasUBSanRT = TC.getVFS().exists(
+        TC.getCompilerRT(Args, "ubsan_minimal", ToolChain::FT_Static));
+    // Don't forward sanitizer arguments if the toolchain doesn't support it.
+    // Without this check using it on the host would result in linker errors.
+    if (!HasUBSanRT &&
+        (A->getOption().matches(OPT_fsanitize_EQ) ||
+         A->getOption().matches(OPT_fno_sanitize_EQ) ||
+         A->getOption().matches(OPT_fsanitize_minimal_runtime) ||
+         A->getOption().matches(OPT_fno_sanitize_minimal_runtime) ||
+         A->getOption().matches(OPT_fsanitize_trap_EQ) ||
+         A->getOption().matches(OPT_fno_sanitize_trap_EQ)))
+      return false;
     // Don't forward -mllvm to toolchains that don't support LLVM.
     return TC.HasNativeLLVMSupport() || A->getOption().getID() != OPT_mllvm;
   };
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 9a17fa2546e68..d115a7b26fa06 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1789,7 +1789,7 @@ bool tools::addSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
   }
   // If there is a static runtime with no dynamic list, force all the symbols
   // to be dynamic to be sure we export sanitizer interface functions.
-  if (AddExportDynamic)
+  if (AddExportDynamic && !TC.getTriple().isNVPTX())
     CmdArgs.push_back("--export-dynamic");
 
   if (SanArgs.hasCrossDsoCfi() && !AddExportDynamic)
diff --git a/clang/lib/Driver/ToolChains/Cuda.cpp b/clang/lib/Driver/ToolChains/Cuda.cpp
index dc94fca06db7d..2a968fcd88402 100644
--- a/clang/lib/Driver/ToolChains/Cuda.cpp
+++ b/clang/lib/Driver/ToolChains/Cuda.cpp
@@ -645,6 +645,7 @@ void NVPTX::Linker::ConstructJob(Compilation &C, const JobAction &JA,
   CmdArgs.push_back(Args.MakeArgString(Twine("-L") + DefaultLibPath));
 
   getToolChain().addProfileRTLibs(Args, CmdArgs);
+  addSanitizerRuntimes(getToolChain(), Args, CmdArgs);
 
   if (Args.hasArg(options::OPT_stdlib))
     CmdArgs.append({"-lc", "-lm"});
diff --git a/clang/test/Driver/Inputs/resource_dir_with_per_target_subdir/lib/amdgcn-amd-amdhsa/libclang_rt.ubsan_minimal.a b/clang/test/Driver/Inputs/resource_dir_with_per_target_subdir/lib/amdgcn-amd-amdhsa/libclang_rt.ubsan_minimal.a
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/resource_dir_with_per_target_subdir/lib/nvptx64-nvidia-cuda/libclang_rt.ubsan_minimal.a b/clang/test/Driver/Inputs/resource_dir_with_per_target_subdir/lib/nvptx64-nvidia-cuda/libclang_rt.ubsan_minimal.a
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/amdgpu-toolchain.c b/clang/test/Driver/amdgpu-toolchain.c
index 2a48ca6bb7670..54c9a5703c081 100644
--- a/clang/test/Driver/amdgpu-toolchain.c
+++ b/clang/test/Driver/amdgpu-toolchain.c
@@ -52,3 +52,10 @@
 // RUN:   -fprofile-generate %s 2>&1 | FileCheck -check-prefixes=PROFILE %s
 //      PROFILE: ld.lld
 // PROFILE-SAME: "[[RESOURCE_DIR:.+]]{{/|\\\\}}lib{{/|\\\\}}amdgcn-amd-amdhsa{{/|\\\\}}libclang_rt.profile.a"
+
+// RUN: %clang -### --target=amdgcn-amd-amdhsa -mcpu=gfx906 -nogpulib \
+// RUN:   -resource-dir=%S/Inputs/resource_dir_with_per_target_subdir \
+// RUN:   -fsanitize=undefined -fsanitize-minimal-runtime %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=UBSAN %s
+//      UBSAN: ld.lld
+// UBSAN-SAME: "[[RESOURCE_DIR:.+]]{{/|\\\\}}lib{{/|\\\\}}amdgcn-amd-amdhsa{{/|\\\\}}libclang_rt.ubsan_minimal.a"
diff --git a/clang/test/Driver/cuda-cross-compiling.c b/clang/test/Driver/cuda-cross-compiling.c
index 1dea9426f75ce..2bfe9f2a228ac 100644
--- a/clang/test/Driver/cuda-cross-compiling.c
+++ b/clang/test/Driver/cuda-cross-compiling.c
@@ -118,3 +118,10 @@
 // RUN:   -fprofile-generate %s 2>&1 | FileCheck -check-prefixes=PROFILE %s
 //      PROFILE: clang-nvlink-wrapper
 // PROFILE-SAME: "[[RESOURCE_DIR:.+]]{{/|\\\\}}lib{{/|\\\\}}nvptx64-nvidia-cuda{{/|\\\\}}libclang_rt.profile.a"
+
+// RUN: %clang -### --target=nvptx64-nvidia-cuda -march=sm_89 -nogpulib \
+// RUN:   -resource-dir=%S/Inputs/resource_dir_with_per_target_subdir \
+// RUN:   -fsanitize=undefined -fsanitize-minimal-runtime %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=UBSAN %s
+//      UBSAN: clang-nvlink-wrapper
+// UBSAN-SAME: "[[RESOURCE_DIR:.+]]{{/|\\\\}}lib{{/|\\\\}}nvptx64-nvidia-cuda{{/|\\\\}}libclang_rt.ubsan_minimal.a"
diff --git a/clang/test/Driver/openmp-offload-gpu.c b/clang/test/Driver/openmp-offload-gpu.c
index e057959d62044..9115e86a599bc 100644
--- a/clang/test/Driver/openmp-offload-gpu.c
+++ b/clang/test/Driver/openmp-offload-gpu.c
@@ -428,3 +428,24 @@
 // RUN:   | FileCheck --check-prefix=NO-PROFILE %s
 //
 // NO-PROFILE-NOT: --device-compiler=amdgcn-amd-amdhsa=-fprofile-generate
+
+//
+// Check that `-fsanitize=` flags are forwarded to link in the runtime
+// only if present in the resource directory.
+//
+// RUN:   %clang -### --target=x86_64-unknown-linux-gnu -fopenmp=libomp \
+// RUN:     -resource-dir=%S/Inputs/resource_dir_with_per_target_subdir \
+// RUN:     --offload-arch=gfx906 -fsanitize=undefined \
+// RUN:     -fsanitize-minimal-runtime -nogpulib -nogpuinc %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=UBSAN %s
+//
+// UBSAN: clang-linker-wrapper{{.*}}--device-compiler=amdgcn-amd-amdhsa=-fsanitize=undefined
+// UBSAN-SAME: --device-compiler=amdgcn-amd-amdhsa=-fsanitize-minimal-runtime
+//
+// RUN:   %clang -### --target=x86_64-unknown-linux-gnu -fopenmp=libomp \
+// RUN:     -resource-dir=%S/Inputs/resource_dir \
+// RUN:     --offload-arch=gfx906 -fsanitize=undefined \
+// RUN:     -fsanitize-minimal-runtime -nogpulib -nogpuinc %s 2>&1 \
+// RUN:   | FileCheck --check-prefix=NO-UBSAN %s
+//
+// NO-UBSAN-NOT: --device-compiler=amdgcn-amd-amdhsa=-fsanitize=undefined

>From 23b2208d6cd7c8e87be558598d4d5cfc42309560 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Wed, 25 Mar 2026 08:26:45 -0500
Subject: [PATCH 2/2] permit trap w/o runtime

---
 clang/lib/Driver/ToolChains/Clang.cpp | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index d437939db67b5..50ce8e9ce81bd 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -9388,9 +9388,7 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
         (A->getOption().matches(OPT_fsanitize_EQ) ||
          A->getOption().matches(OPT_fno_sanitize_EQ) ||
          A->getOption().matches(OPT_fsanitize_minimal_runtime) ||
-         A->getOption().matches(OPT_fno_sanitize_minimal_runtime) ||
-         A->getOption().matches(OPT_fsanitize_trap_EQ) ||
-         A->getOption().matches(OPT_fno_sanitize_trap_EQ)))
+         A->getOption().matches(OPT_fno_sanitize_minimal_runtime)))
       return false;
     // Don't forward -mllvm to toolchains that don't support LLVM.
     return TC.HasNativeLLVMSupport() || A->getOption().getID() != OPT_mllvm;



More information about the cfe-commits mailing list