https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/225781
>From 2aaf32a57db73dd3193c7283269b45794df06068 Mon Sep 17 00:00:00 2001 From: Joseph Huber <[email protected]> Date: Mon, 21 Sep 2026 17:34:54 -0500 Subject: [PATCH] [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 | 38 +++- 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, 374 insertions(+), 15 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 00000000000000..d58cdb959d1d0a --- /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 bb2ba828637af4..e7eaced355d3c9 100644 --- a/clang/docs/index.md +++ b/clang/docs/index.md @@ -36,6 +36,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 da854316250265..8120c5842919b4 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 62f154bae3ffd3..9d61165e940094 100644 --- a/clang/include/clang/Driver/SanitizerArgs.h +++ b/clang/include/clang/Driver/SanitizerArgs.h @@ -106,6 +106,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 8103b72cf4e3a2..8634cbd930d42f 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/DataFlowSanitizer.h" #include "llvm/Transforms/Instrumentation/GCOVProfiler.h" #include "llvm/Transforms/Instrumentation/HWAddressSanitizer.h" @@ -703,6 +704,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 a54809c48037e8..f58492f1f41e02 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 745423945ca838..e7e4e269ef526e 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -688,6 +688,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. @@ -824,6 +828,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)) @@ -851,9 +857,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 778cde8285aaf1..418c403f3ecc72 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 d6dacb4ccba839..b66e5ac0ca8ae8 100644 --- a/clang/lib/Driver/ToolChains/AMDGPU.cpp +++ b/clang/lib/Driver/ToolChains/AMDGPU.cpp @@ -1391,6 +1391,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 6636a5fd6e6551..274222cbed1f66 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -9809,7 +9809,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 445eb4ccfbfa72..0747540a23ff92 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()) { @@ -1745,16 +1759,24 @@ collectSanitizerRuntimes(Compilation &C, const ToolChain &TC, HelperStaticRuntimes.push_back("asan_static"); // Offloading images can live in DSOs, the host interceptors must follow. - if (NeedsOffloadRt) { + if (NeedsUbsanOffloadRt) { NonWholeStaticRuntimes.push_back("ubsan_offload"); RequiredSymbols.push_back("__ubsan_offload_init"); } + 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; } @@ -1813,6 +1835,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 e295b2516da16f..43b48903fc0533 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 784ff9951a25ad..264412ea3f714b 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 00000000000000..aaae91ca71da4a --- /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 97dbee7c15bc84..55579be3ae1e24 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 d903a40de050f4..cec6aa4ffee2ec 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 _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
