[llvm-branch-commits] [clang] [Clang] Add support for the `-fsanitize=concurrency` runtime (PR #225781)
Joseph Huber via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Oct 1 18:01:26 PDT 2026
https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/225781
>From 222e13f855759ef83e770673b60eb638091459db Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Mon, 21 Sep 2026 17:34:54 -0500
Subject: [PATCH 1/4] [Clang] Add support for the `-fsanitize=concurrency`
runtime
Summary:
Add the frontend sanitizer kind, function attributes, pass pipeline
integration, predefined macro, driver handling, and documentation for
ConcurrencySanitizer.
---
clang/docs/ConcurrencySanitizer.md | 207 ++++++++++++++++++
clang/docs/index.md | 1 +
clang/include/clang/Basic/Sanitizers.def | 3 +
clang/include/clang/Driver/SanitizerArgs.h | 3 +
clang/lib/CodeGen/BackendUtil.cpp | 7 +
clang/lib/CodeGen/CGDeclCXX.cpp | 4 +
clang/lib/CodeGen/CodeGenFunction.cpp | 13 +-
clang/lib/Driver/SanitizerArgs.cpp | 14 +-
clang/lib/Driver/ToolChains/AMDGPU.cpp | 2 +
clang/lib/Driver/ToolChains/Clang.cpp | 2 +-
clang/lib/Driver/ToolChains/CommonArgs.cpp | 40 +++-
clang/lib/Driver/ToolChains/Linux.cpp | 2 +
clang/lib/Frontend/InitPreprocessor.cpp | 2 +
clang/test/CodeGen/sanitize-concurrency.c | 73 ++++++
clang/test/Driver/fsanitize.c | 15 ++
.../test/Preprocessor/sanitizer-predefines.c | 3 +
16 files changed, 375 insertions(+), 16 deletions(-)
create mode 100644 clang/docs/ConcurrencySanitizer.md
create mode 100644 clang/test/CodeGen/sanitize-concurrency.c
diff --git a/clang/docs/ConcurrencySanitizer.md b/clang/docs/ConcurrencySanitizer.md
new file mode 100644
index 0000000000000..d58cdb959d1d0
--- /dev/null
+++ b/clang/docs/ConcurrencySanitizer.md
@@ -0,0 +1,207 @@
+# ConcurrencySanitizer
+
+## Introduction
+
+ConcurrencySanitizer (CSan) is a data race detector for CPU and GPU programs.
+It consists of an LLVM instrumentation pass and run-time libraries in
+compiler-rt.
+
+Unlike ThreadSanitizer, CSan does not maintain a happens-before model or shadow
+state for every memory location. Memory accesses are sampled at random and
+stalled to detect unordered accesses. Importantly, this method has **no false
+positives** and **fixed memory overhead.** However, the probabalistic nature
+means that many runs are required to state confidently that the application is
+not racy.
+
+## How to build
+
+Build LLVM/Clang with [CMake](https://llvm.org/docs/CMake.html). To enable
+offloading support, the easiest configuration is provided in a CMake cache file.
+
+```sh
+cmake ../llvm -G Ninja \
+ -C ../offload/cmake/caches/Offload.cmake \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX=<PATH>
+```
+
+## Supported platforms
+
+CSan currently supports:
+
+- Linux on x86-64.
+- AMDGPU devices on Linux through the HSA runtime.
+
+Support for other hosts and GPU targets is not currently implemented.
+
+## Usage
+
+Compile and link the complete program with `-fsanitize=concurrency`. Use `-g`
+to include source locations in reports and `-O1` or higher for representative
+optimized code.
+
+For a CPU program:
+
+```console
+$ clang++ -fsanitize=concurrency -g -O1 race.cpp -pthread
+$ ./a.out
+```
+
+For a HIP program:
+
+```console
+$ clang++ -x hip --offload-arch=gfx1030 -fsanitize=concurrency \
+ -g -O1 race.hip
+$ ./a.out
+```
+
+For an OpenMP offload program:
+
+```console
+$ clang++ -fopenmp --offload-arch=gfx1030 -fsanitize=concurrency \
+ -g -O1 race.cpp
+$ ./a.out
+```
+
+When CSan detects a race, it writes a report to standard error. A watchpoint
+report normally identifies both conflicting accesses:
+
+```text
+WARNING: ConcurrencySanitizer: data race
+ Write of size 4 at 0x...:
+ #0 update race.cpp:12
+ Previous write of size 4 at 0x...:
+ #0 update race.cpp:13
+```
+
+A value change without a matching instrumented access is reported as a
+`data race of unknown origin` and contains only the sampled access. GPU reports
+also identify the block, thread, and lane and may name the global variable
+containing the raced address.
+
+## How it works
+
+The instrumentation marks memory accesses with a runtime function. The runtime
+then samples these accesses. Sampling is fundamentally stalling a given value
+while checking if any other threads have touched it during that window. This is
+done using a **watchpoint** table and by **value comparison**. Each probed
+access sets up a tripwire and checks later if another thread triggered it like
+in the following pseudocode:
+
+```c
+static u64 watchpoints[N]; // Hash-indexed, zero is empty.
+
+// Emitted before the access to simulate a stalled operation.
+void check_access(volatile void *addr, u32 size, u32 type) {
+ // Every access probes. A read conflicts only with a watched write, a
+ // write conflicts with either.
+ if (u64 *wp = find_watchpoint(addr, size, type))
+ consume(wp, this_pc()); // Hand our location to the owner.
+
+ if (!should_sample()) // 1-in-N chance for a non-atomic access.
+ return;
+
+ u64 *wp = arm_watchpoint(addr, size, type);
+ if (!wp) // Slot is already taken.
+ return;
+
+ auto old = read(addr, size);
+ delay(ctx.rand()); // Randomized, bounded.
+ auto new = read(addr, size);
+
+ if (void *peer = disarm(wp))
+ report_race(addr, this_pc(), peer);
+ else if (new != old)
+ report_race(addr, this_pc(), UNKNOWN);
+}
+```
+
+The probability of detecting a race and the overhead of the sanitizer is
+directly related to the probability of sampling and the length of the delay. The
+watchpoint table can be omitted to make a minimal detector with less coverage.
+
+## `__SANITIZE_CONCURRENCY__`
+
+Code can test whether CSan instrumentation is enabled:
+
+```c
+#if defined(__SANITIZE_CONCURRENCY__)
+// Code built with ConcurrencySanitizer.
+#endif
+```
+
+## Disabling instrumentation
+
+Use `no_sanitize("concurrency")` to disable CSan memory-access instrumentation
+for a function:
+
+```c
+__attribute__((no_sanitize("concurrency")))
+void uninstrumented_function() {
+ // ...
+}
+```
+
+The `disable_sanitizer_instrumentation` attribute disables all sanitizer
+instrumentation and takes precedence over `no_sanitize` attributes. Use either
+form carefully: uninstrumented accesses cannot consume watchpoints and may
+reduce detection or produce reports of unknown origin.
+
+## Run-time flags
+
+CSan reads options from the `CSAN_OPTIONS` environment variable:
+
+```console
+$ CSAN_OPTIONS="skip_watch=1000:udelay=200" ./a.out
+```
+
+The CSan-specific host options are:
+
+- `skip_watch` (default: `4000`): Approximate number of accesses skipped by a
+ thread before arming another watchpoint.
+- `udelay` (default: `80`): Microseconds to delay after arming a watchpoint.
+- `halt_on_error` (default: `false`): Terminate after the first host report.
+
+These options currently control the host detector. The AMDGPU sampling rate and
+delay interval are fixed. Common sanitizer options still control facilities
+such as symbolization and report formatting on the host side of GPU reporting.
+
+Use `CSAN_OPTIONS=help=1` to print all CSan and sanitizer-common options.
+Programs may provide host defaults by defining:
+
+```c++
+extern "C" const char *__csan_default_options() {
+ return "skip_watch=1000:udelay=200";
+}
+```
+
+Environment options take precedence over `__csan_default_options`.
+
+## Limitations
+
+- CSan is probabilistic. It detects races that overlap a sampled observation
+ window rather than proving that a program is race-free.
+- CSan does not implement ThreadSanitizer's happens-before model, lock-order
+ analysis, thread history, or synchronization diagnostics.
+- CSan cannot be combined with several other full sanitizers, including
+ ThreadSanitizer and AddressSanitizer.
+
+## Security considerations
+
+ConcurrencySanitizer is a testing tool. Its runtime is not intended for
+production executables and was not developed under security-sensitive runtime
+constraints. Instrumented programs deliberately change scheduling and reserve
+additional memory.
+
+## Current status
+
+ConcurrencySanitizer is experimental. Its interface, reports, supported targets,
+sampling policy, and run-time options may change. The compiler-rt test suite can
+be run with:
+
+```console
+$ ninja check-csan
+```
+
+Minimized test cases and reports from real CPU and GPU applications are
+welcome.
diff --git a/clang/docs/index.md b/clang/docs/index.md
index a05210b8e73a0..260aeef3f7ed1 100644
--- a/clang/docs/index.md
+++ b/clang/docs/index.md
@@ -29,6 +29,7 @@ FunctionEffectAnalysis
AddressSanitizer
HardwareAssistedAddressSanitizer
ThreadSanitizer
+ConcurrencySanitizer
MemorySanitizer
UndefinedBehaviorSanitizer
DataFlowSanitizer
diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def
index da85431625026..8120c5842919b 100644
--- a/clang/include/clang/Basic/Sanitizers.def
+++ b/clang/include/clang/Basic/Sanitizers.def
@@ -198,6 +198,9 @@ SANITIZER("scudo", Scudo)
// AllocToken
SANITIZER("alloc-token", AllocToken)
+// ConcurrencySanitizer.
+SANITIZER("concurrency", Concurrency)
+
// Magic group, containing all sanitizers. For example, "-fno-sanitize=all"
// can be used to disable all the sanitizers.
SANITIZER_GROUP("all", All, ~SanitizerMask())
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index d7823d71b5860..145df2d94c61f 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -107,6 +107,9 @@ class SanitizerArgs {
}
bool needsTysanRt() const { return Sanitizers.has(SanitizerKind::Type); }
bool needsTsanRt() const { return Sanitizers.has(SanitizerKind::Thread); }
+ bool needsCsanRt() const {
+ return Sanitizers.has(SanitizerKind::Concurrency);
+ }
bool needsMsanRt() const { return Sanitizers.has(SanitizerKind::Memory); }
bool needsFuzzer() const { return Sanitizers.has(SanitizerKind::Fuzzer); }
bool needsLsanRt() const {
diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp
index c754c75aa59de..f8b9c26ada55d 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -74,6 +74,7 @@
#include "llvm/Transforms/Instrumentation/AddressSanitizer.h"
#include "llvm/Transforms/Instrumentation/AddressSanitizerOptions.h"
#include "llvm/Transforms/Instrumentation/BoundsChecking.h"
+#include "llvm/Transforms/Instrumentation/ConcurrencySanitizer.h"
#include "llvm/Transforms/Instrumentation/CopyProf.h"
#include "llvm/Transforms/Instrumentation/DataFlowSanitizer.h"
#include "llvm/Transforms/Instrumentation/GCOVProfiler.h"
@@ -704,6 +705,12 @@ static void addSanitizers(const Triple &TargetTriple,
MPM.addPass(createModuleToFunctionPassAdaptor(ThreadSanitizerPass()));
}
+ if (LangOpts.Sanitize.has(SanitizerKind::Concurrency)) {
+ MPM.addPass(ModuleConcurrencySanitizerPass());
+ MPM.addPass(
+ createModuleToFunctionPassAdaptor(ConcurrencySanitizerPass()));
+ }
+
if (LangOpts.Sanitize.has(SanitizerKind::Type))
MPM.addPass(TypeSanitizerPass());
diff --git a/clang/lib/CodeGen/CGDeclCXX.cpp b/clang/lib/CodeGen/CGDeclCXX.cpp
index a54809c48037e..f58492f1f41e0 100644
--- a/clang/lib/CodeGen/CGDeclCXX.cpp
+++ b/clang/lib/CodeGen/CGDeclCXX.cpp
@@ -487,6 +487,10 @@ llvm::Function *CodeGenModule::CreateGlobalInitOrCleanUpFunction(
!isInNoSanitizeList(SanitizerKind::Thread, Fn, Loc))
Fn->addFnAttr(llvm::Attribute::SanitizeThread);
+ if (getLangOpts().Sanitize.has(SanitizerKind::Concurrency) &&
+ !isInNoSanitizeList(SanitizerKind::Concurrency, Fn, Loc))
+ Fn->addFnAttr(llvm::Attribute::SanitizeConcurrency);
+
if (getLangOpts().Sanitize.has(SanitizerKind::NumericalStability) &&
!isInNoSanitizeList(SanitizerKind::NumericalStability, Fn, Loc))
Fn->addFnAttr(llvm::Attribute::SanitizeNumericalStability);
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 79bcae4c5f7a0..f069550b3a0b6 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -689,6 +689,10 @@ void CodeGenFunction::markAsIgnoreThreadCheckingAtRuntime(llvm::Function *Fn) {
Fn->addFnAttr("sanitize_thread_no_checking_at_run_time");
Fn->removeFnAttr(llvm::Attribute::SanitizeThread);
}
+ if (SanOpts.has(SanitizerKind::Concurrency)) {
+ Fn->addFnAttr("sanitize_concurrency_no_checking_at_run_time");
+ Fn->removeFnAttr(llvm::Attribute::SanitizeConcurrency);
+ }
}
/// Check if the return value of this function requires sanitization.
@@ -825,6 +829,8 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
Fn->addFnAttr(llvm::Attribute::SanitizeMemTag);
if (SanOpts.has(SanitizerKind::Thread))
Fn->addFnAttr(llvm::Attribute::SanitizeThread);
+ if (SanOpts.has(SanitizerKind::Concurrency))
+ Fn->addFnAttr(llvm::Attribute::SanitizeConcurrency);
if (SanOpts.has(SanitizerKind::Type))
Fn->addFnAttr(llvm::Attribute::SanitizeType);
if (SanOpts.has(SanitizerKind::NumericalStability))
@@ -852,9 +858,10 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
if (SanOpts.hasOneOf(SanitizerKind::Fuzzer | SanitizerKind::FuzzerNoLink))
Fn->addFnAttr(llvm::Attribute::OptForFuzzing);
- // Ignore TSan memory acesses from within ObjC/ObjC++ dealloc, initialize,
- // .cxx_destruct, __destroy_helper_block_ and all of their calees at run time.
- if (SanOpts.has(SanitizerKind::Thread)) {
+ // Ignore TSan/CSan memory accesses from within ObjC/ObjC++ dealloc,
+ // initialize, .cxx_destruct, __destroy_helper_block_ and all of their callees
+ // at run time.
+ if (SanOpts.hasOneOf(SanitizerKind::Thread | SanitizerKind::Concurrency)) {
if (const auto *OMD = dyn_cast_or_null<ObjCMethodDecl>(D)) {
const IdentifierInfo *II = OMD->getSelector().getIdentifierInfoForSlot(0);
if (OMD->getMethodFamily() == OMF_dealloc ||
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index 438283c60c0ba..de35f8e4153f9 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -40,8 +40,8 @@ static const SanitizerMask NotAllowedWithExecuteOnly =
SanitizerKind::Function | SanitizerKind::KCFI;
static const SanitizerMask NeedsUnwindTables =
SanitizerKind::Address | SanitizerKind::HWAddress | SanitizerKind::Type |
- SanitizerKind::Thread | SanitizerKind::Memory | SanitizerKind::DataFlow |
- SanitizerKind::NumericalStability;
+ SanitizerKind::Thread | SanitizerKind::Concurrency | SanitizerKind::Memory |
+ SanitizerKind::DataFlow | SanitizerKind::NumericalStability;
static const SanitizerMask SupportsCoverage =
SanitizerKind::Address | SanitizerKind::HWAddress |
SanitizerKind::KernelAddress | SanitizerKind::KernelHWAddress |
@@ -53,7 +53,8 @@ static const SanitizerMask SupportsCoverage =
SanitizerKind::DataFlow | SanitizerKind::Fuzzer |
SanitizerKind::FuzzerNoLink | SanitizerKind::FloatDivideByZero |
SanitizerKind::SafeStack | SanitizerKind::ShadowCallStack |
- SanitizerKind::Thread | SanitizerKind::ObjCCast | SanitizerKind::KCFI |
+ SanitizerKind::Thread | SanitizerKind::Concurrency |
+ SanitizerKind::ObjCCast | SanitizerKind::KCFI |
SanitizerKind::NumericalStability | SanitizerKind::Vptr |
SanitizerKind::CFI | SanitizerKind::AllocToken;
static const SanitizerMask RecoverableByDefault =
@@ -738,7 +739,12 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
SanitizerKind::Address | SanitizerKind::KernelAddress |
SanitizerKind::Memory | SanitizerKind::Leak |
SanitizerKind::Thread),
- std::make_pair(SanitizerKind::Thread, SanitizerKind::Memory),
+ std::make_pair(SanitizerKind::Thread,
+ SanitizerKind::Memory | SanitizerKind::Concurrency),
+ std::make_pair(SanitizerKind::Concurrency,
+ SanitizerKind::Thread | SanitizerKind::Address |
+ SanitizerKind::Memory | SanitizerKind::Leak |
+ SanitizerKind::Undefined),
std::make_pair(SanitizerKind::Leak,
SanitizerKind::Thread | SanitizerKind::Memory),
std::make_pair(SanitizerKind::KernelAddress,
diff --git a/clang/lib/Driver/ToolChains/AMDGPU.cpp b/clang/lib/Driver/ToolChains/AMDGPU.cpp
index 11583a5d8b931..84e9db447ff09 100644
--- a/clang/lib/Driver/ToolChains/AMDGPU.cpp
+++ b/clang/lib/Driver/ToolChains/AMDGPU.cpp
@@ -1393,6 +1393,8 @@ SanitizerMask AMDGPUToolChain::getSupportedSanitizers(
// arch xnack support.
if (!BA || isXnackAvailable(getTriple(), BA.ArchName))
SupportedMask |= SanitizerKind::Address;
+ // Watchpoint probes do not require xnack.
+ SupportedMask |= SanitizerKind::Concurrency;
return SupportedMask;
}
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index f0d04bf4b6a83..25818c880c348 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -9820,7 +9820,7 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
// 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 (requiresUBSanRT(ID) && !ToolChainHasRT(TC, "ubsan_minimal") &&
- !ToolChainHasRT(TC, "ubsan_standalone"))
+ !ToolChainHasRT(TC, "ubsan_standalone") && !ToolChainHasRT(TC, "csan"))
return false;
// Don't forward -mllvm to toolchains that don't support LLVM.
return TC.HasNativeLLVMSupport() || ID != OPT_mllvm;
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index c54a0b1068f43..e31990df551a9 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1658,8 +1658,9 @@ void tools::linkSanitizerRuntimeDeps(const ToolChain &TC,
CmdArgs.push_back("-lresolv");
}
-// Host interceptor library for offload UBSan.
-static bool hostNeedsUbsanOffloadRt(Compilation &C, const ToolChain &HostTC) {
+template <typename Predicate>
+static bool hostNeedsOffloadRt(Compilation &C, const ToolChain &HostTC,
+ Predicate NeedsRuntime) {
if (HostTC.getTriple().isGPU())
return false;
@@ -1677,7 +1678,7 @@ static bool hostNeedsUbsanOffloadRt(Compilation &C, const ToolChain &HostTC) {
C.getDriver().getOffloadArchs(C, C.getArgs(), Kind, *DevTC)) {
const ArgList &DevArgs = C.getArgsForToolChain(DevTC, BA, Kind);
SanitizerArgs DevSan = DevTC->getSanitizerArgs(DevArgs, BA, Kind);
- if (DevSan.needsUbsanRt() && !DevSan.requiresMinimalRuntime())
+ if (NeedsRuntime(DevSan))
return true;
}
}
@@ -1685,6 +1686,17 @@ static bool hostNeedsUbsanOffloadRt(Compilation &C, const ToolChain &HostTC) {
return false;
}
+static bool hostNeedsUbsanOffloadRt(Compilation &C, const ToolChain &HostTC) {
+ return hostNeedsOffloadRt(C, HostTC, [](const SanitizerArgs &S) {
+ return S.needsUbsanRt() && !S.requiresMinimalRuntime();
+ });
+}
+
+static bool hostNeedsCsanOffloadRt(Compilation &C, const ToolChain &HostTC) {
+ return hostNeedsOffloadRt(
+ C, HostTC, [](const SanitizerArgs &S) { return S.needsCsanRt(); });
+}
+
static void
collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
const ArgList &Args,
@@ -1695,8 +1707,10 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
SmallVectorImpl<StringRef> &RequiredSymbols) {
assert(!TC.getTriple().isOSDarwin() && "it's not used by Darwin");
const SanitizerArgs &SanArgs = TC.getSanitizerArgs(Args);
- const bool NeedsOffloadRt = hostNeedsUbsanOffloadRt(C, TC);
- const bool NeedsUbsanRt = SanArgs.needsUbsanRt() || NeedsOffloadRt;
+ const bool NeedsUbsanOffloadRt = hostNeedsUbsanOffloadRt(C, TC);
+ const bool NeedsCsanOffloadRt = hostNeedsCsanOffloadRt(C, TC);
+ const bool NeedsUbsanRt = SanArgs.needsUbsanRt() || NeedsUbsanOffloadRt;
+ const bool NeedsCsanRt = SanArgs.needsCsanRt() || NeedsCsanOffloadRt;
// Collect shared runtimes.
if (SanArgs.needsSharedRt()) {
if (SanArgs.needsAsanRt()) {
@@ -1746,18 +1760,26 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
// Offloading images can live in DSOs, the host interceptors must follow. The
// shared UBSan runtime already contains them.
- if (NeedsOffloadRt && !SanArgs.needsSharedRt()) {
+ if (NeedsUbsanOffloadRt && !SanArgs.needsSharedRt()) {
NonWholeStaticRuntimes.push_back("ubsan_offload");
RequiredSymbols.push_back("__ubsan_offload_init");
}
- if (NeedsOffloadRt && !Args.hasArg(options::OPT_shared))
+ if (NeedsUbsanOffloadRt && !Args.hasArg(options::OPT_shared))
HelperStaticRuntimes.push_back("ubsan_offload-preinit");
+ if (NeedsCsanOffloadRt) {
+ NonWholeStaticRuntimes.push_back("csan_offload");
+ RequiredSymbols.push_back("__csan_offload_init");
+ }
// Collect static runtimes.
if (Args.hasArg(options::OPT_shared)) {
// Don't link static runtimes into DSOs.
- if (NeedsOffloadRt && !SanArgs.needsSharedRt() && !SanArgs.needsUbsanRt())
+ if (NeedsUbsanOffloadRt && !SanArgs.needsSharedRt() &&
+ !SanArgs.needsUbsanRt())
StaticRuntimes.push_back("ubsan_standalone");
+ if (NeedsCsanOffloadRt && !SanArgs.needsSharedRt() &&
+ !SanArgs.needsCsanRt())
+ StaticRuntimes.push_back("csan");
return;
}
@@ -1818,6 +1840,8 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
StaticRuntimes.push_back("ubsan_standalone");
}
}
+ if (!SanArgs.needsSharedRt() && NeedsCsanRt)
+ StaticRuntimes.push_back("csan");
if (SanArgs.needsSafeStackRt()) {
NonWholeStaticRuntimes.push_back("safestack");
RequiredSymbols.push_back("__safestack_init");
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index e295b2516da16..43b48903fc053 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -1007,6 +1007,8 @@ Linux::getSupportedSanitizers(BoundArch BA,
if (IsX86_64 || IsMIPS64 || IsAArch64 || IsPowerPC64 || IsSystemZ ||
IsLoongArch64 || IsRISCV64)
Res |= SanitizerKind::Thread;
+ if (IsX86_64)
+ Res |= SanitizerKind::Concurrency;
if (IsX86_64 || IsAArch64 || IsSystemZ || IsHexagon)
Res |= SanitizerKind::Type;
if (IsX86_64 || IsSystemZ || IsPowerPC64)
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 784ff9951a25a..264412ea3f714 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -1535,6 +1535,8 @@ static void InitializePredefinedMacros(const TargetInfo &TI,
Builder.defineMacro("__SANITIZE_HWADDRESS__");
if (LangOpts.Sanitize.has(SanitizerKind::Thread))
Builder.defineMacro("__SANITIZE_THREAD__");
+ if (LangOpts.Sanitize.has(SanitizerKind::Concurrency))
+ Builder.defineMacro("__SANITIZE_CONCURRENCY__");
if (LangOpts.Sanitize.has(SanitizerKind::AllocToken))
Builder.defineMacro("__SANITIZE_ALLOC_TOKEN__");
diff --git a/clang/test/CodeGen/sanitize-concurrency.c b/clang/test/CodeGen/sanitize-concurrency.c
new file mode 100644
index 0000000000000..aaae91ca71da4
--- /dev/null
+++ b/clang/test/CodeGen/sanitize-concurrency.c
@@ -0,0 +1,73 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck -check-prefixes CHECK,WITHOUT %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -o - %s -fsanitize=concurrency | FileCheck -check-prefixes CHECK,CSAN %s
+
+#if defined(__SANITIZE_CONCURRENCY__)
+// CSAN-LABEL: define dso_local i32 @concurrency_macro_defined(
+// CSAN-SAME: ) #[[ATTR0:[0-9]+]] {
+// CSAN-NEXT: [[ENTRY:.*:]]
+// CSAN-NEXT: ret i32 1
+//
+int concurrency_macro_defined(void) { return 1; }
+#endif
+
+// WITHOUT-LABEL: define dso_local i32 @instrumented1(
+// WITHOUT-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]]) #[[ATTR0:[0-9]+]] {
+// WITHOUT-NEXT: [[ENTRY:.*:]]
+// WITHOUT-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8
+// WITHOUT-NEXT: [[B_ADDR:%.*]] = alloca ptr, align 8
+// WITHOUT-NEXT: store ptr [[A]], ptr [[A_ADDR]], align 8
+// WITHOUT-NEXT: store ptr [[B]], ptr [[B_ADDR]], align 8
+// WITHOUT-NEXT: [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// WITHOUT-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
+// WITHOUT-NEXT: [[TMP2:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// WITHOUT-NEXT: [[ATOMIC_LOAD:%.*]] = load atomic i32, ptr [[TMP2]] seq_cst, align 4
+// WITHOUT-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP1]], [[ATOMIC_LOAD]]
+// WITHOUT-NEXT: ret i32 [[ADD]]
+//
+// CSAN-LABEL: define dso_local i32 @instrumented1(
+// CSAN-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]]) #[[ATTR0]] {
+// CSAN-NEXT: [[ENTRY:.*:]]
+// CSAN-NEXT: [[TMP0:%.*]] = call ptr @llvm.returnaddress.p0(i32 0)
+// CSAN-NEXT: call void @__csan_func_entry(ptr [[TMP0]])
+// CSAN-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8
+// CSAN-NEXT: [[B_ADDR:%.*]] = alloca ptr, align 8
+// CSAN-NEXT: store ptr [[A]], ptr [[A_ADDR]], align 8
+// CSAN-NEXT: store ptr [[B]], ptr [[B_ADDR]], align 8
+// CSAN-NEXT: [[TMP1:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CSAN-NEXT: call void @__csan_read4(ptr [[TMP1]], i32 0)
+// CSAN-NEXT: [[TMP2:%.*]] = load i32, ptr [[TMP1]], align 4
+// CSAN-NEXT: [[TMP3:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// CSAN-NEXT: call void @__csan_read4(ptr [[TMP3]], i32 1)
+// CSAN-NEXT: [[ATOMIC_LOAD:%.*]] = load atomic i32, ptr [[TMP3]] seq_cst, align 4
+// CSAN-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP2]], [[ATOMIC_LOAD]]
+// CSAN-NEXT: call void @__csan_func_exit()
+// CSAN-NEXT: ret i32 [[ADD]]
+//
+int instrumented1(int *a, _Atomic int *b) {
+ return *a + *b;
+}
+
+// WITHOUT-LABEL: define dso_local i32 @suppressed(
+// WITHOUT-SAME: ptr noundef [[A:%.*]]) #[[ATTR0]] {
+// WITHOUT-NEXT: [[ENTRY:.*:]]
+// WITHOUT-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8
+// WITHOUT-NEXT: store ptr [[A]], ptr [[A_ADDR]], align 8
+// WITHOUT-NEXT: [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// WITHOUT-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
+// WITHOUT-NEXT: ret i32 [[TMP1]]
+//
+// CSAN-LABEL: define dso_local i32 @suppressed(
+// CSAN-SAME: ptr noundef [[A:%.*]]) #[[ATTR1:[0-9]+]] {
+// CSAN-NEXT: [[ENTRY:.*:]]
+// CSAN-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8
+// CSAN-NEXT: store ptr [[A]], ptr [[A_ADDR]], align 8
+// CSAN-NEXT: [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// CSAN-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
+// CSAN-NEXT: ret i32 [[TMP1]]
+//
+__attribute__((no_sanitize("concurrency"))) int suppressed(int *a) {
+ return *a;
+}
+//// NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
+// CHECK: {{.*}}
diff --git a/clang/test/Driver/fsanitize.c b/clang/test/Driver/fsanitize.c
index 97dbee7c15bc8..55579be3ae1e2 100644
--- a/clang/test/Driver/fsanitize.c
+++ b/clang/test/Driver/fsanitize.c
@@ -23,6 +23,21 @@
// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=leak,thread -pie -fno-rtti %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANL-SANT
// CHECK-SANL-SANT: '-fsanitize=leak' not allowed with '-fsanitize=thread'
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize=concurrency %s -### 2>&1 \
+// RUN: -resource-dir=%S/Inputs/resource_dir \
+// RUN: | FileCheck %s --check-prefix=CHECK-SANC-X64
+// CHECK-SANC-X64: "-fsanitize=concurrency"
+// CHECK-SANC-X64: libclang_rt.csan.a
+
+// RUN: %clang --target=amdgcn-amd-amdhsa -mcpu=gfx900 -nogpulib -fsanitize=concurrency %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-AMDGPU
+// CHECK-SANC-AMDGPU: "-fsanitize=concurrency"
+
+// RUN: not %clang --target=amdgcn-amd-amdhsa -mcpu=gfx900:xnack+ -nogpulib -fsanitize=concurrency,address %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-SANA
+// CHECK-SANC-SANA: '-fsanitize=concurrency' not allowed with '-fsanitize=address'
+
+// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=concurrency,undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-SANU
+// CHECK-SANC-SANU: '-fsanitize=concurrency' not allowed with '-fsanitize=undefined'
+
// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=leak,memory -pie -fno-rtti %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANL-SANM
// CHECK-SANL-SANM: '-fsanitize=leak' not allowed with '-fsanitize=memory'
diff --git a/clang/test/Preprocessor/sanitizer-predefines.c b/clang/test/Preprocessor/sanitizer-predefines.c
index d903a40de050f..cec6aa4ffee2e 100644
--- a/clang/test/Preprocessor/sanitizer-predefines.c
+++ b/clang/test/Preprocessor/sanitizer-predefines.c
@@ -8,3 +8,6 @@
// RUN: %clang_cc1 -E -dM -triple aarch64-unknown-linux -fsanitize=thread %s | FileCheck %s --check-prefix=TSAN
// TSAN: #define __SANITIZE_THREAD__ 1
+
+// RUN: %clang_cc1 -E -dM -triple aarch64-unknown-linux -fsanitize=concurrency %s | FileCheck %s --check-prefix=CSAN
+// CSAN: #define __SANITIZE_CONCURRENCY__ 1
>From c2c8c816554046428a6291aa23d086fa0e6a53a6 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Thu, 24 Sep 2026 08:40:20 -0500
Subject: [PATCH 2/4] Comments
---
clang/docs/ConcurrencySanitizer.md | 2 +-
clang/lib/Driver/SanitizerArgs.cpp | 15 ++++--
clang/lib/Driver/ToolChains/AMDGPU.cpp | 5 +-
clang/lib/Driver/ToolChains/Clang.cpp | 23 ++++++---
clang/lib/Driver/ToolChains/CommonArgs.cpp | 11 ++---
clang/lib/Driver/ToolChains/Linux.cpp | 2 +-
.../lib/amdgpu-amd-amdhsa/libclang_rt.csan.a | 0
.../libclang_rt.csan.a | 0
.../libclang_rt.csan_offload.a | 0
.../Driver/fsanitize-concurrency-offload.c | 48 +++++++++++++++++++
clang/test/Driver/fsanitize.c | 27 +++++++++++
11 files changed, 112 insertions(+), 21 deletions(-)
create mode 100644 clang/test/Driver/Inputs/resource_dir_with_amdgpu_csan/lib/amdgpu-amd-amdhsa/libclang_rt.csan.a
create mode 100644 clang/test/Driver/Inputs/resource_dir_with_amdgpu_csan/lib/x86_64-unknown-linux-gnu/libclang_rt.csan.a
create mode 100644 clang/test/Driver/Inputs/resource_dir_with_amdgpu_csan/lib/x86_64-unknown-linux-gnu/libclang_rt.csan_offload.a
create mode 100644 clang/test/Driver/fsanitize-concurrency-offload.c
diff --git a/clang/docs/ConcurrencySanitizer.md b/clang/docs/ConcurrencySanitizer.md
index d58cdb959d1d0..ca255a7f079ae 100644
--- a/clang/docs/ConcurrencySanitizer.md
+++ b/clang/docs/ConcurrencySanitizer.md
@@ -9,7 +9,7 @@ compiler-rt.
Unlike ThreadSanitizer, CSan does not maintain a happens-before model or shadow
state for every memory location. Memory accesses are sampled at random and
stalled to detect unordered accesses. Importantly, this method has **no false
-positives** and **fixed memory overhead.** However, the probabalistic nature
+positives** and **fixed memory overhead.** However, the probabilistic nature
means that many runs are required to state confidently that the application is
not racy.
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index de35f8e4153f9..258a83f2b3898 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -396,6 +396,10 @@ bool SanitizerArgs::needsUbsanRt() const {
needsCfiCrossDsoDiagRt() || (needsScudoRt() && !requiresMinimalRuntime()))
return false;
+ // CSan provides coverage and is incompatible with UBSan.
+ if (needsCsanRt())
+ return false;
+
return (Sanitizers.Mask & NeedsUbsanRt & ~TrapSanitizers.Mask) ||
CoverageFeatures;
}
@@ -741,10 +745,13 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
SanitizerKind::Thread),
std::make_pair(SanitizerKind::Thread,
SanitizerKind::Memory | SanitizerKind::Concurrency),
- std::make_pair(SanitizerKind::Concurrency,
- SanitizerKind::Thread | SanitizerKind::Address |
- SanitizerKind::Memory | SanitizerKind::Leak |
- SanitizerKind::Undefined),
+ std::make_pair(
+ SanitizerKind::Concurrency,
+ SanitizerKind::Thread | SanitizerKind::Address |
+ SanitizerKind::Memory | SanitizerKind::Leak |
+ SanitizerKind::Undefined | SanitizerKind::Integer |
+ SanitizerKind::ImplicitConversion | SanitizerKind::Nullability |
+ SanitizerKind::LocalBounds | SanitizerKind::FloatDivideByZero),
std::make_pair(SanitizerKind::Leak,
SanitizerKind::Thread | SanitizerKind::Memory),
std::make_pair(SanitizerKind::KernelAddress,
diff --git a/clang/lib/Driver/ToolChains/AMDGPU.cpp b/clang/lib/Driver/ToolChains/AMDGPU.cpp
index 84e9db447ff09..47c36ee18d227 100644
--- a/clang/lib/Driver/ToolChains/AMDGPU.cpp
+++ b/clang/lib/Driver/ToolChains/AMDGPU.cpp
@@ -1393,8 +1393,9 @@ SanitizerMask AMDGPUToolChain::getSupportedSanitizers(
// arch xnack support.
if (!BA || isXnackAvailable(getTriple(), BA.ArchName))
SupportedMask |= SanitizerKind::Address;
- // Watchpoint probes do not require xnack.
- SupportedMask |= SanitizerKind::Concurrency;
+ // Watchpoint probes do not require xnack, but the runtime requires HSA.
+ if (getTriple().isAMDGCN() && getTriple().getOS() == llvm::Triple::AMDHSA)
+ SupportedMask |= SanitizerKind::Concurrency;
return SupportedMask;
}
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 25818c880c348..eeb6ff3b78da1 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -9809,7 +9809,15 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
return TC.getVFS().exists(
TC.getCompilerRT(Args, Name, ToolChain::FT_Static));
};
- auto ShouldForwardForToolChain = [&](Arg *A, const ToolChain &TC) {
+ auto ToolChainHasSanitizerRT = [&](const ToolChain &TC, const ArgList &TCArgs,
+ Action::OffloadKind Kind) {
+ if (TC.getSanitizerArgs(TCArgs, /*BA=*/{}, Kind).needsCsanRt())
+ return ToolChainHasRT(TC, "csan");
+ return ToolChainHasRT(TC, "ubsan_minimal") ||
+ ToolChainHasRT(TC, "ubsan_standalone");
+ };
+ auto ShouldForwardForToolChain = [&](Arg *A, const ToolChain &TC,
+ bool HasSanitizerRT) {
unsigned ID = A->getOption().getID();
// Don't forward profiling arguments if the toolchain doesn't support it.
// Without this check using it on the host would result in linker errors.
@@ -9819,20 +9827,19 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
return false;
// 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 (requiresUBSanRT(ID) && !ToolChainHasRT(TC, "ubsan_minimal") &&
- !ToolChainHasRT(TC, "ubsan_standalone") && !ToolChainHasRT(TC, "csan"))
+ if (requiresUBSanRT(ID) && !HasSanitizerRT)
return false;
// Don't forward -mllvm to toolchains that don't support LLVM.
return TC.HasNativeLLVMSupport() || ID != OPT_mllvm;
};
auto ShouldForward = [&](const llvm::DenseSet<unsigned> &Set, Arg *A,
- const ToolChain &TC) {
+ const ToolChain &TC, bool HasSanitizerRT) {
if (A->getOption().matches(OPT_v) && SuppressHIPNoRDCVerbose)
return false;
return (Set.contains(A->getOption().getID()) ||
(A->getOption().getGroup().isValid() &&
Set.contains(A->getOption().getGroup().getID()))) &&
- ShouldForwardForToolChain(A, TC);
+ ShouldForwardForToolChain(A, TC, HasSanitizerRT);
};
ArgStringList CmdArgs;
@@ -9848,13 +9855,15 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
ArgStringList LinkerArgs;
const DerivedArgList &ToolChainArgs =
C.getArgsForToolChain(TC, /*BA=*/{}, Kind);
+ const bool HasSanitizerRT =
+ ToolChainHasSanitizerRT(*TC, ToolChainArgs, Kind);
for (Arg *A : ToolChainArgs) {
if (A->getOption().matches(OPT_Zlinker_input))
LinkerArgs.emplace_back(A->getValue());
- else if (ShouldForward(CompilerOptions, A, *TC)) {
+ else if (ShouldForward(CompilerOptions, A, *TC, HasSanitizerRT)) {
A->claim();
A->render(Args, CompilerArgs);
- } else if (ShouldForward(LinkerOptions, A, *TC)) {
+ } else if (ShouldForward(LinkerOptions, A, *TC, HasSanitizerRT)) {
A->claim();
A->render(Args, LinkerArgs);
}
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index e31990df551a9..0a519874cec86 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1658,9 +1658,9 @@ void tools::linkSanitizerRuntimeDeps(const ToolChain &TC,
CmdArgs.push_back("-lresolv");
}
-template <typename Predicate>
-static bool hostNeedsOffloadRt(Compilation &C, const ToolChain &HostTC,
- Predicate NeedsRuntime) {
+static bool hostNeedsOffloadRt(
+ Compilation &C, const ToolChain &HostTC,
+ llvm::function_ref<bool(const SanitizerArgs &)> NeedsRuntime) {
if (HostTC.getTriple().isGPU())
return false;
@@ -1777,8 +1777,7 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
if (NeedsUbsanOffloadRt && !SanArgs.needsSharedRt() &&
!SanArgs.needsUbsanRt())
StaticRuntimes.push_back("ubsan_standalone");
- if (NeedsCsanOffloadRt && !SanArgs.needsSharedRt() &&
- !SanArgs.needsCsanRt())
+ if (NeedsCsanOffloadRt && !SanArgs.needsCsanRt())
StaticRuntimes.push_back("csan");
return;
}
@@ -1840,7 +1839,7 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
StaticRuntimes.push_back("ubsan_standalone");
}
}
- if (!SanArgs.needsSharedRt() && NeedsCsanRt)
+ if (NeedsCsanRt)
StaticRuntimes.push_back("csan");
if (SanArgs.needsSafeStackRt()) {
NonWholeStaticRuntimes.push_back("safestack");
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 43b48903fc053..3e1a81aec74ab 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -1007,7 +1007,7 @@ Linux::getSupportedSanitizers(BoundArch BA,
if (IsX86_64 || IsMIPS64 || IsAArch64 || IsPowerPC64 || IsSystemZ ||
IsLoongArch64 || IsRISCV64)
Res |= SanitizerKind::Thread;
- if (IsX86_64)
+ if (IsX86_64 && !IsAndroid)
Res |= SanitizerKind::Concurrency;
if (IsX86_64 || IsAArch64 || IsSystemZ || IsHexagon)
Res |= SanitizerKind::Type;
diff --git a/clang/test/Driver/Inputs/resource_dir_with_amdgpu_csan/lib/amdgpu-amd-amdhsa/libclang_rt.csan.a b/clang/test/Driver/Inputs/resource_dir_with_amdgpu_csan/lib/amdgpu-amd-amdhsa/libclang_rt.csan.a
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/resource_dir_with_amdgpu_csan/lib/x86_64-unknown-linux-gnu/libclang_rt.csan.a b/clang/test/Driver/Inputs/resource_dir_with_amdgpu_csan/lib/x86_64-unknown-linux-gnu/libclang_rt.csan.a
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/Inputs/resource_dir_with_amdgpu_csan/lib/x86_64-unknown-linux-gnu/libclang_rt.csan_offload.a b/clang/test/Driver/Inputs/resource_dir_with_amdgpu_csan/lib/x86_64-unknown-linux-gnu/libclang_rt.csan_offload.a
new file mode 100644
index 0000000000000..e69de29bb2d1d
diff --git a/clang/test/Driver/fsanitize-concurrency-offload.c b/clang/test/Driver/fsanitize-concurrency-offload.c
new file mode 100644
index 0000000000000..70aad9e3df9e9
--- /dev/null
+++ b/clang/test/Driver/fsanitize-concurrency-offload.c
@@ -0,0 +1,48 @@
+// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
+// RUN: -fopenmp=libomp --offload-arch=gfx908 -fsanitize=concurrency -nogpuinc \
+// RUN: -nogpulib --rocm-path=%S/Inputs/rocm \
+// RUN: -resource-dir=%S/Inputs/resource_dir_with_amdgpu_csan %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-OPENMP
+// CHECK-OPENMP-DAG: "-triple" "amdgpu9.08-amd-amdhsa"{{.*}}"-fsanitize=concurrency"
+// CHECK-OPENMP-DAG: "--device-compiler=amdgpu-amd-amdhsa=-fsanitize=concurrency"
+// CHECK-OPENMP-DAG: "-u" "__csan_offload_init"
+// CHECK-OPENMP-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan.a"
+// CHECK-OPENMP-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan_offload.a"
+
+// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
+// RUN: -x hip --offload-arch=gfx908 -fsanitize=concurrency -nogpuinc -nogpulib \
+// RUN: --rocm-path=%S/Inputs/rocm \
+// RUN: -resource-dir=%S/Inputs/resource_dir_with_amdgpu_csan %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-HIP
+// CHECK-HIP-DAG: "-triple" "amdgpu9.08-amd-amdhsa"{{.*}}"-fsanitize=concurrency"
+// CHECK-HIP-DAG: "--device-compiler=amdgpu-amd-amdhsa=-fsanitize=concurrency"
+// CHECK-HIP-DAG: "-u" "__csan_offload_init"
+// CHECK-HIP-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan.a"
+// CHECK-HIP-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan_offload.a"
+
+// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
+// RUN: -x hip --offload-arch=gfx908 -Xarch_device -fsanitize=concurrency \
+// RUN: -nogpuinc -nogpulib --rocm-path=%S/Inputs/rocm \
+// RUN: -resource-dir=%S/Inputs/resource_dir_with_amdgpu_csan %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-XARCH-DEV
+// CHECK-XARCH-DEV-DAG: "-u" "__csan_offload_init"
+// CHECK-XARCH-DEV-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan.a"
+// CHECK-XARCH-DEV-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan_offload.a"
+
+// The device toolchain provides UBSan but not CSan.
+// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
+// RUN: -fopenmp=libomp --offload-arch=gfx908 -fsanitize=concurrency -nogpuinc \
+// RUN: -nogpulib --rocm-path=%S/Inputs/rocm \
+// RUN: -resource-dir=%S/Inputs/resource_dir_with_amdgpu_per_target_subdir %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-NO-DEVICE-RT
+// CHECK-NO-DEVICE-RT-NOT: "--device-compiler=amdgpu-amd-amdhsa=-fsanitize=concurrency"
+
+// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
+// RUN: -fopenmp=libomp -fopenmp-targets=x86_64-unknown-linux-gnu \
+// RUN: -fsanitize=concurrency \
+// RUN: -resource-dir=%S/Inputs/resource_dir_with_amdgpu_csan %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-OMP-CPU
+// CHECK-OMP-CPU-NOT: csan_offload
+// CHECK-OMP-CPU-NOT: __csan_offload_init
+
+int main(void) { return 0; }
diff --git a/clang/test/Driver/fsanitize.c b/clang/test/Driver/fsanitize.c
index 55579be3ae1e2..ca3dace2b5cc6 100644
--- a/clang/test/Driver/fsanitize.c
+++ b/clang/test/Driver/fsanitize.c
@@ -38,6 +38,33 @@
// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=concurrency,undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-SANU
// CHECK-SANC-SANU: '-fsanitize=concurrency' not allowed with '-fsanitize=undefined'
+// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=concurrency,integer %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-SANI
+// CHECK-SANC-SANI: '-fsanitize=concurrency' not allowed with '-fsanitize=integer'
+
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize=concurrency -fsanitize-coverage=trace-pc-guard %s -### 2>&1 \
+// RUN: -resource-dir=%S/Inputs/resource_dir \
+// RUN: | FileCheck %s --check-prefix=CHECK-SANC-COV
+// CHECK-SANC-COV-NOT: libclang_rt.ubsan
+// CHECK-SANC-COV: libclang_rt.csan.a
+// CHECK-SANC-COV-NOT: libclang_rt.ubsan
+
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize=concurrency -shared-libsan %s -### 2>&1 \
+// RUN: -resource-dir=%S/Inputs/resource_dir \
+// RUN: | FileCheck %s --check-prefix=CHECK-SANC-SHARED
+// CHECK-SANC-SHARED: libclang_rt.csan.a
+
+// RUN: not %clang --target=x86_64-linux-android -fsanitize=concurrency %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-ANDROID
+// CHECK-SANC-ANDROID: unsupported option '-fsanitize=concurrency' for target 'x86_64-unknown-linux-android'
+
+// RUN: not %clang --target=r600 -mcpu=cypress -nogpulib -fsanitize=concurrency %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-R600
+// CHECK-SANC-R600: unsupported option '-fsanitize=concurrency' for target 'r600'
+// RUN: not %clang --target=amdgpu-amd-amdpal -mcpu=gfx900 -nogpulib -fsanitize=concurrency %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-PAL
+// CHECK-SANC-PAL: unsupported option '-fsanitize=concurrency' for target 'amdgpu-amd-amdpal'
+// RUN: not %clang --target=amdgpu-mesa-mesa3d -mcpu=gfx900 -nogpulib -fsanitize=concurrency %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-MESA
+// CHECK-SANC-MESA: unsupported option '-fsanitize=concurrency' for target 'amdgpu-mesa-mesa3d'
+// RUN: not %clang --target=spirv64-amd-amdhsa -nogpulib -fsanitize=concurrency %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-SPIRV
+// CHECK-SANC-SPIRV: unsupported option '-fsanitize=concurrency' for target 'spirv64-amd-amdhsa'
+
// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=leak,memory -pie -fno-rtti %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANL-SANM
// CHECK-SANL-SANM: '-fsanitize=leak' not allowed with '-fsanitize=memory'
>From e8f3025f282d0dd46d45e7a4280b590b179142e2 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Tue, 29 Sep 2026 12:19:49 -0500
Subject: [PATCH 3/4] ignore
---
.../sanitize-concurrency-ignorelist.txt | 2 ++
clang/test/CodeGen/sanitize-concurrency.c | 29 +++++++++++++++++++
2 files changed, 31 insertions(+)
create mode 100644 clang/test/CodeGen/Inputs/sanitize-concurrency-ignorelist.txt
diff --git a/clang/test/CodeGen/Inputs/sanitize-concurrency-ignorelist.txt b/clang/test/CodeGen/Inputs/sanitize-concurrency-ignorelist.txt
new file mode 100644
index 0000000000000..f23a9a2e657bf
--- /dev/null
+++ b/clang/test/CodeGen/Inputs/sanitize-concurrency-ignorelist.txt
@@ -0,0 +1,2 @@
+[concurrency]
+fun:instrumented1
diff --git a/clang/test/CodeGen/sanitize-concurrency.c b/clang/test/CodeGen/sanitize-concurrency.c
index aaae91ca71da4..c08cbb6174922 100644
--- a/clang/test/CodeGen/sanitize-concurrency.c
+++ b/clang/test/CodeGen/sanitize-concurrency.c
@@ -1,6 +1,7 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck -check-prefixes CHECK,WITHOUT %s
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -o - %s -fsanitize=concurrency | FileCheck -check-prefixes CHECK,CSAN %s
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -o - %s -fsanitize=concurrency -fsanitize-ignorelist=%S/Inputs/sanitize-concurrency-ignorelist.txt | FileCheck -check-prefixes CHECK,IGNORE %s
#if defined(__SANITIZE_CONCURRENCY__)
// CSAN-LABEL: define dso_local i32 @concurrency_macro_defined(
@@ -8,6 +9,11 @@
// CSAN-NEXT: [[ENTRY:.*:]]
// CSAN-NEXT: ret i32 1
//
+// IGNORE-LABEL: define dso_local i32 @concurrency_macro_defined(
+// IGNORE-SAME: ) #[[ATTR0:[0-9]+]] {
+// IGNORE-NEXT: [[ENTRY:.*:]]
+// IGNORE-NEXT: ret i32 1
+//
int concurrency_macro_defined(void) { return 1; }
#endif
@@ -44,6 +50,20 @@ int concurrency_macro_defined(void) { return 1; }
// CSAN-NEXT: call void @__csan_func_exit()
// CSAN-NEXT: ret i32 [[ADD]]
//
+// IGNORE-LABEL: define dso_local i32 @instrumented1(
+// IGNORE-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]]) #[[ATTR1:[0-9]+]] {
+// IGNORE-NEXT: [[ENTRY:.*:]]
+// IGNORE-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8
+// IGNORE-NEXT: [[B_ADDR:%.*]] = alloca ptr, align 8
+// IGNORE-NEXT: store ptr [[A]], ptr [[A_ADDR]], align 8
+// IGNORE-NEXT: store ptr [[B]], ptr [[B_ADDR]], align 8
+// IGNORE-NEXT: [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// IGNORE-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
+// IGNORE-NEXT: [[TMP2:%.*]] = load ptr, ptr [[B_ADDR]], align 8
+// IGNORE-NEXT: [[ATOMIC_LOAD:%.*]] = load atomic i32, ptr [[TMP2]] seq_cst, align 4
+// IGNORE-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP1]], [[ATOMIC_LOAD]]
+// IGNORE-NEXT: ret i32 [[ADD]]
+//
int instrumented1(int *a, _Atomic int *b) {
return *a + *b;
}
@@ -66,6 +86,15 @@ int instrumented1(int *a, _Atomic int *b) {
// CSAN-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
// CSAN-NEXT: ret i32 [[TMP1]]
//
+// IGNORE-LABEL: define dso_local i32 @suppressed(
+// IGNORE-SAME: ptr noundef [[A:%.*]]) #[[ATTR1]] {
+// IGNORE-NEXT: [[ENTRY:.*:]]
+// IGNORE-NEXT: [[A_ADDR:%.*]] = alloca ptr, align 8
+// IGNORE-NEXT: store ptr [[A]], ptr [[A_ADDR]], align 8
+// IGNORE-NEXT: [[TMP0:%.*]] = load ptr, ptr [[A_ADDR]], align 8
+// IGNORE-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP0]], align 4
+// IGNORE-NEXT: ret i32 [[TMP1]]
+//
__attribute__((no_sanitize("concurrency"))) int suppressed(int *a) {
return *a;
}
>From edce25d7be977cccfe0e882ea954d6e65f7fa399 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Tue, 29 Sep 2026 13:51:54 -0500
Subject: [PATCH 4/4] [Clang] Support `-shared-libsan` for offload CSan
Summary:
Follow the UBSan handling. The shared runtime embeds the HSA
interceptors, so `csan_offload` is only linked with static runtimes and
executables pull in `csan_offload-preinit` to initialize early.
---
clang/lib/Driver/ToolChains/CommonArgs.cpp | 13 +++++--
.../Driver/fsanitize-concurrency-offload.c | 38 +++++++++++++++++++
clang/test/Driver/fsanitize.c | 4 +-
3 files changed, 50 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 0a519874cec86..b9f45ba8632d3 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1736,6 +1736,8 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
}
if (SanArgs.needsTsanRt())
SharedRuntimes.push_back("tsan");
+ if (NeedsCsanRt)
+ SharedRuntimes.push_back("csan");
if (SanArgs.needsTysanRt())
SharedRuntimes.push_back("tysan");
if (SanArgs.needsHwasanRt()) {
@@ -1759,17 +1761,19 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
HelperStaticRuntimes.push_back("asan_static");
// Offloading images can live in DSOs, the host interceptors must follow. The
- // shared UBSan runtime already contains them.
+ // shared runtimes already contain them.
if (NeedsUbsanOffloadRt && !SanArgs.needsSharedRt()) {
NonWholeStaticRuntimes.push_back("ubsan_offload");
RequiredSymbols.push_back("__ubsan_offload_init");
}
if (NeedsUbsanOffloadRt && !Args.hasArg(options::OPT_shared))
HelperStaticRuntimes.push_back("ubsan_offload-preinit");
- if (NeedsCsanOffloadRt) {
+ if (NeedsCsanOffloadRt && !SanArgs.needsSharedRt()) {
NonWholeStaticRuntimes.push_back("csan_offload");
RequiredSymbols.push_back("__csan_offload_init");
}
+ if (NeedsCsanOffloadRt && !Args.hasArg(options::OPT_shared))
+ HelperStaticRuntimes.push_back("csan_offload-preinit");
// Collect static runtimes.
if (Args.hasArg(options::OPT_shared)) {
@@ -1777,7 +1781,8 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
if (NeedsUbsanOffloadRt && !SanArgs.needsSharedRt() &&
!SanArgs.needsUbsanRt())
StaticRuntimes.push_back("ubsan_standalone");
- if (NeedsCsanOffloadRt && !SanArgs.needsCsanRt())
+ if (NeedsCsanOffloadRt && !SanArgs.needsSharedRt() &&
+ !SanArgs.needsCsanRt())
StaticRuntimes.push_back("csan");
return;
}
@@ -1839,7 +1844,7 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC,
StaticRuntimes.push_back("ubsan_standalone");
}
}
- if (NeedsCsanRt)
+ if (!SanArgs.needsSharedRt() && NeedsCsanRt)
StaticRuntimes.push_back("csan");
if (SanArgs.needsSafeStackRt()) {
NonWholeStaticRuntimes.push_back("safestack");
diff --git a/clang/test/Driver/fsanitize-concurrency-offload.c b/clang/test/Driver/fsanitize-concurrency-offload.c
index 70aad9e3df9e9..08d0bb97e3f4b 100644
--- a/clang/test/Driver/fsanitize-concurrency-offload.c
+++ b/clang/test/Driver/fsanitize-concurrency-offload.c
@@ -19,6 +19,7 @@
// CHECK-HIP-DAG: "-u" "__csan_offload_init"
// CHECK-HIP-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan.a"
// CHECK-HIP-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan_offload.a"
+// CHECK-HIP-DAG: "--whole-archive" "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan_offload-preinit.a" "--no-whole-archive"
// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
// RUN: -x hip --offload-arch=gfx908 -Xarch_device -fsanitize=concurrency \
@@ -29,6 +30,43 @@
// CHECK-XARCH-DEV-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan.a"
// CHECK-XARCH-DEV-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan_offload.a"
+// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
+// RUN: -x hip --offload-arch=gfx908 -Xarch_device -fsanitize=concurrency \
+// RUN: -fPIC -shared -nogpuinc -nogpulib --rocm-path=%S/Inputs/rocm \
+// RUN: -resource-dir=%S/Inputs/resource_dir_with_amdgpu_csan %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-SHARED-DEV \
+// RUN: --implicit-check-not=csan_offload-preinit
+// CHECK-SHARED-DEV-DAG: "-u" "__csan_offload_init"
+// CHECK-SHARED-DEV-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan_offload.a"
+// CHECK-SHARED-DEV-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan.a"
+
+// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
+// RUN: -x hip --offload-arch=gfx908 -fsanitize=concurrency \
+// RUN: -fPIC -shared -nogpuinc -nogpulib --rocm-path=%S/Inputs/rocm \
+// RUN: -resource-dir=%S/Inputs/resource_dir_with_amdgpu_csan %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-SHARED \
+// RUN: --implicit-check-not=csan_offload-preinit \
+// RUN: --implicit-check-not=libclang_rt.csan.a
+// CHECK-SHARED-DAG: "-u" "__csan_offload_init"
+// CHECK-SHARED-DAG: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan_offload.a"
+
+// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
+// RUN: -x hip --offload-arch=gfx908 -fsanitize=concurrency -shared-libsan \
+// RUN: -nogpuinc -nogpulib --rocm-path=%S/Inputs/rocm \
+// RUN: -resource-dir=%S/Inputs/resource_dir_with_amdgpu_csan %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-SHARED-RT \
+// RUN: --implicit-check-not=csan_offload.a \
+// RUN: --implicit-check-not=libclang_rt.csan.a
+// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
+// RUN: -x hip --offload-arch=gfx908 -Xarch_device -fsanitize=concurrency \
+// RUN: -shared-libsan -nogpuinc -nogpulib --rocm-path=%S/Inputs/rocm \
+// RUN: -resource-dir=%S/Inputs/resource_dir_with_amdgpu_csan %s 2>&1 \
+// RUN: | FileCheck %s --check-prefix=CHECK-SHARED-RT \
+// RUN: --implicit-check-not=csan_offload.a \
+// RUN: --implicit-check-not=libclang_rt.csan.a
+// CHECK-SHARED-RT: "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan.so"
+// CHECK-SHARED-RT: "--whole-archive" "{{[^"]*}}x86_64-unknown-linux-gnu{{/|\\\\}}libclang_rt.csan_offload-preinit.a" "--no-whole-archive"
+
// The device toolchain provides UBSan but not CSan.
// RUN: %clang -no-canonical-prefixes -### --target=x86_64-unknown-linux-gnu \
// RUN: -fopenmp=libomp --offload-arch=gfx908 -fsanitize=concurrency -nogpuinc \
diff --git a/clang/test/Driver/fsanitize.c b/clang/test/Driver/fsanitize.c
index ca3dace2b5cc6..adf2811fac842 100644
--- a/clang/test/Driver/fsanitize.c
+++ b/clang/test/Driver/fsanitize.c
@@ -51,7 +51,9 @@
// RUN: %clang --target=x86_64-linux-gnu -fsanitize=concurrency -shared-libsan %s -### 2>&1 \
// RUN: -resource-dir=%S/Inputs/resource_dir \
// RUN: | FileCheck %s --check-prefix=CHECK-SANC-SHARED
-// CHECK-SANC-SHARED: libclang_rt.csan.a
+// CHECK-SANC-SHARED-NOT: libclang_rt.csan.a
+// CHECK-SANC-SHARED: libclang_rt.csan.so
+// CHECK-SANC-SHARED-NOT: libclang_rt.csan.a
// RUN: not %clang --target=x86_64-linux-android -fsanitize=concurrency %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-SANC-ANDROID
// CHECK-SANC-ANDROID: unsupported option '-fsanitize=concurrency' for target 'x86_64-unknown-linux-android'
More information about the llvm-branch-commits
mailing list