[llvm-branch-commits] [llvm] [LLVM] Add a ConcurrencySanitizer LLVM pass (PR #225780)

Joseph Huber via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Sep 23 07:01:48 PDT 2026


https://github.com/jhuber6 created https://github.com/llvm/llvm-project/pull/225780

Summary:
Add an LLVM ConcurrencySanitizer pass to instrument memory and atomic
operations for a sampling race detector. Fundamentally the pass iterates
through every candidate memory operation and adds a check above it.
Unlike TSAN, we do not replace any operations, which heavily simplifies
the instrumentation.

We do not re-use the TSAN instrumentaiton because the appraoches are
different enough and the passes simple enough that the extra code is not
a large concern. More differences will be added in the future, so
keeping them separate is easlier.

---

<sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>

>From 33f9fd2a2939e83f15de7dda4ec224049121a7ed Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Mon, 21 Sep 2026 17:34:50 -0500
Subject: [PATCH] [LLVM] Add a ConcurrencySanitizer LLVM pass

Summary:
Add an LLVM ConcurrencySanitizer pass to instrument memory and atomic
operations for a sampling race detector. Fundamentally the pass iterates
through every candidate memory operation and adds a check above it.
Unlike TSAN, we do not replace any operations, which heavily simplifies
the instrumentation.

We do not re-use the TSAN instrumentaiton because the appraoches are
different enough and the passes simple enough that the extra code is not
a large concern. More differences will be added in the future, so
keeping them separate is easlier.
---
 .../Instrumentation/ConcurrencySanitizer.h    |  29 +
 llvm/lib/Passes/PassBuilder.cpp               |   1 +
 llvm/lib/Passes/PassRegistry.def              |   2 +
 .../Transforms/Instrumentation/CMakeLists.txt |   1 +
 .../Instrumentation/ConcurrencySanitizer.cpp  | 614 ++++++++++++++++++
 .../ConcurrencySanitizer/address_space.ll     | 106 +++
 .../ConcurrencySanitizer/atomic.ll            |  86 +++
 .../ConcurrencySanitizer/basic.ll             |  49 ++
 .../ConcurrencySanitizer/callbacks.ll         |  72 ++
 .../ConcurrencySanitizer/function_control.ll  |  59 ++
 .../ConcurrencySanitizer/memintrinsics.ll     |  46 ++
 .../ConcurrencySanitizer/module.ll            |  12 +
 .../ConcurrencySanitizer/no_sanitize.ll       |  57 ++
 .../ConcurrencySanitizer/selection.ll         | 131 ++++
 .../ConcurrencySanitizer/shared_alloca.ll     |  23 +
 .../ConcurrencySanitizer/unusual_sizes.ll     |  33 +
 16 files changed, 1321 insertions(+)
 create mode 100644 llvm/include/llvm/Transforms/Instrumentation/ConcurrencySanitizer.h
 create mode 100644 llvm/lib/Transforms/Instrumentation/ConcurrencySanitizer.cpp
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/address_space.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/atomic.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/basic.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/callbacks.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/function_control.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/memintrinsics.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/module.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/no_sanitize.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/selection.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/shared_alloca.ll
 create mode 100644 llvm/test/Instrumentation/ConcurrencySanitizer/unusual_sizes.ll

diff --git a/llvm/include/llvm/Transforms/Instrumentation/ConcurrencySanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/ConcurrencySanitizer.h
new file mode 100644
index 00000000000000..9bf3b8bde6a13b
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Instrumentation/ConcurrencySanitizer.h
@@ -0,0 +1,29 @@
+//===- ConcurrencySanitizer.h - ConcurrencySanitizer ------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_INSTRUMENTATION_CONCURRENCYSANITIZER_H
+#define LLVM_TRANSFORMS_INSTRUMENTATION_CONCURRENCYSANITIZER_H
+
+#include "llvm/IR/PassManager.h"
+#include "llvm/Support/Compiler.h"
+
+namespace llvm {
+
+struct ConcurrencySanitizerPass
+    : public RequiredPassInfoMixin<ConcurrencySanitizerPass> {
+  LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
+};
+
+struct ModuleConcurrencySanitizerPass
+    : public RequiredPassInfoMixin<ModuleConcurrencySanitizerPass> {
+  LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+};
+
+} // namespace llvm
+
+#endif // LLVM_TRANSFORMS_INSTRUMENTATION_CONCURRENCYSANITIZER_H
diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp
index 858c76706427e8..d2c25e85beeb42 100644
--- a/llvm/lib/Passes/PassBuilder.cpp
+++ b/llvm/lib/Passes/PassBuilder.cpp
@@ -274,6 +274,7 @@
 #include "llvm/Transforms/Instrumentation/AllocToken.h"
 #include "llvm/Transforms/Instrumentation/BoundsChecking.h"
 #include "llvm/Transforms/Instrumentation/CGProfile.h"
+#include "llvm/Transforms/Instrumentation/ConcurrencySanitizer.h"
 #include "llvm/Transforms/Instrumentation/ControlHeightReduction.h"
 #include "llvm/Transforms/Instrumentation/CopyProf.h"
 #include "llvm/Transforms/Instrumentation/DataFlowSanitizer.h"
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index 33c9e19988d7ab..3913d0aaf75d49 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -67,6 +67,7 @@ MODULE_PASS("coro-cleanup", CoroCleanupPass())
 MODULE_PASS("coro-early", CoroEarlyPass())
 MODULE_PASS("lower-comment-string", LowerCommentStringPass())
 MODULE_PASS("cross-dso-cfi", CrossDSOCFIPass())
+MODULE_PASS("csan-module", ModuleConcurrencySanitizerPass())
 MODULE_PASS("ctx-instr-gen",
             PGOInstrumentationGen(PGOInstrumentationType::CTXPROF))
 MODULE_PASS("ctx-prof-flatten",
@@ -429,6 +430,7 @@ FUNCTION_PASS("copyprof-stores", CopyProfStoresPass())
 FUNCTION_PASS("coro-elide", CoroElidePass())
 FUNCTION_PASS("correlated-propagation", CorrelatedValuePropagationPass())
 FUNCTION_PASS("count-visits", CountVisitsPass())
+FUNCTION_PASS("csan", ConcurrencySanitizerPass())
 FUNCTION_PASS("dce", DCEPass())
 FUNCTION_PASS("declare-to-assign", llvm::AssignmentTrackingPass())
 FUNCTION_PASS("dfa-jump-threading", DFAJumpThreadingPass())
diff --git a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
index 7d2e658acb5c15..4d08d0fa42184b 100644
--- a/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
+++ b/llvm/lib/Transforms/Instrumentation/CMakeLists.txt
@@ -25,6 +25,7 @@ add_llvm_component_library(LLVMInstrumentation
   SanitizerBinaryMetadata.cpp
   ValueProfileCollector.cpp
   ThreadSanitizer.cpp
+  ConcurrencySanitizer.cpp
   TypeSanitizer.cpp
   HWAddressSanitizer.cpp
   RealtimeSanitizer.cpp
diff --git a/llvm/lib/Transforms/Instrumentation/ConcurrencySanitizer.cpp b/llvm/lib/Transforms/Instrumentation/ConcurrencySanitizer.cpp
new file mode 100644
index 00000000000000..0a14d67f9f5bc1
--- /dev/null
+++ b/llvm/lib/Transforms/Instrumentation/ConcurrencySanitizer.cpp
@@ -0,0 +1,614 @@
+//===- ConcurrencySanitizer.cpp - watchpoint race detector ----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements instrumentation for ConcurrencySanitizer, a sampling
+// data-race detector. Memory accesses are preceded by runtime probes while the
+// original operations remain unchanged.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Instrumentation/ConcurrencySanitizer.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/bit.h"
+#include "llvm/Analysis/CaptureTracking.h"
+#include "llvm/Analysis/TargetLibraryInfo.h"
+#include "llvm/Analysis/ValueTracking.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/Intrinsics.h"
+#include "llvm/IR/Module.h"
+#include "llvm/ProfileData/InstrProf.h"
+#include "llvm/Support/AMDGPUAddrSpace.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/NVPTXAddrSpace.h"
+#include "llvm/TargetParser/Triple.h"
+#include "llvm/Transforms/Utils/EscapeEnumerator.h"
+#include "llvm/Transforms/Utils/Instrumentation.h"
+#include "llvm/Transforms/Utils/Local.h"
+#include "llvm/Transforms/Utils/ModuleUtils.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "csan"
+
+static cl::opt<bool> ClInstrumentMemoryAccesses(
+    "csan-instrument-memory-accesses", cl::init(true),
+    cl::desc("Instrument memory accesses"), cl::Hidden);
+static cl::opt<bool>
+    ClInstrumentFuncEntryExit("csan-instrument-func-entry-exit", cl::init(true),
+                              cl::desc("Instrument function entry and exit"),
+                              cl::Hidden);
+static cl::opt<bool> ClHandleCxxExceptions(
+    "csan-handle-cxx-exceptions", cl::init(true),
+    cl::desc("Handle C++ exceptions (insert cleanup blocks for unwinding)"),
+    cl::Hidden);
+static cl::opt<bool> ClInstrumentAtomics("csan-instrument-atomics",
+                                         cl::init(true),
+                                         cl::desc("Instrument atomics"),
+                                         cl::Hidden);
+static cl::opt<bool> ClInstrumentMemIntrinsics(
+    "csan-instrument-memintrinsics", cl::init(true),
+    cl::desc("Instrument memintrinsics (memset/memcpy/memmove)"), cl::Hidden);
+static cl::opt<bool> ClDistinguishVolatile(
+    "csan-distinguish-volatile", cl::init(false),
+    cl::desc("Emit special instrumentation for accesses to volatiles"),
+    cl::Hidden);
+STATISTIC(NumInstrumentedReads, "Number of instrumented reads");
+STATISTIC(NumInstrumentedWrites, "Number of instrumented writes");
+STATISTIC(NumOmittedReadsBeforeWrite,
+          "Number of reads ignored due to following writes");
+
+static constexpr char kCsanModuleCtorName[] = "csan.module_ctor";
+static constexpr char kCsanInitName[] = "__csan_init";
+
+namespace {
+
+/// Must match CSAN_ACCESS_* in compiler-rt/lib/csan/csan_defs.h.
+enum AccessFlags : unsigned {
+  AF_None = 0,
+  AF_Atomic = 1u << 0,
+  AF_Compound = 1u << 1,
+};
+
+static bool isAtomicMemoryAccess(const Instruction *I) {
+  auto SSID = getAtomicSyncScopeID(I);
+  if (!SSID)
+    return false;
+  if (isa<LoadInst>(I) || isa<StoreInst>(I))
+    return *SSID != SyncScope::SingleThread;
+  return true;
+}
+
+static Value *getCallbackAddress(IRBuilderBase &IRB, Value *Addr) {
+  return IRB.CreateAddrSpaceCast(Addr, IRB.getPtrTy());
+}
+
+static ConstantInt *createOrdering(IRBuilderBase &IRB, AtomicOrdering Ord) {
+  uint32_t Value = 0;
+  switch (Ord) {
+  case AtomicOrdering::NotAtomic:
+    llvm_unreachable("unexpected atomic ordering");
+  case AtomicOrdering::Unordered:
+  case AtomicOrdering::Monotonic:
+    Value = 0;
+    break;
+  case AtomicOrdering::Acquire:
+    Value = 2;
+    break;
+  case AtomicOrdering::Release:
+    Value = 3;
+    break;
+  case AtomicOrdering::AcquireRelease:
+    Value = 4;
+    break;
+  case AtomicOrdering::SequentiallyConsistent:
+    Value = 5;
+    break;
+  }
+  return IRB.getInt32(Value);
+}
+
+static int getAccessSizeIndex(Type *Ty, const DataLayout &DL) {
+  assert(Ty->isSized());
+  if (Ty->isScalableTy())
+    return -1;
+  uint32_t TypeSize = DL.getTypeStoreSizeInBits(Ty);
+  if (TypeSize != 8 && TypeSize != 16 && TypeSize != 32 && TypeSize != 64 &&
+      TypeSize != 128)
+    return -1;
+  unsigned Idx = llvm::countr_zero(TypeSize / 8);
+  return static_cast<int>(Idx);
+}
+
+static bool addressSpaceMayRace(const Triple &T, unsigned AS) {
+  if (T.isAMDGPU())
+    // GDS and buffer fat pointers cannot form a generic watchpoint key.
+    return AS == AMDGPUAS::FLAT_ADDRESS || AS == AMDGPUAS::GLOBAL_ADDRESS ||
+           AS == AMDGPUAS::LOCAL_ADDRESS;
+  if (T.isNVPTX())
+    return AS == NVPTXAS::ADDRESS_SPACE_GENERIC ||
+           AS == NVPTXAS::ADDRESS_SPACE_GLOBAL ||
+           AS == NVPTXAS::ADDRESS_SPACE_SHARED ||
+           AS == NVPTXAS::ADDRESS_SPACE_SHARED_CLUSTER;
+  if (T.isSPIRV())
+    // FIXME: No exposed address spaces for SPIR-V.
+    return false;
+  return AS == 0;
+}
+
+struct ConcurrencySanitizer {
+  bool sanitizeFunction(Function &F, const TargetLibraryInfo &TLI);
+
+private:
+  struct MemoryAccessLists {
+    SmallVector<Instruction *, 8> LoadsAndStores;
+    SmallVector<Instruction *, 8> AtomicAccesses;
+    SmallVector<MemIntrinsic *, 8> MemIntrinCalls;
+    bool HasCalls = false;
+  };
+
+  void initialize(Module &M, const TargetLibraryInfo &TLI);
+  void collectMemoryAccesses(Function &F, MemoryAccessLists &Out);
+  bool instrumentLoadOrStore(Instruction *I, const DataLayout &DL);
+  bool instrumentAtomic(Instruction *I, const DataLayout &DL);
+  bool instrumentMemIntrinsic(MemIntrinsic *M);
+  bool insertAccessProbe(Instruction *I, Value *Addr, Type *AccessTy,
+                         const DataLayout &DL, bool IsWrite, bool IsCompound,
+                         bool IsAtomic);
+  bool shouldInstrumentAddress(Value *Addr) const;
+  bool shouldInstrumentAccess(Instruction *I) const;
+  void insertFuncEntryExit(Function &F);
+  void insertRuntimeIgnores(Function &F);
+
+  Module *Mod = nullptr;
+  Type *IntptrTy = nullptr;
+  IntegerType *FlagsTy = nullptr;
+
+  // Accesses sizes are powers of two: 1, 2, 4, 8, 16.
+  static const size_t kNumAccessSizes = 5;
+  // void __csan_readN(ptr, i32);
+  FunctionCallee CsanRead[kNumAccessSizes];
+  // void __csan_writeN(ptr, i32);
+  FunctionCallee CsanWrite[kNumAccessSizes];
+  // void __csan_unaligned_readN(ptr, i32);
+  FunctionCallee CsanUnalignedRead[kNumAccessSizes];
+  // void __csan_unaligned_writeN(ptr, i32);
+  FunctionCallee CsanUnalignedWrite[kNumAccessSizes];
+  // void __csan_volatile_readN(ptr, i32);
+  FunctionCallee CsanVolatileRead[kNumAccessSizes];
+  // void __csan_volatile_writeN(ptr, i32);
+  FunctionCallee CsanVolatileWrite[kNumAccessSizes];
+  // void __csan_unaligned_volatile_readN(ptr, i32);
+  FunctionCallee CsanUnalignedVolatileRead[kNumAccessSizes];
+  // void __csan_unaligned_volatile_writeN(ptr, i32);
+  FunctionCallee CsanUnalignedVolatileWrite[kNumAccessSizes];
+  // void __csan_read_writeN(ptr, i32);
+  FunctionCallee CsanCompoundRW[kNumAccessSizes];
+  // void __csan_unaligned_read_writeN(ptr, i32);
+  FunctionCallee CsanUnalignedCompoundRW[kNumAccessSizes];
+  // void __csan_func_entry(ptr);
+  FunctionCallee CsanFuncEntry;
+  // void __csan_func_exit();
+  FunctionCallee CsanFuncExit;
+  // void __csan_ignore_thread_begin();
+  FunctionCallee CsanIgnoreBegin;
+  // void __csan_ignore_thread_end();
+  FunctionCallee CsanIgnoreEnd;
+  // void __csan_read_range(ptr, intptr_t, i32);
+  FunctionCallee CsanReadRange;
+  // void __csan_write_range(ptr, intptr_t, i32);
+  FunctionCallee CsanWriteRange;
+  // void __csan_atomic_thread_fence(i32);
+  FunctionCallee CsanAtomicThreadFence;
+  // void __csan_atomic_signal_fence(i32);
+  FunctionCallee CsanAtomicSignalFence;
+};
+
+void insertModuleCtor(Module &M) {
+  getOrCreateSanitizerCtorAndInitFunctions(
+      M, kCsanModuleCtorName, kCsanInitName, /*InitArgTypes=*/{},
+      /*InitArgs=*/{},
+      [&](Function *Ctor, FunctionCallee) { appendToGlobalCtors(M, Ctor, 0); });
+}
+
+} // namespace
+
+PreservedAnalyses ConcurrencySanitizerPass::run(Function &F,
+                                                FunctionAnalysisManager &FAM) {
+  ConcurrencySanitizer CSan;
+  if (CSan.sanitizeFunction(F, FAM.getResult<TargetLibraryAnalysis>(F)))
+    return PreservedAnalyses::none();
+  return PreservedAnalyses::all();
+}
+
+PreservedAnalyses ModuleConcurrencySanitizerPass::run(Module &M,
+                                                      ModuleAnalysisManager &) {
+  if (checkIfAlreadyInstrumented(M, "nosanitize_concurrency"))
+    return PreservedAnalyses::all();
+  insertModuleCtor(M);
+  return PreservedAnalyses::none();
+}
+
+void ConcurrencySanitizer::initialize(Module &M, const TargetLibraryInfo &TLI) {
+  LLVMContext &Ctx = M.getContext();
+  Mod = &M;
+  IntptrTy = M.getDataLayout().getIntPtrType(Ctx);
+  FlagsTy = Type::getInt32Ty(Ctx);
+
+  AttributeList Attr = AttributeList().addFnAttribute(Ctx, Attribute::NoUnwind);
+  IRBuilder<> IRB(Ctx);
+  Type *VoidTy = IRB.getVoidTy();
+  Type *PtrTy = IRB.getPtrTy();
+  CsanFuncEntry =
+      M.getOrInsertFunction("__csan_func_entry", Attr, VoidTy, PtrTy);
+  CsanFuncExit = M.getOrInsertFunction("__csan_func_exit", Attr, VoidTy);
+  CsanIgnoreBegin =
+      M.getOrInsertFunction("__csan_ignore_thread_begin", Attr, VoidTy);
+  CsanIgnoreEnd =
+      M.getOrInsertFunction("__csan_ignore_thread_end", Attr, VoidTy);
+  for (unsigned I = 0; I < kNumAccessSizes; ++I) {
+    std::string ByteSize = utostr(1U << I);
+    auto AccessFn = [&](const Twine &Name) {
+      return M.getOrInsertFunction(("__csan_" + Name).str(), Attr, VoidTy,
+                                   PtrTy, FlagsTy);
+    };
+    CsanRead[I] = AccessFn("read" + ByteSize);
+    CsanWrite[I] = AccessFn("write" + ByteSize);
+    CsanUnalignedRead[I] = AccessFn("unaligned_read" + ByteSize);
+    CsanUnalignedWrite[I] = AccessFn("unaligned_write" + ByteSize);
+    CsanVolatileRead[I] = AccessFn("volatile_read" + ByteSize);
+    CsanVolatileWrite[I] = AccessFn("volatile_write" + ByteSize);
+    CsanUnalignedVolatileRead[I] =
+        AccessFn("unaligned_volatile_read" + ByteSize);
+    CsanUnalignedVolatileWrite[I] =
+        AccessFn("unaligned_volatile_write" + ByteSize);
+    CsanCompoundRW[I] = AccessFn("read_write" + ByteSize);
+    CsanUnalignedCompoundRW[I] = AccessFn("unaligned_read_write" + ByteSize);
+  }
+  IntegerType *OrdTy = IRB.getInt32Ty();
+  CsanReadRange = M.getOrInsertFunction("__csan_read_range", Attr, VoidTy,
+                                        PtrTy, IntptrTy, FlagsTy);
+  CsanWriteRange = M.getOrInsertFunction("__csan_write_range", Attr, VoidTy,
+                                         PtrTy, IntptrTy, FlagsTy);
+  CsanAtomicThreadFence = M.getOrInsertFunction(
+      "__csan_atomic_thread_fence",
+      TLI.getAttrList(&Ctx, {0}, /*Signed=*/true, /*Ret=*/false, Attr), VoidTy,
+      OrdTy);
+  CsanAtomicSignalFence = M.getOrInsertFunction(
+      "__csan_atomic_signal_fence",
+      TLI.getAttrList(&Ctx, {0}, /*Signed=*/true, /*Ret=*/false, Attr), VoidTy,
+      OrdTy);
+}
+
+bool ConcurrencySanitizer::sanitizeFunction(Function &F,
+                                            const TargetLibraryInfo &TLI) {
+  // This is required to prevent instrumenting call to __csan_init from within
+  // the module constructor.
+  if (F.getName() == kCsanModuleCtorName)
+    return false;
+  // Naked functions can not have prologue/epilogue
+  // (__csan_func_entry/__csan_func_exit) generated, so don't instrument them at
+  // all.
+  if (F.hasFnAttribute(Attribute::Naked))
+    return false;
+
+  // __attribute__(disable_sanitizer_instrumentation) prevents all kinds of
+  // instrumentation.
+  if (F.hasFnAttribute(Attribute::DisableSanitizerInstrumentation))
+    return false;
+
+  Mod = F.getParent();
+  MemoryAccessLists Acc;
+  collectMemoryAccesses(F, Acc);
+
+  const bool SuppressChecking =
+      F.hasFnAttribute("sanitize_concurrency_no_checking_at_run_time");
+  const bool SanitizeFunction =
+      F.hasFnAttribute(Attribute::SanitizeConcurrency) && !SuppressChecking;
+  const bool MayInstrument =
+      SanitizeFunction &&
+      ((ClInstrumentMemoryAccesses && !Acc.LoadsAndStores.empty()) ||
+       (ClInstrumentAtomics && !Acc.AtomicAccesses.empty()) ||
+       (ClInstrumentMemIntrinsics && !Acc.MemIntrinCalls.empty()));
+  const bool NeedsRuntimeIgnores = SuppressChecking && Acc.HasCalls;
+  const bool NeedsFuncEntryExit = ClInstrumentFuncEntryExit && Acc.HasCalls;
+  if (!MayInstrument && !NeedsRuntimeIgnores && !NeedsFuncEntryExit)
+    return false;
+
+  initialize(*Mod, TLI);
+  bool Res = false;
+  const DataLayout &DL = F.getDataLayout();
+
+  if (ClInstrumentMemoryAccesses && SanitizeFunction)
+    for (Instruction *I : Acc.LoadsAndStores)
+      Res |= instrumentLoadOrStore(I, DL);
+
+  if (ClInstrumentAtomics && SanitizeFunction)
+    for (Instruction *I : Acc.AtomicAccesses)
+      Res |= instrumentAtomic(I, DL);
+
+  if (ClInstrumentMemIntrinsics && SanitizeFunction)
+    for (MemIntrinsic *MI : Acc.MemIntrinCalls)
+      Res |= instrumentMemIntrinsic(MI);
+
+  if (NeedsRuntimeIgnores) {
+    insertRuntimeIgnores(F);
+    Res = true;
+  }
+
+  if ((Res || Acc.HasCalls) && ClInstrumentFuncEntryExit) {
+    insertFuncEntryExit(F);
+    Res = true;
+  }
+  // Callback declarations may have changed the module.
+  return true;
+}
+
+bool ConcurrencySanitizer::shouldInstrumentAddress(Value *Addr) const {
+  Value *BaseAddr = Addr->stripInBoundsOffsets();
+  if (auto *GV = dyn_cast<GlobalVariable>(BaseAddr)) {
+    if (GV->hasSection()) {
+      StringRef SectionName = GV->getSection();
+      auto OF = Mod->getTargetTriple().getObjectFormat();
+      if (SectionName.ends_with(
+              getInstrProfSectionName(IPSK_cnts, OF, /*AddSegmentInfo=*/false)))
+        return false;
+    }
+  }
+
+  Type *PtrTy = cast<PointerType>(Addr->getType()->getScalarType());
+  unsigned AS = PtrTy->getPointerAddressSpace();
+  if (Mod->getDataLayout().getPointerSizeInBits(AS) > 64)
+    return false;
+  return addressSpaceMayRace(Mod->getTargetTriple(), AS);
+}
+
+bool ConcurrencySanitizer::shouldInstrumentAccess(Instruction *I) const {
+  const bool IsWrite = isa<StoreInst>(I);
+  Value *Addr = getLoadStorePointerOperand(I);
+  if (!shouldInstrumentAddress(Addr))
+    return false;
+
+  if (!IsWrite)
+    if (auto *GV = dyn_cast<GlobalVariable>(getUnderlyingObject(Addr)))
+      if (GV->isConstant())
+        return false;
+
+  const AllocaInst *AI = findAllocaForValue(Addr);
+  if (!AI || PointerMayBeCaptured(AI, /*ReturnCaptures=*/true))
+    return true;
+
+  const Triple &T = AI->getModule()->getTargetTriple();
+  return T.isGPU() && addressSpaceMayRace(T, AI->getAddressSpace());
+}
+
+void ConcurrencySanitizer::collectMemoryAccesses(Function &F,
+                                                 MemoryAccessLists &Out) {
+  SmallVector<Instruction *, 8> LocalLoadsAndStores;
+  auto FlushLocalAccesses = [&] {
+    DenseSet<Value *> WriteTargets;
+    for (Instruction *Inst : reverse(LocalLoadsAndStores)) {
+      const bool IsWrite = isa<StoreInst>(Inst);
+      Value *Addr = getLoadStorePointerOperand(Inst);
+      if (!IsWrite && WriteTargets.contains(Addr)) {
+        ++NumOmittedReadsBeforeWrite;
+        continue;
+      }
+
+      Out.LoadsAndStores.push_back(Inst);
+      if (IsWrite)
+        WriteTargets.insert(Addr);
+    }
+    LocalLoadsAndStores.clear();
+  };
+
+  for (BasicBlock &BB : F) {
+    for (Instruction &Inst : BB) {
+      // Skip instructions inserted by another instrumentation.
+      if (Inst.hasMetadata(LLVMContext::MD_nosanitize))
+        continue;
+      if (isAtomicMemoryAccess(&Inst))
+        Out.AtomicAccesses.push_back(&Inst);
+      else if ((isa<LoadInst>(Inst) || isa<StoreInst>(Inst)) &&
+               shouldInstrumentAccess(&Inst))
+        LocalLoadsAndStores.push_back(&Inst);
+      else if (isa<CallInst>(Inst) || isa<InvokeInst>(Inst)) {
+        FlushLocalAccesses();
+        if (auto *MI = dyn_cast<MemIntrinsic>(&Inst))
+          Out.MemIntrinCalls.push_back(MI);
+        Out.HasCalls = true;
+      }
+    }
+    FlushLocalAccesses();
+  }
+}
+
+bool ConcurrencySanitizer::instrumentLoadOrStore(Instruction *I,
+                                                 const DataLayout &DL) {
+  const bool IsWrite = isa<StoreInst>(I);
+  Value *Addr = getLoadStorePointerOperand(I);
+  if (!insertAccessProbe(I, Addr, getLoadStoreType(I), DL, IsWrite,
+                         /*IsCompound=*/false, /*IsAtomic=*/false))
+    return false;
+  if (IsWrite)
+    ++NumInstrumentedWrites;
+  else
+    ++NumInstrumentedReads;
+  return true;
+}
+
+bool ConcurrencySanitizer::instrumentAtomic(Instruction *I,
+                                            const DataLayout &DL) {
+  if (auto *FI = dyn_cast<FenceInst>(I)) {
+    InstrumentationIRBuilder IRB(I);
+    FunctionCallee Fn = FI->getSyncScopeID() == SyncScope::SingleThread
+                            ? CsanAtomicSignalFence
+                            : CsanAtomicThreadFence;
+    IRB.CreateCall(Fn, createOrdering(IRB, FI->getOrdering()));
+    return true;
+  }
+
+  Value *Addr = nullptr;
+  Type *AccessTy = nullptr;
+  bool IsWrite = true;
+  bool IsCompound = false;
+  if (auto *LI = dyn_cast<LoadInst>(I)) {
+    Addr = LI->getPointerOperand();
+    AccessTy = LI->getType();
+    IsWrite = false;
+  } else if (auto *SI = dyn_cast<StoreInst>(I)) {
+    Addr = SI->getPointerOperand();
+    AccessTy = SI->getValueOperand()->getType();
+  } else if (auto *RMW = dyn_cast<AtomicRMWInst>(I)) {
+    Addr = RMW->getPointerOperand();
+    AccessTy = RMW->getValOperand()->getType();
+    IsCompound = true;
+  } else if (auto *CAS = dyn_cast<AtomicCmpXchgInst>(I)) {
+    Addr = CAS->getPointerOperand();
+    AccessTy = CAS->getNewValOperand()->getType();
+    IsCompound = true;
+  } else {
+    return false;
+  }
+
+  if (!shouldInstrumentAddress(Addr))
+    return false;
+  if (!insertAccessProbe(I, Addr, AccessTy, DL, IsWrite, IsCompound,
+                         /*IsAtomic=*/true))
+    return false;
+  if (IsCompound || IsWrite)
+    ++NumInstrumentedWrites;
+  if (IsCompound || !IsWrite)
+    ++NumInstrumentedReads;
+  return true;
+}
+
+bool ConcurrencySanitizer::instrumentMemIntrinsic(MemIntrinsic *M) {
+  if (auto *MS = dyn_cast<MemSetInst>(M)) {
+    if (!shouldInstrumentAddress(MS->getRawDest()))
+      return false;
+    InstrumentationIRBuilder IRB(M);
+    Value *Len = IRB.CreateIntCast(M->getLength(), IntptrTy, false);
+    IRB.CreateCall(CsanWriteRange, {getCallbackAddress(IRB, MS->getRawDest()),
+                                    Len, ConstantInt::get(FlagsTy, AF_None)});
+    ++NumInstrumentedWrites;
+    return true;
+  }
+
+  auto *MT = cast<MemTransferInst>(M);
+  bool InstrumentRead = shouldInstrumentAddress(MT->getRawSource());
+  bool InstrumentWrite = shouldInstrumentAddress(MT->getRawDest());
+  if (!InstrumentRead && !InstrumentWrite)
+    return false;
+
+  InstrumentationIRBuilder IRB(M);
+  Value *Len = IRB.CreateIntCast(M->getLength(), IntptrTy, false);
+  if (InstrumentRead) {
+    IRB.CreateCall(CsanReadRange, {getCallbackAddress(IRB, MT->getRawSource()),
+                                   Len, ConstantInt::get(FlagsTy, AF_None)});
+    ++NumInstrumentedReads;
+  }
+  if (InstrumentWrite) {
+    IRB.CreateCall(CsanWriteRange, {getCallbackAddress(IRB, MT->getRawDest()),
+                                    Len, ConstantInt::get(FlagsTy, AF_None)});
+    ++NumInstrumentedWrites;
+  }
+  return true;
+}
+
+bool ConcurrencySanitizer::insertAccessProbe(Instruction *I, Value *Addr,
+                                             Type *AccessTy,
+                                             const DataLayout &DL, bool IsWrite,
+                                             bool IsCompound, bool IsAtomic) {
+  if (Addr->isSwiftError())
+    return false;
+  unsigned Flags =
+      (IsAtomic ? AF_Atomic : AF_None) | (IsCompound ? AF_Compound : AF_None);
+  int Idx = getAccessSizeIndex(AccessTy, DL);
+  if (Idx < 0) {
+    if (IsCompound)
+      return false;
+    InstrumentationIRBuilder IRB(I);
+    Value *Len = IRB.CreateTypeSize(IntptrTy, DL.getTypeStoreSize(AccessTy));
+    IRB.CreateCall(
+        IsWrite ? CsanWriteRange : CsanReadRange,
+        {getCallbackAddress(IRB, Addr), Len, ConstantInt::get(FlagsTy, Flags)});
+    return true;
+  }
+
+  Align Alignment = Align(1);
+  bool IsVolatile = false;
+  if (auto *LI = dyn_cast<LoadInst>(I)) {
+    Alignment = LI->getAlign();
+    IsVolatile = LI->isVolatile();
+  } else if (auto *SI = dyn_cast<StoreInst>(I)) {
+    Alignment = SI->getAlign();
+    IsVolatile = SI->isVolatile();
+  } else if (auto *RMW = dyn_cast<AtomicRMWInst>(I)) {
+    Alignment = RMW->getAlign();
+    IsVolatile = RMW->isVolatile();
+  } else if (auto *CAS = dyn_cast<AtomicCmpXchgInst>(I)) {
+    Alignment = CAS->getAlign();
+    IsVolatile = CAS->isVolatile();
+  }
+  IsVolatile &= ClDistinguishVolatile;
+
+  uint32_t TypeSize = DL.getTypeStoreSizeInBits(AccessTy);
+  bool Unaligned =
+      Alignment < Align(8) && Alignment.value() % (TypeSize / 8) != 0;
+  FunctionCallee Callback;
+  if (IsCompound)
+    Callback = Unaligned ? CsanUnalignedCompoundRW[Idx] : CsanCompoundRW[Idx];
+  else if (IsVolatile && Unaligned)
+    Callback = IsWrite ? CsanUnalignedVolatileWrite[Idx]
+                       : CsanUnalignedVolatileRead[Idx];
+  else if (IsVolatile)
+    Callback = IsWrite ? CsanVolatileWrite[Idx] : CsanVolatileRead[Idx];
+  else if (Unaligned)
+    Callback = IsWrite ? CsanUnalignedWrite[Idx] : CsanUnalignedRead[Idx];
+  else
+    Callback = IsWrite ? CsanWrite[Idx] : CsanRead[Idx];
+
+  InstrumentationIRBuilder IRB(I);
+  IRB.CreateCall(Callback, {getCallbackAddress(IRB, Addr),
+                            ConstantInt::get(FlagsTy, Flags)});
+  return true;
+}
+
+void ConcurrencySanitizer::insertFuncEntryExit(Function &F) {
+  const DataLayout &DL = F.getDataLayout();
+  InstrumentationIRBuilder IRB(&F.getEntryBlock(),
+                               F.getEntryBlock().getFirstNonPHIIt());
+  Type *ProgramAsPtrTy =
+      PointerType::get(F.getContext(), DL.getProgramAddressSpace());
+  Value *ReturnAddress = IRB.CreateIntrinsic(Intrinsic::returnaddress,
+                                             {ProgramAsPtrTy}, IRB.getInt32(0));
+  IRB.CreateCall(CsanFuncEntry, ReturnAddress);
+
+  EscapeEnumerator EE(F, "csan_cleanup", ClHandleCxxExceptions);
+  while (IRBuilder<> *AtExit = EE.Next()) {
+    InstrumentationIRBuilder::ensureDebugInfo(*AtExit, F);
+    AtExit->CreateCall(CsanFuncExit);
+  }
+}
+
+void ConcurrencySanitizer::insertRuntimeIgnores(Function &F) {
+  InstrumentationIRBuilder IRB(&F.getEntryBlock(),
+                               F.getEntryBlock().getFirstNonPHIIt());
+  IRB.CreateCall(CsanIgnoreBegin);
+  EscapeEnumerator EE(F, "csan_ignore_cleanup", ClHandleCxxExceptions);
+  while (IRBuilder<> *AtExit = EE.Next()) {
+    InstrumentationIRBuilder::ensureDebugInfo(*AtExit, F);
+    AtExit->CreateCall(CsanIgnoreEnd);
+  }
+}
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/address_space.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/address_space.ll
new file mode 100644
index 00000000000000..c0190cf74b5c60
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/address_space.ll
@@ -0,0 +1,106 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes='function(csan)' -S -mtriple=amdgcn-amd-amdhsa | FileCheck %s --check-prefixes=CHECK
+
+define void @load_as1(ptr addrspace(1) %p) sanitize_concurrency {
+; CHECK-LABEL: define void @load_as1(
+; CHECK-SAME: ptr addrspace(1) [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call ptr @llvm.returnaddress.p0(i32 0)
+; CHECK-NEXT:    call void @__csan_func_entry(ptr [[TMP0]])
+; CHECK-NEXT:    [[TMP1:%.*]] = addrspacecast ptr addrspace(1) [[P]] to ptr
+; CHECK-NEXT:    call void @__csan_read4(ptr [[TMP1]], i32 0)
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr addrspace(1) [[P]], align 4
+; CHECK-NEXT:    call void @__csan_func_exit()
+; CHECK-NEXT:    ret void
+;
+entry:
+  %v = load i32, ptr addrspace(1) %p, align 4
+  ret void
+}
+
+define void @load_lds(ptr addrspace(3) %p) sanitize_concurrency {
+; CHECK-LABEL: define void @load_lds(
+; CHECK-SAME: ptr addrspace(3) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call ptr @llvm.returnaddress.p0(i32 0)
+; CHECK-NEXT:    call void @__csan_func_entry(ptr [[TMP0]])
+; CHECK-NEXT:    [[TMP1:%.*]] = addrspacecast ptr addrspace(3) [[P]] to ptr
+; CHECK-NEXT:    call void @__csan_read4(ptr [[TMP1]], i32 0)
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr addrspace(3) [[P]], align 4
+; CHECK-NEXT:    call void @__csan_func_exit()
+; CHECK-NEXT:    ret void
+;
+entry:
+  %v = load i32, ptr addrspace(3) %p, align 4
+  ret void
+}
+
+define void @load_private(ptr addrspace(5) %p) sanitize_concurrency {
+; CHECK-LABEL: define void @load_private(
+; CHECK-SAME: ptr addrspace(5) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr addrspace(5) [[P]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  %v = load i32, ptr addrspace(5) %p, align 4
+  ret void
+}
+
+define void @load_gds(ptr addrspace(2) %p) sanitize_concurrency {
+; CHECK-LABEL: define void @load_gds(
+; CHECK-SAME: ptr addrspace(2) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr addrspace(2) [[P]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  %v = load i32, ptr addrspace(2) %p, align 4
+  ret void
+}
+
+define void @load_buffer_fat(ptr addrspace(7) %p) sanitize_concurrency {
+; CHECK-LABEL: define void @load_buffer_fat(
+; CHECK-SAME: ptr addrspace(7) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[V:%.*]] = load i32, ptr addrspace(7) [[P]], align 4
+; CHECK-NEXT:    ret void
+;
+entry:
+  %v = load i32, ptr addrspace(7) %p, align 4
+  ret void
+}
+
+define void @memcpy_private(ptr addrspace(5) %dst, ptr addrspace(5) %src, i64 %n) sanitize_concurrency {
+; CHECK-LABEL: define void @memcpy_private(
+; CHECK-SAME: ptr addrspace(5) [[DST:%.*]], ptr addrspace(5) [[SRC:%.*]], i64 [[N:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call ptr @llvm.returnaddress.p0(i32 0)
+; CHECK-NEXT:    call void @__csan_func_entry(ptr [[TMP0]])
+; CHECK-NEXT:    call void @llvm.memcpy.p5.p5.i64(ptr addrspace(5) [[DST]], ptr addrspace(5) [[SRC]], i64 [[N]], i1 false)
+; CHECK-NEXT:    call void @__csan_func_exit()
+; CHECK-NEXT:    ret void
+;
+entry:
+  call void @llvm.memcpy.p5.p5.i64(ptr addrspace(5) %dst, ptr addrspace(5) %src, i64 %n, i1 false)
+  ret void
+}
+
+define i32 @scoped_atomic(ptr addrspace(1) %p) sanitize_concurrency {
+; CHECK-LABEL: define i32 @scoped_atomic(
+; CHECK-SAME: ptr addrspace(1) [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call ptr @llvm.returnaddress.p0(i32 0)
+; CHECK-NEXT:    call void @__csan_func_entry(ptr [[TMP0]])
+; CHECK-NEXT:    [[TMP1:%.*]] = addrspacecast ptr addrspace(1) [[P]] to ptr
+; CHECK-NEXT:    call void @__csan_read4(ptr [[TMP1]], i32 1)
+; CHECK-NEXT:    [[V:%.*]] = load atomic i32, ptr addrspace(1) [[P]] syncscope("agent") seq_cst, align 4
+; CHECK-NEXT:    call void @__csan_func_exit()
+; CHECK-NEXT:    ret i32 [[V]]
+;
+entry:
+  %v = load atomic i32, ptr addrspace(1) %p syncscope("agent") seq_cst, align 4
+  ret i32 %v
+}
+
+declare void @llvm.memcpy.p5.p5.i64(ptr addrspace(5), ptr addrspace(5), i64, i1)
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/atomic.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/atomic.ll
new file mode 100644
index 00000000000000..ac2e0c2f78835e
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/atomic.ll
@@ -0,0 +1,86 @@
+; RUN: opt < %s -passes='function(csan)' -S -mtriple=x86_64-unknown-linux-gnu | FileCheck %s
+; RUN: opt < %s -passes='function(csan)' -S -mtriple=amdgcn-amd-amdhsa | FileCheck %s
+
+define i32 @atomic_load(ptr %a) sanitize_concurrency {
+entry:
+  %v = load atomic i32, ptr %a seq_cst, align 4
+  ret i32 %v
+}
+; CHECK-LABEL: @atomic_load(
+; CHECK: call void @__csan_read4(ptr %a, i32 1)
+; CHECK-NEXT: %v = load atomic i32, ptr %a seq_cst, align 4
+
+define void @atomic_store(ptr %a, i32 %v) sanitize_concurrency {
+entry:
+  store atomic i32 %v, ptr %a release, align 4
+  ret void
+}
+; CHECK-LABEL: @atomic_store(
+; CHECK: call void @__csan_write4(ptr %a, i32 1)
+; CHECK-NEXT: store atomic i32 %v, ptr %a release, align 4
+
+define i32 @atomic_rmw(ptr %a, i32 %v) sanitize_concurrency {
+entry:
+  %old = atomicrmw add ptr %a, i32 %v seq_cst
+  ret i32 %old
+}
+; CHECK-LABEL: @atomic_rmw(
+; CHECK: call void @__csan_read_write4(ptr %a, i32 3)
+; CHECK-NEXT: %old = atomicrmw add ptr %a, i32 %v seq_cst
+
+define i32 @atomic_cas(ptr %a, i32 %cmp, i32 %new) sanitize_concurrency {
+entry:
+  %pair = cmpxchg ptr %a, i32 %cmp, i32 %new seq_cst seq_cst
+  %old = extractvalue { i32, i1 } %pair, 0
+  ret i32 %old
+}
+; CHECK-LABEL: @atomic_cas(
+; CHECK: call void @__csan_read_write4(ptr %a, i32 3)
+; CHECK-NEXT: %pair = cmpxchg ptr %a, i32 %cmp, i32 %new seq_cst seq_cst
+
+define void @atomic_fence() sanitize_concurrency {
+entry:
+  fence seq_cst
+  ret void
+}
+; CHECK-LABEL: @atomic_fence(
+; CHECK: call void @__csan_atomic_thread_fence(i32 5)
+; CHECK-NEXT: fence seq_cst
+
+define void @atomic_signal_fence() sanitize_concurrency {
+entry:
+  fence syncscope("singlethread") seq_cst
+  ret void
+}
+; CHECK-LABEL: @atomic_signal_fence(
+; CHECK: call void @__csan_atomic_signal_fence(i32 5)
+; CHECK-NEXT: fence syncscope("singlethread") seq_cst
+
+define void @atomic_fence_orderings() sanitize_concurrency {
+entry:
+  fence acquire
+  fence release
+  fence acq_rel
+  ret void
+}
+; CHECK-LABEL: @atomic_fence_orderings(
+; CHECK: call void @__csan_atomic_thread_fence(i32 2)
+; CHECK-NEXT: fence acquire
+; CHECK: call void @__csan_atomic_thread_fence(i32 3)
+; CHECK-NEXT: fence release
+; CHECK: call void @__csan_atomic_thread_fence(i32 4)
+; CHECK-NEXT: fence acq_rel
+
+define i32 @suppressed(ptr %a) {
+entry:
+  %v = load atomic i32, ptr %a seq_cst, align 4
+  ret i32 %v
+}
+; CHECK-LABEL: @suppressed(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: %v = load atomic i32, ptr %a seq_cst, align 4
+; CHECK-NEXT: ret i32 %v
+
+; CHECK-NOT: @__tsan
+; CHECK-NOT: @__csan_atomic8
+; CHECK-NOT: @__csan_atomic32
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/basic.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/basic.ll
new file mode 100644
index 00000000000000..fa1cc65c709892
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/basic.ll
@@ -0,0 +1,49 @@
+; RUN: opt < %s -passes='module(csan-module),function(csan)' -S -mtriple=x86_64-unknown-linux-gnu | FileCheck %s
+; RUN: opt < %s -passes='module(csan-module),function(csan)' -S -mtriple=amdgcn-amd-amdhsa | FileCheck %s
+
+define void @read_write(ptr %a, ptr %b) sanitize_concurrency {
+entry:
+  %v = load i32, ptr %a, align 4
+  store i32 %v, ptr %b, align 4
+  ret void
+}
+; CHECK-LABEL: @read_write(
+; CHECK: call void @__csan_func_entry
+; CHECK: call void @__csan_read4(ptr %a, i32 0)
+; CHECK-NEXT: %v = load i32, ptr %a, align 4
+; CHECK: call void @__csan_write4(ptr %b, i32 0)
+; CHECK-NEXT: store i32 %v, ptr %b, align 4
+; CHECK: call void @__csan_func_exit
+
+define i16 @unaligned(ptr %a) sanitize_concurrency {
+entry:
+  %v = load i16, ptr %a, align 1
+  ret i16 %v
+}
+; CHECK-LABEL: @unaligned(
+; CHECK: call void @__csan_unaligned_read2(ptr %a, i32 0)
+; CHECK-NEXT: %v = load i16, ptr %a, align 1
+
+define void @memintrinsics(ptr %dst, ptr %src, i64 %n) sanitize_concurrency {
+entry:
+  call void @llvm.memcpy.p0.p0.i64(ptr %dst, ptr %src, i64 %n, i1 false)
+  call void @llvm.memmove.p0.p0.i64(ptr %dst, ptr %src, i64 %n, i1 false)
+  call void @llvm.memset.p0.i64(ptr %dst, i8 0, i64 %n, i1 false)
+  ret void
+}
+; CHECK-LABEL: @memintrinsics(
+; CHECK: call void @__csan_read_range(ptr %src, i64 %n, i32 0)
+; CHECK: call void @__csan_write_range(ptr %dst, i64 %n, i32 0)
+; CHECK: call void @llvm.memcpy
+; CHECK: call void @llvm.memmove
+; CHECK: call void @llvm.memset
+
+; CHECK: define internal void @csan.module_ctor
+; CHECK: call void @__csan_init()
+; CHECK-NOT: @__tsan
+; CHECK-NOT: @__csan_atomic8
+; CHECK-NOT: @__csan_memcpy
+
+declare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1)
+declare void @llvm.memmove.p0.p0.i64(ptr, ptr, i64, i1)
+declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/callbacks.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/callbacks.ll
new file mode 100644
index 00000000000000..d7d8269b081c17
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/callbacks.ll
@@ -0,0 +1,72 @@
+; RUN: opt < %s -passes='function(csan)' -csan-distinguish-volatile -S | FileCheck %s
+
+define void @access_sizes(ptr %p1, ptr %p2, ptr %p4, ptr %p8, ptr %p16) sanitize_concurrency {
+entry:
+  %v1 = load i8, ptr %p1, align 1
+  %v2 = load i16, ptr %p2, align 2
+  %v4 = load i32, ptr %p4, align 4
+  %v8 = load i64, ptr %p8, align 8
+  %v16 = load i128, ptr %p16, align 16
+  store i8 %v1, ptr %p1, align 1
+  store i16 %v2, ptr %p2, align 2
+  store i32 %v4, ptr %p4, align 4
+  store i64 %v8, ptr %p8, align 8
+  store i128 %v16, ptr %p16, align 16
+  ret void
+}
+; CHECK-LABEL: @access_sizes(
+; CHECK-DAG: call void @__csan_write1(ptr %p1, i32 0)
+; CHECK-DAG: call void @__csan_write2(ptr %p2, i32 0)
+; CHECK-DAG: call void @__csan_write4(ptr %p4, i32 0)
+; CHECK-DAG: call void @__csan_write8(ptr %p8, i32 0)
+; CHECK-DAG: call void @__csan_write16(ptr %p16, i32 0)
+
+define i128 @read_sizes(ptr %p1, ptr %p8, ptr %p16) sanitize_concurrency {
+entry:
+  %v1 = load i8, ptr %p1, align 1
+  %v8 = load i64, ptr %p8, align 8
+  %v16 = load i128, ptr %p16, align 16
+  %v1.ext = zext i8 %v1 to i128
+  %v8.ext = zext i64 %v8 to i128
+  %sum1 = add i128 %v1.ext, %v8.ext
+  %sum2 = add i128 %sum1, %v16
+  ret i128 %sum2
+}
+; CHECK-LABEL: @read_sizes(
+; CHECK: call void @__csan_read1(ptr %p1, i32 0)
+; CHECK: call void @__csan_read8(ptr %p8, i32 0)
+; CHECK: call void @__csan_read16(ptr %p16, i32 0)
+
+define i32 @volatile_read(ptr %p) sanitize_concurrency {
+entry:
+  %v = load volatile i32, ptr %p, align 4
+  ret i32 %v
+}
+; CHECK-LABEL: @volatile_read(
+; CHECK: call void @__csan_volatile_read4(ptr %p, i32 0)
+
+define void @unaligned_volatile_write(ptr %p) sanitize_concurrency {
+entry:
+  store volatile i64 1, ptr %p, align 1
+  ret void
+}
+; CHECK-LABEL: @unaligned_volatile_write(
+; CHECK: call void @__csan_unaligned_volatile_write8(ptr %p, i32 0)
+
+define i32 @unaligned_atomic_rmw(ptr %p) sanitize_concurrency {
+entry:
+  %v = atomicrmw add ptr %p, i32 1 seq_cst, align 1
+  ret i32 %v
+}
+; CHECK-LABEL: @unaligned_atomic_rmw(
+; CHECK: call void @__csan_unaligned_read_write4(ptr %p, i32 3)
+; CHECK-NEXT: %v = atomicrmw add ptr %p, i32 1 seq_cst, align 1
+
+define float @atomic_float(ptr %p) sanitize_concurrency {
+entry:
+  %v = load atomic float, ptr %p monotonic, align 4
+  ret float %v
+}
+; CHECK-LABEL: @atomic_float(
+; CHECK: call void @__csan_read4(ptr %p, i32 1)
+; CHECK-NEXT: %v = load atomic float, ptr %p monotonic, align 4
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/function_control.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/function_control.ll
new file mode 100644
index 00000000000000..69636563b24754
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/function_control.ll
@@ -0,0 +1,59 @@
+; RUN: opt < %s -passes='function(csan)' -S | FileCheck %s --check-prefixes=CHECK,EH
+; RUN: opt < %s -passes='function(csan)' -csan-handle-cxx-exceptions=0 -S | FileCheck %s --check-prefixes=CHECK,NOEH
+; RUN: opt < %s -passes='function(csan)' -csan-instrument-func-entry-exit=0 -S | FileCheck %s --check-prefix=NOENTRY
+
+declare void @may_throw()
+
+; Entry and exit callbacks must remain balanced when a call unwinds.
+define void @exception_exit() sanitize_concurrency {
+entry:
+  call void @may_throw()
+  ret void
+}
+; EH-LABEL: @exception_exit(
+; EH: invoke void @may_throw()
+; EH: csan_cleanup:
+; EH: call void @__csan_func_exit()
+; EH-NEXT: resume
+; NOEH-LABEL: @exception_exit(
+; NOEH: call void @may_throw()
+; NOEH: call void @__csan_func_exit()
+
+define void @unattributed_call() {
+entry:
+  call void @may_throw()
+  ret void
+}
+; CHECK-LABEL: @unattributed_call(
+; CHECK: call void @__csan_func_entry
+; CHECK: {{call|invoke}} void @may_throw()
+; CHECK: call void @__csan_func_exit()
+
+define i32 @musttail_callee(ptr %p) sanitize_concurrency {
+entry:
+  %v = load i32, ptr %p
+  ret i32 %v
+}
+
+; A musttail call must remain immediately before its return.
+define i32 @musttail_caller(ptr %p) sanitize_concurrency {
+entry:
+  %v = musttail call i32 @musttail_callee(ptr %p)
+  ret i32 %v
+}
+; CHECK-LABEL: @musttail_caller(
+; CHECK: call void @__csan_func_exit()
+; CHECK-NEXT: %v = musttail call i32 @musttail_callee(ptr %p)
+; CHECK-NEXT: ret i32 %v
+
+define void @ignored() "sanitize_concurrency_no_checking_at_run_time" {
+entry:
+  call void @may_throw()
+  ret void
+}
+; NOENTRY-LABEL: @ignored(
+; NOENTRY-NOT: __csan_func_
+; NOENTRY: call void @__csan_ignore_thread_begin()
+; NOENTRY: invoke void @may_throw()
+; NOENTRY: call void @__csan_ignore_thread_end()
+; NOENTRY: ret void
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/memintrinsics.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/memintrinsics.ll
new file mode 100644
index 00000000000000..35216aa84091a8
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/memintrinsics.ll
@@ -0,0 +1,46 @@
+; RUN: opt < %s -passes='function(csan)' -csan-instrument-func-entry-exit=0 -S -mtriple=amdgcn-amd-amdhsa | FileCheck %s
+
+define void @instrument_source(ptr addrspace(5) %dst, ptr addrspace(1) %src, i32 %n) sanitize_concurrency {
+entry:
+  call void @llvm.memcpy.p5.p1.i32(ptr addrspace(5) %dst, ptr addrspace(1) %src, i32 %n, i1 false)
+  ret void
+}
+; CHECK-LABEL: @instrument_source(
+; CHECK: %[[LEN:.*]] = zext i32 %n to i64
+; CHECK: %[[SRC:.*]] = addrspacecast ptr addrspace(1) %src to ptr
+; CHECK: call void @__csan_read_range(ptr %[[SRC]], i64 %[[LEN]], i32 0)
+; CHECK-NOT: __csan_write_range
+; CHECK: call void @llvm.memcpy
+
+define void @instrument_dest(ptr addrspace(1) %dst, ptr addrspace(5) %src, i32 %n) sanitize_concurrency {
+entry:
+  call void @llvm.memcpy.p1.p5.i32(ptr addrspace(1) %dst, ptr addrspace(5) %src, i32 %n, i1 false)
+  ret void
+}
+; CHECK-LABEL: @instrument_dest(
+; CHECK: %[[LEN:.*]] = zext i32 %n to i64
+; CHECK-NOT: __csan_read_range
+; CHECK: %[[DST:.*]] = addrspacecast ptr addrspace(1) %dst to ptr
+; CHECK: call void @__csan_write_range(ptr %[[DST]], i64 %[[LEN]], i32 0)
+; CHECK: call void @llvm.memcpy
+
+define void @private_copy(ptr addrspace(5) %dst, ptr addrspace(5) %src, i32 %n) sanitize_concurrency {
+entry:
+  call void @llvm.memcpy.p5.p5.i32(ptr addrspace(5) %dst, ptr addrspace(5) %src, i32 %n, i1 false)
+  ret void
+}
+; CHECK-LABEL: @private_copy(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: call void @llvm.memcpy.p5.p5.i32
+; CHECK-NEXT: ret void
+
+define void @instrument_memset(ptr addrspace(1) %dst, i32 %n) sanitize_concurrency {
+entry:
+  call void @llvm.memset.p1.i32(ptr addrspace(1) %dst, i8 0, i32 %n, i1 false)
+  ret void
+}
+; CHECK-LABEL: @instrument_memset(
+; CHECK: %[[LEN:.*]] = zext i32 %n to i64
+; CHECK: %[[DST:.*]] = addrspacecast ptr addrspace(1) %dst to ptr
+; CHECK: call void @__csan_write_range(ptr %[[DST]], i64 %[[LEN]], i32 0)
+; CHECK: call void @llvm.memset
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/module.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/module.ll
new file mode 100644
index 00000000000000..b0ae86ad53bc88
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/module.ll
@@ -0,0 +1,12 @@
+; RUN: opt < %s -passes='csan-module,csan-module' -S | FileCheck %s
+
+; CHECK: @llvm.used = appending global [1 x ptr] [ptr @csan.module_ctor]
+; CHECK: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 0, ptr @csan.module_ctor, ptr null }]
+
+define void @f() sanitize_concurrency {
+  ret void
+}
+
+; CHECK-LABEL: define internal void @csan.module_ctor()
+; CHECK: call void @__csan_init()
+; CHECK: !{i32 4, !"nosanitize_concurrency", i32 1}
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/no_sanitize.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/no_sanitize.ll
new file mode 100644
index 00000000000000..506ec9f380d130
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/no_sanitize.ll
@@ -0,0 +1,57 @@
+; RUN: opt < %s -passes='function(csan)' -S -mtriple=x86_64-unknown-linux-gnu | FileCheck %s
+; RUN: opt < %s -passes='function(csan)' -S -mtriple=amdgcn-amd-amdhsa | FileCheck %s
+
+define i32 @unattributed(ptr %a) {
+entry:
+  %v = load i32, ptr %a, align 4
+  ret i32 %v
+}
+; CHECK-LABEL: @unattributed(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: %v = load i32, ptr %a, align 4
+; CHECK-NEXT: ret i32 %v
+
+define i32 @thread_only(ptr %a) sanitize_thread {
+entry:
+  %v = load i32, ptr %a, align 4
+  ret i32 %v
+}
+; CHECK-LABEL: @thread_only(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: %v = load i32, ptr %a, align 4
+; CHECK-NEXT: ret i32 %v
+
+define i32 @instrumented(ptr %a) sanitize_concurrency {
+entry:
+  %v = load i32, ptr %a, align 4
+  ret i32 %v
+}
+; CHECK-LABEL: @instrumented(
+; CHECK: call void @__csan_read4(ptr %a, i32 0)
+
+define i32 @disabled(ptr %a) sanitize_concurrency disable_sanitizer_instrumentation {
+entry:
+  %v = load i32, ptr %a, align 4
+  ret i32 %v
+}
+; CHECK-LABEL: @disabled(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: %v = load i32, ptr %a, align 4
+; CHECK-NEXT: ret i32 %v
+
+declare void @callee()
+
+define i32 @checking_suppressed(ptr %a) sanitize_concurrency "sanitize_concurrency_no_checking_at_run_time" {
+entry:
+  %v = load i32, ptr %a, align 4
+  call void @callee()
+  ret i32 %v
+}
+; CHECK-LABEL: @checking_suppressed(
+; CHECK: call void @__csan_func_entry
+; CHECK: call void @__csan_ignore_thread_begin()
+; CHECK-NOT: call void @__csan_read
+; CHECK: %v = load i32, ptr %a, align 4
+; CHECK: {{call|invoke}} void @callee()
+; CHECK: call void @__csan_ignore_thread_end()
+; CHECK: call void @__csan_func_exit()
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/selection.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/selection.ll
new file mode 100644
index 00000000000000..eb56ab4111e018
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/selection.ll
@@ -0,0 +1,131 @@
+; RUN: opt < %s -passes='function(csan)' -S | FileCheck %s --check-prefix=DEFAULT
+
+ at constant = constant i32 0
+ at mutable = global i32 0
+
+declare void @capture(ptr)
+
+define void @uncaptured_alloca() sanitize_concurrency {
+entry:
+  %p = alloca i32, align 4
+  store i32 1, ptr %p, align 4
+  ret void
+}
+; DEFAULT-LABEL: @uncaptured_alloca(
+; DEFAULT-NOT: __csan_write
+; DEFAULT: ret void
+
+define void @captured_alloca() sanitize_concurrency {
+entry:
+  %p = alloca i32, align 4
+  call void @capture(ptr %p)
+  store i32 1, ptr %p, align 4
+  ret void
+}
+; DEFAULT-LABEL: @captured_alloca(
+; DEFAULT: {{call|invoke}} void @capture(ptr %p)
+; DEFAULT: call void @__csan_write4(ptr %p, i32 0)
+
+define void @read_before_write(ptr %p) sanitize_concurrency {
+entry:
+  %v = load i32, ptr %p, align 4
+  store i32 %v, ptr %p, align 4
+  ret void
+}
+; DEFAULT-LABEL: @read_before_write(
+; DEFAULT-NOT: call void @__csan_read4(ptr %p, i32 0)
+; DEFAULT: call void @__csan_write4(ptr %p, i32 0)
+
+define void @call_between_accesses(ptr %p) sanitize_concurrency {
+entry:
+  %v = load i32, ptr %p, align 4
+  call void @capture(ptr %p)
+  store i32 %v, ptr %p, align 4
+  ret void
+}
+; DEFAULT-LABEL: @call_between_accesses(
+; DEFAULT: call void @__csan_read4(ptr %p, i32 0)
+; DEFAULT: {{call|invoke}} void @capture(ptr %p)
+; DEFAULT: call void @__csan_write4(ptr %p, i32 0)
+
+define i32 @constant_global() sanitize_concurrency {
+entry:
+  %v = load i32, ptr @constant, align 4
+  ret i32 %v
+}
+; DEFAULT-LABEL: @constant_global(
+; DEFAULT-NOT: __csan_read
+; DEFAULT: ret i32
+
+define i32 @mutable_global() sanitize_concurrency {
+entry:
+  %v = load i32, ptr @mutable, align 4
+  ret i32 %v
+}
+; DEFAULT-LABEL: @mutable_global(
+; DEFAULT: call void @__csan_read4(ptr @mutable, i32 0)
+
+define void @volatile_read_before_write(ptr %p) sanitize_concurrency {
+entry:
+  %v = load volatile i32, ptr %p, align 4
+  store volatile i32 %v, ptr %p, align 4
+  ret void
+}
+; DEFAULT-LABEL: @volatile_read_before_write(
+; DEFAULT-NOT: call void @__csan_read4(ptr %p, i32 0)
+; DEFAULT: call void @__csan_write4(ptr %p, i32 0)
+
+define void @no_sanitize_metadata(ptr %p) sanitize_concurrency {
+entry:
+  %v = load i32, ptr %p, align 4, !nosanitize !0
+  store i32 %v, ptr %p, align 4, !nosanitize !0
+  ret void
+}
+; DEFAULT-LABEL: @no_sanitize_metadata(
+; DEFAULT-NOT: __csan_read
+; DEFAULT-NOT: __csan_write
+; DEFAULT: ret void
+
+define void @unusual_size(ptr %p) sanitize_concurrency {
+entry:
+  %v = load i24, ptr %p, align 4
+  store i24 %v, ptr %p, align 4
+  ret void
+}
+; DEFAULT-LABEL: @unusual_size(
+; DEFAULT-NOT: call void @__csan_read_range(ptr %p, i64 3, i32 0)
+; DEFAULT: call void @__csan_write_range(ptr %p, i64 3, i32 0)
+; DEFAULT: ret void
+
+define void @scalable(ptr %p) sanitize_concurrency {
+entry:
+  %v = load <vscale x 4 x i32>, ptr %p, align 16
+  store <vscale x 4 x i32> %v, ptr %p, align 16
+  ret void
+}
+; DEFAULT-LABEL: @scalable(
+; DEFAULT-NOT: call void @__csan_read_range(ptr %p, i64 %{{.*}}, i32 0)
+; DEFAULT: call void @__csan_write_range(ptr %p, i64 %{{.*}}, i32 0)
+; DEFAULT: ret void
+
+define void @swifterror(ptr swifterror %p) sanitize_concurrency {
+entry:
+  %v = load ptr, ptr %p
+  store ptr null, ptr %p
+  ret void
+}
+; DEFAULT-LABEL: @swifterror(
+; DEFAULT-NOT: __csan_read
+; DEFAULT-NOT: __csan_write
+; DEFAULT: ret void
+
+define i32 @naked() naked sanitize_concurrency {
+entry:
+  %v = load i32, ptr @mutable
+  ret i32 %v
+}
+; DEFAULT-LABEL: @naked(
+; DEFAULT-NEXT: entry:
+; DEFAULT-NEXT: %v = load i32, ptr @mutable, align 4
+
+!0 = !{}
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/shared_alloca.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/shared_alloca.ll
new file mode 100644
index 00000000000000..694720f1f31bd8
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/shared_alloca.ll
@@ -0,0 +1,23 @@
+; RUN: opt < %s -passes='function(csan)' -S -mtriple=nvptx64-nvidia-cuda | FileCheck %s
+
+define void @shared_alloca() sanitize_concurrency {
+entry:
+  %p = alloca i32, align 4, addrspace(3)
+  store i32 0, ptr addrspace(3) %p, align 4
+  ret void
+}
+; CHECK-LABEL: @shared_alloca(
+; CHECK: call void @__csan_write4(ptr %{{.*}}, i32 0)
+; CHECK: store i32 0, ptr addrspace(3) %p, align 4
+
+define void @local_alloca() sanitize_concurrency {
+entry:
+  %p = alloca i32, align 4, addrspace(5)
+  store i32 0, ptr addrspace(5) %p, align 4
+  ret void
+}
+; CHECK-LABEL: @local_alloca(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: %p = alloca i32, align 4, addrspace(5)
+; CHECK-NEXT: store i32 0, ptr addrspace(5) %p, align 4
+; CHECK-NEXT: ret void
diff --git a/llvm/test/Instrumentation/ConcurrencySanitizer/unusual_sizes.ll b/llvm/test/Instrumentation/ConcurrencySanitizer/unusual_sizes.ll
new file mode 100644
index 00000000000000..cd8f320a20155b
--- /dev/null
+++ b/llvm/test/Instrumentation/ConcurrencySanitizer/unusual_sizes.ll
@@ -0,0 +1,33 @@
+; RUN: opt < %s -passes='function(csan)' -S | FileCheck %s
+
+target datalayout = "e-p:64:64"
+
+define void @three_byte_access(ptr %src, ptr %dst) sanitize_concurrency {
+entry:
+  %value = load i24, ptr %src, align 1
+  store i24 %value, ptr %dst, align 1
+  ret void
+}
+; CHECK-LABEL: @three_byte_access(
+; CHECK: call void @__csan_read_range(ptr %src, i64 3, i32 0)
+; CHECK: call void @__csan_write_range(ptr %dst, i64 3, i32 0)
+
+define void @large_access(ptr %src, ptr %dst) sanitize_concurrency {
+entry:
+  %value = load i256, ptr %src, align 32
+  store i256 %value, ptr %dst, align 32
+  ret void
+}
+; CHECK-LABEL: @large_access(
+; CHECK: call void @__csan_read_range(ptr %src, i64 32, i32 0)
+; CHECK: call void @__csan_write_range(ptr %dst, i64 32, i32 0)
+
+define void @scalable_access(ptr %src, ptr %dst) sanitize_concurrency {
+entry:
+  %value = load <vscale x 8 x i32>, ptr %src, align 32
+  store <vscale x 8 x i32> %value, ptr %dst, align 32
+  ret void
+}
+; CHECK-LABEL: @scalable_access(
+; CHECK: call void @__csan_read_range(ptr %src, i64 %{{.*}}, i32 0)
+; CHECK: call void @__csan_write_range(ptr %dst, i64 %{{.*}}, i32 0)



More information about the llvm-branch-commits mailing list