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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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' _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
