[clang] [compiler-rt] [llvm] [tsan] Revive TSAN's standalone deadlock sanitizer implementation (PR #212748)

Naveen Seth Hanig via cfe-commits cfe-commits at lists.llvm.org
Wed Jul 29 04:50:29 PDT 2026


https://github.com/naveen-seth updated https://github.com/llvm/llvm-project/pull/212748

>From 03407a0198cbd1b31aa725e7c7595b1d6282eb72 Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at outlook.com>
Date: Wed, 29 Jul 2026 13:20:20 +0200
Subject: [PATCH] [tsan] Revive TSAN's standalone deadlock sanitizer
 implementation

This introduces a lightweight ThreadSanitizer submode that focuses exclusively
on lock correctness validation, enabled by `-fsanitize=thread-deadlock`.

The motivation behind this is to allow lock-order inversion and mutex misuse
detection without enabling data-race detection.

This is useful for:
- Programs that exceed regular TSan's shadow memory address space constraints
- Combining with other sanitizers incompatible with TSan
- Platforms where regular TSan is not supported
- Users who are only interested in lock-correctness but not in data-races.

This is implemented as a separate standalone runtime, as the regular TSan
runtime intertwines its lock tracking with SyncVar objects in shadow memory.

A follow up patch will remove the old tsan/dd, which was never wired to the
compiler and is effectively unmaintained/dead code.

This follows the RFC:
https://discourse.llvm.org/t/rfc-add-update-tsan-submode-for-lock-correctness-validation-only/91319?u=naveen-seth
---
 clang/include/clang/Basic/Sanitizers.def      |   3 +
 clang/include/clang/Driver/SanitizerArgs.h    |   3 +
 clang/lib/CodeGen/BackendUtil.cpp             |   9 +
 clang/lib/Driver/SanitizerArgs.cpp            |   4 +-
 clang/lib/Driver/ToolChains/CommonArgs.cpp    |   4 +
 clang/lib/Driver/ToolChains/Linux.cpp         |   1 +
 .../cmake/Modules/AllSupportedArchDefs.cmake  |   1 +
 compiler-rt/cmake/config-ix.cmake             |  10 +-
 .../sanitizer_internal_defs.h                 |   3 +
 compiler-rt/lib/tsan_deadlock/CMakeLists.txt  |  76 ++++
 .../tsan_deadlock_interceptors.cpp            | 398 ++++++++++++++++++
 .../tsan_deadlock/tsan_deadlock_interface.cpp |  36 ++
 .../lib/tsan_deadlock/tsan_deadlock_rtl.cpp   | 342 +++++++++++++++
 .../lib/tsan_deadlock/tsan_deadlock_rtl.h     | 102 +++++
 compiler-rt/test/tsan_deadlock/CMakeLists.txt |  29 ++
 compiler-rt/test/tsan_deadlock/lit.cfg.py     |  52 +++
 .../test/tsan_deadlock/lit.site.cfg.py.in     |  12 +
 .../test/tsan_deadlock/mutex_bad_unlock.cpp   |  26 ++
 .../test/tsan_deadlock/mutex_cycle2.cpp       |  31 ++
 .../test/tsan_deadlock/mutex_cycle_long.c     |  45 ++
 .../tsan_deadlock/mutex_destroy_locked.cpp    |  20 +
 .../tsan_deadlock/mutex_destroy_locked2.cpp   |  27 ++
 .../test/tsan_deadlock/mutex_double_lock.cpp  |  17 +
 .../Instrumentation/ThreadSanitizer.h         |  16 +-
 .../Instrumentation/ThreadSanitizer.cpp       |  19 +-
 25 files changed, 1276 insertions(+), 10 deletions(-)
 create mode 100644 compiler-rt/lib/tsan_deadlock/CMakeLists.txt
 create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp
 create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp
 create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp
 create mode 100644 compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h
 create mode 100644 compiler-rt/test/tsan_deadlock/CMakeLists.txt
 create mode 100644 compiler-rt/test/tsan_deadlock/lit.cfg.py
 create mode 100644 compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in
 create mode 100644 compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp
 create mode 100644 compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp
 create mode 100644 compiler-rt/test/tsan_deadlock/mutex_cycle_long.c
 create mode 100644 compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp
 create mode 100644 compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp
 create mode 100644 compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp

diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def
index da85431625026..fcb43dc31fcdc 100644
--- a/clang/include/clang/Basic/Sanitizers.def
+++ b/clang/include/clang/Basic/Sanitizers.def
@@ -79,6 +79,9 @@ SANITIZER("type", Type)
 // ThreadSanitizer
 SANITIZER("thread", Thread)
 
+// DeadlockSanitizer (ThreadSanitizer, with only the deadlock detection.)
+SANITIZER("thread-deadlock", ThreadDeadlock)
+
 // Numerical stability sanitizer.
 SANITIZER("numerical", NumericalStability)
 
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 6a01b3e36d44c..7b7f738d91131 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -104,6 +104,9 @@ class SanitizerArgs {
   }
   bool needsTysanRt() const { return Sanitizers.has(SanitizerKind::Type); }
   bool needsTsanRt() const { return Sanitizers.has(SanitizerKind::Thread); }
+  bool needsDeadlockRt() const {
+    return Sanitizers.has(SanitizerKind::ThreadDeadlock);
+  }
   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 2b755fa916e55..ce9740554a348 100644
--- a/clang/lib/CodeGen/BackendUtil.cpp
+++ b/clang/lib/CodeGen/BackendUtil.cpp
@@ -741,6 +741,15 @@ static void addSanitizers(const Triple &TargetTriple,
       MPM.addPass(createModuleToFunctionPassAdaptor(ThreadSanitizerPass()));
     }
 
+    if (LangOpts.Sanitize.has(SanitizerKind::ThreadDeadlock)) {
+      MPM.addPass(ModuleThreadSanitizerPass());
+      MPM.addPass(createModuleToFunctionPassAdaptor(ThreadSanitizerPass(
+          ThreadSanitizerOptions{/*InstrumentMemoryAccesses=*/false,
+                                 /*InstrumentAtomics=*/false,
+                                 /*InstrumentMemIntrinsics=*/false,
+                                 /*AlwaysInstrumentFuncEntryExit=*/true})));
+    }
+
     if (LangOpts.Sanitize.has(SanitizerKind::Type))
       MPM.addPass(TypeSanitizerPass());
 
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index c77ba78122a81..3c3bc84eacd3f 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -40,7 +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::Thread | SanitizerKind::ThreadDeadlock |
+    SanitizerKind::Memory | SanitizerKind::DataFlow |
     SanitizerKind::NumericalStability;
 static const SanitizerMask SupportsCoverage =
     SanitizerKind::Address | SanitizerKind::HWAddress |
@@ -707,6 +708,7 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
                          SanitizerKind::Memory | SanitizerKind::Leak |
                          SanitizerKind::Thread),
       std::make_pair(SanitizerKind::Thread, SanitizerKind::Memory),
+      std::make_pair(SanitizerKind::Thread, SanitizerKind::ThreadDeadlock),
       std::make_pair(SanitizerKind::Leak,
                      SanitizerKind::Thread | SanitizerKind::Memory),
       std::make_pair(SanitizerKind::KernelAddress,
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 08c06951cf220..d7acfacd17b05 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1655,6 +1655,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
     }
     if (SanArgs.needsTsanRt())
       SharedRuntimes.push_back("tsan");
+    if (SanArgs.needsDeadlockRt())
+      SharedRuntimes.push_back("tsan_deadlock");
     if (SanArgs.needsTysanRt())
       SharedRuntimes.push_back("tysan");
     if (SanArgs.needsHwasanRt()) {
@@ -1729,6 +1731,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
     if (SanArgs.linkCXXRuntimes())
       StaticRuntimes.push_back("tsan_cxx");
   }
+  if (!SanArgs.needsSharedRt() && SanArgs.needsDeadlockRt())
+    StaticRuntimes.push_back("tsan_deadlock");
   if (!SanArgs.needsSharedRt() && SanArgs.needsTysanRt())
     StaticRuntimes.push_back("tysan");
   if (!SanArgs.needsSharedRt() && SanArgs.needsUbsanRt()) {
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 6dca14d8bf0a8..ab9257362cea1 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -1001,6 +1001,7 @@ Linux::getSupportedSanitizers(BoundArch BA,
   if (IsX86_64 || IsMIPS64 || IsAArch64 || IsPowerPC64 || IsSystemZ ||
       IsLoongArch64 || IsRISCV64)
     Res |= SanitizerKind::Thread;
+  Res |= SanitizerKind::ThreadDeadlock;
   if (IsX86_64 || IsAArch64 || IsSystemZ || IsHexagon)
     Res |= SanitizerKind::Type;
   if (IsX86_64 || IsSystemZ || IsPowerPC64)
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index 9c9874d94a1f2..5eebb98cda68a 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -52,6 +52,7 @@ set(ALL_ASAN_ABI_SUPPORTED_ARCH ${X86_64} ${ARM64} ${ARM64_32})
 set(ALL_DFSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${LOONGARCH64}
     ${S390X})
 set(ALL_RTSAN_SUPPORTED_ARCH ${X86_64} ${ARM64} ${HEXAGON})
+set(ALL_TSAN_DEADLOCK_SUPPORTED_ARCH ${ALL_SANITIZER_COMMON_SUPPORTED_ARCH})
 
 if(ANDROID)
   set(OS_NAME "Android")
diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake
index 083f1c98d0f16..d0ef92ed6d446 100644
--- a/compiler-rt/cmake/config-ix.cmake
+++ b/compiler-rt/cmake/config-ix.cmake
@@ -476,6 +476,7 @@ if(APPLE)
   set(SANITIZER_COMMON_SUPPORTED_OS osx)
   set(PROFILE_SUPPORTED_OS osx)
   set(TSAN_SUPPORTED_OS osx)
+  set(TSAN_DEADLOCK_SUPPORTED_OS osx)
   set(TYSAN_SUPPORTED_OS osx)
   set(XRAY_SUPPORTED_OS osx)
   set(FUZZER_SUPPORTED_OS osx)
@@ -575,6 +576,7 @@ if(APPLE)
           list(APPEND SANITIZER_COMMON_SUPPORTED_OS ${platform}sim)
           list(APPEND PROFILE_SUPPORTED_OS ${platform}sim)
           list(APPEND TSAN_SUPPORTED_OS ${platform}sim)
+          list(APPEND TSAN_DEADLOCK_SUPPORTED_OS ${platform}sim)
           list(APPEND FUZZER_SUPPORTED_OS ${platform}sim)
           list(APPEND ORC_SUPPORTED_OS ${platform}sim)
           list(APPEND UBSAN_SUPPORTED_OS ${platform}sim)
@@ -608,6 +610,7 @@ if(APPLE)
           list_intersect(DARWIN_${platform}_TSAN_ARCHS DARWIN_${platform}_ARCHS ALL_TSAN_SUPPORTED_ARCH)
           if(DARWIN_${platform}_TSAN_ARCHS)
             list(APPEND TSAN_SUPPORTED_OS ${platform})
+            list(APPEND TSAN_DEADLOCK_SUPPORTED_OS ${platform})
           endif()
           list(APPEND FUZZER_SUPPORTED_OS ${platform})
           list(APPEND ORC_SUPPORTED_OS ${platform})
@@ -677,6 +680,9 @@ if(APPLE)
   list_intersect(TSAN_SUPPORTED_ARCH
     ALL_TSAN_SUPPORTED_ARCH
     SANITIZER_COMMON_SUPPORTED_ARCH)
+  list_intersect(TSAN_DEADLOCK_SUPPORTED_ARCH
+    ALL_TSAN_DEADLOCK_SUPPORTED_ARCH
+    SANITIZER_COMMON_SUPPORTED_ARCH)
   list_intersect(UBSAN_SUPPORTED_ARCH
     ALL_UBSAN_SUPPORTED_ARCH
     SANITIZER_COMMON_SUPPORTED_ARCH)
@@ -726,6 +732,7 @@ else()
   filter_available_targets(PROFILE_SUPPORTED_ARCH ${ALL_PROFILE_SUPPORTED_ARCH})
   filter_available_targets(CTX_PROFILE_SUPPORTED_ARCH ${ALL_CTX_PROFILE_SUPPORTED_ARCH})
   filter_available_targets(TSAN_SUPPORTED_ARCH ${ALL_TSAN_SUPPORTED_ARCH})
+  filter_available_targets(TSAN_DEADLOCK_SUPPORTED_ARCH ${ALL_TSAN_DEADLOCK_SUPPORTED_ARCH})
   filter_available_targets(TYSAN_SUPPORTED_ARCH ${ALL_TYSAN_SUPPORTED_ARCH})
   filter_available_targets(UBSAN_SUPPORTED_ARCH ${ALL_UBSAN_SUPPORTED_ARCH})
   filter_available_targets(SAFESTACK_SUPPORTED_ARCH
@@ -765,7 +772,7 @@ if(COMPILER_RT_SUPPORTED_ARCH)
 endif()
 message(STATUS "Compiler-RT supported architectures: ${COMPILER_RT_SUPPORTED_ARCH}")
 
-set(ALL_SANITIZERS asan;rtsan;dfsan;msan;hwasan;tsan;tysan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi)
+set(ALL_SANITIZERS asan;rtsan;dfsan;msan;hwasan;tsan;tsan_deadlock;tysan;safestack;cfi;scudo_standalone;ubsan_minimal;gwp_asan;nsan;asan_abi)
 set(COMPILER_RT_SANITIZERS_TO_BUILD all CACHE STRING
     "sanitizers to build if supported on the target (all;${ALL_SANITIZERS})")
 list_replace(COMPILER_RT_SANITIZERS_TO_BUILD all "${ALL_SANITIZERS}")
@@ -879,6 +886,7 @@ if (COMPILER_RT_HAS_SANITIZER_COMMON AND TSAN_SUPPORTED_ARCH)
 else()
   set(COMPILER_RT_HAS_TSAN FALSE)
 endif()
+set(COMPILER_RT_HAS_TSAN_DEADLOCK ${COMPILER_RT_HAS_TSAN})
 
 if (OS_NAME MATCHES "Linux|FreeBSD|Windows|NetBSD|SunOS")
   set(COMPILER_RT_TSAN_HAS_STATIC_RUNTIME TRUE)
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
index c694897b6556b..8d30a4e2f044c 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_internal_defs.h
@@ -500,5 +500,8 @@ using namespace __sanitizer;
 namespace __memprof {
 using namespace __sanitizer;
 }
+namespace __tsan_deadlock {
+using namespace __sanitizer;
+}
 
 #endif  // SANITIZER_DEFS_H
diff --git a/compiler-rt/lib/tsan_deadlock/CMakeLists.txt b/compiler-rt/lib/tsan_deadlock/CMakeLists.txt
new file mode 100644
index 0000000000000..74fc0f8bdb1cb
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/CMakeLists.txt
@@ -0,0 +1,76 @@
+# Build for the DeadlockSanitizer runtime support library.
+
+include_directories(..)
+
+set(TSAN_DEADLOCK_SOURCES
+  tsan_deadlock_rtl.cpp
+  tsan_deadlock_interceptors.cpp
+  tsan_deadlock_interface.cpp)
+
+set(TSAN_DEADLOCK_HEADERS
+  tsan_deadlock_rtl.h)
+
+set(TSAN_DEADLOCK_CFLAGS ${SANITIZER_COMMON_CFLAGS})
+append_rtti_flag(OFF TSAN_DEADLOCK_CFLAGS)
+
+set(TSAN_DEADLOCK_DYNAMIC_LINK_LIBS
+  ${COMPILER_RT_UNWINDER_LINK_LIBS}
+  ${SANITIZER_CXX_ABI_LIBRARIES}
+  ${SANITIZER_COMMON_LINK_LIBS})
+
+append_list_if(COMPILER_RT_HAS_LIBDL dl TSAN_DEADLOCK_DYNAMIC_LINK_LIBS)
+append_list_if(COMPILER_RT_HAS_LIBM m TSAN_DEADLOCK_DYNAMIC_LINK_LIBS)
+append_list_if(COMPILER_RT_HAS_LIBPTHREAD pthread TSAN_DEADLOCK_DYNAMIC_LINK_LIBS)
+
+add_compiler_rt_component(tsan_deadlock)
+
+if(APPLE)
+  add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS)
+
+  add_compiler_rt_runtime(clang_rt.tsan_deadlock
+    SHARED
+    OS ${TSAN_DEADLOCK_SUPPORTED_OS}
+    ARCHS ${TSAN_DEADLOCK_SUPPORTED_ARCH}
+    SOURCES ${TSAN_DEADLOCK_SOURCES}
+    ADDITIONAL_HEADERS ${TSAN_DEADLOCK_HEADERS}
+    OBJECT_LIBS RTInterception
+                RTSanitizerCommon
+                RTSanitizerCommonLibc
+                RTSanitizerCommonCoverage
+                RTSanitizerCommonSymbolizer
+    CFLAGS ${TSAN_DEADLOCK_CFLAGS}
+    LINK_FLAGS ${SANITIZER_COMMON_LINK_FLAGS} ${WEAK_SYMBOL_LINK_FLAGS}
+    LINK_LIBS ${SANITIZER_COMMON_LINK_LIBS}
+    PARENT_TARGET tsan_deadlock)
+else()
+  foreach(arch ${TSAN_DEADLOCK_SUPPORTED_ARCH})
+    add_compiler_rt_runtime(clang_rt.tsan_deadlock
+      STATIC
+      ARCHS ${arch}
+      SOURCES ${TSAN_DEADLOCK_SOURCES}
+              $<TARGET_OBJECTS:RTInterception.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommon.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommonCoverage.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommonSymbolizerInternal.${arch}>
+      ADDITIONAL_HEADERS ${TSAN_DEADLOCK_HEADERS}
+      CFLAGS ${TSAN_DEADLOCK_CFLAGS}
+      PARENT_TARGET tsan_deadlock)
+    add_compiler_rt_runtime(clang_rt.tsan_deadlock
+      SHARED
+      ARCHS ${arch}
+      SOURCES ${TSAN_DEADLOCK_SOURCES}
+              $<TARGET_OBJECTS:RTInterception.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommon.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommonLibc.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommonCoverage.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommonSymbolizer.${arch}>
+              $<TARGET_OBJECTS:RTSanitizerCommonSymbolizerInternal.${arch}>
+      ADDITIONAL_HEADERS ${TSAN_DEADLOCK_HEADERS}
+      CFLAGS ${TSAN_DEADLOCK_CFLAGS}
+      LINK_LIBS ${TSAN_DEADLOCK_DYNAMIC_LINK_LIBS}
+      LINK_FLAGS ${SANITIZER_COMMON_LINK_FLAGS}
+      PARENT_TARGET tsan_deadlock)
+  endforeach()
+endif()
diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp
new file mode 100644
index 0000000000000..51ed3a22e2d9d
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interceptors.cpp
@@ -0,0 +1,398 @@
+//===-- tsan_deadlock_interceptors.cpp ------------------------------------===//
+//
+// 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 is a part of DeadlockSanitizer.
+//
+//===----------------------------------------------------------------------===//
+
+#include <pthread.h>
+
+#include "interception/interception.h"
+#include "sanitizer_common/sanitizer_allocator_internal.h"
+#include "sanitizer_common/sanitizer_errno.h"
+#include "sanitizer_common/sanitizer_glibc_version.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+#include "tsan_deadlock_rtl.h"
+
+using namespace __tsan_deadlock;
+
+__attribute__((tls_model("initial-exec"))) static __thread volatile int initing;
+static bool inited;
+
+static bool InitThread() {
+  if (initing)
+    return false;
+  if (cur_thread())
+    return true;
+  initing = true;
+  if (!inited) {
+    inited = true;
+    Initialize();
+  }
+  ThreadInit(&thr_tls);
+  initing = false;
+  return true;
+}
+
+struct ThreadArg {
+  void *(*fn)(void *);
+  void *arg;
+};
+
+static void *ThreadTrampoline(void *arg) {
+  ThreadArg *targ = static_cast<ThreadArg *>(arg);
+  void *(*fn)(void *) = targ->fn;
+  void *fn_arg = targ->arg;
+  InternalFree(targ);
+  ThreadInit(&thr_tls);
+  void *retval = fn(fn_arg);
+  // Also called from the pthread_exit interceptor; guard with is_inited to
+  // stay idempotent.
+  if (thr_tls.is_inited)
+    ThreadDestroy(&thr_tls);
+  return retval;
+}
+
+INTERCEPTOR(int, pthread_create, pthread_t *th, const pthread_attr_t *attr,
+            void *(*fn)(void *), void *arg) {
+  InitThread();
+  ThreadArg *targ = static_cast<ThreadArg *>(InternalAlloc(sizeof(ThreadArg)));
+  targ->fn = fn;
+  targ->arg = arg;
+  return REAL(pthread_create)(th, attr, ThreadTrampoline, targ);
+}
+
+INTERCEPTOR(int, pthread_join, pthread_t t, void **retval) {
+  InitThread();
+  return REAL(pthread_join)(t, retval);
+}
+
+INTERCEPTOR(void, pthread_exit, void *retval) {
+  if (thr_tls.is_inited)
+    ThreadDestroy(&thr_tls);
+  REAL(pthread_exit)(retval);
+}
+
+INTERCEPTOR(int, pthread_mutex_init, pthread_mutex_t *m,
+            const pthread_mutexattr_t *attr) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_mutex_init)(m, attr);
+  if (res == 0) {
+    bool reentrant = false;
+    if (attr) {
+      int type = 0;
+      if (pthread_mutexattr_gettype(attr, &type) == 0)
+        reentrant = (type == PTHREAD_MUTEX_RECURSIVE);
+    }
+    MutexInit(cur_thread(), (uptr)m, reentrant, pc);
+  }
+  return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_destroy, pthread_mutex_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_mutex_destroy)(m);
+  if (res == 0 || res == errno_EBUSY)
+    MutexDestroy(cur_thread(), (uptr)m, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_lock, pthread_mutex_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+  int res = REAL(pthread_mutex_lock)(m);
+  MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_trylock, pthread_mutex_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_mutex_trylock)(m);
+  if (res == 0)
+    MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_timedlock, pthread_mutex_t *m,
+            const struct timespec *abstime) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_mutex_timedlock)(m, abstime);
+  if (res == 0)
+    MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_mutex_unlock, pthread_mutex_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc);
+  return REAL(pthread_mutex_unlock)(m);
+}
+
+INTERCEPTOR(int, pthread_spin_init, pthread_spinlock_t *m, int pshared) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_spin_init)(m, pshared);
+  if (res == 0)
+    MutexInit(cur_thread(), (uptr)m, false, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_spin_destroy, pthread_spinlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_spin_destroy)(m);
+  if (res == 0)
+    MutexDestroy(cur_thread(), (uptr)m, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_spin_lock, pthread_spinlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+  int res = REAL(pthread_spin_lock)(m);
+  MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_spin_trylock, pthread_spinlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_spin_trylock)(m);
+  if (res == 0)
+    MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_spin_unlock, pthread_spinlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc);
+  return REAL(pthread_spin_unlock)(m);
+}
+
+INTERCEPTOR(int, pthread_rwlock_init, pthread_rwlock_t *m,
+            const pthread_rwlockattr_t *attr) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_rwlock_init)(m, attr);
+  if (res == 0)
+    MutexInit(cur_thread(), (uptr)m, false, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_destroy, pthread_rwlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexDestroy(cur_thread(), (uptr)m, pc);
+  return REAL(pthread_rwlock_destroy)(m);
+}
+
+INTERCEPTOR(int, pthread_rwlock_rdlock, pthread_rwlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexBeforeLock(cur_thread(), (uptr)m, false, pc);
+  int res = REAL(pthread_rwlock_rdlock)(m);
+  MutexAfterLock(cur_thread(), (uptr)m, false, false, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_tryrdlock, pthread_rwlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_rwlock_tryrdlock)(m);
+  if (res == 0)
+    MutexAfterLock(cur_thread(), (uptr)m, false, true, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_timedrdlock, pthread_rwlock_t *m,
+            const timespec *abstime) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_rwlock_timedrdlock)(m, abstime);
+  if (res == 0)
+    MutexAfterLock(cur_thread(), (uptr)m, false, true, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_wrlock, pthread_rwlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+  int res = REAL(pthread_rwlock_wrlock)(m);
+  MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_trywrlock, pthread_rwlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_rwlock_trywrlock)(m);
+  if (res == 0)
+    MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_timedwrlock, pthread_rwlock_t *m,
+            const timespec *abstime) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  int res = REAL(pthread_rwlock_timedwrlock)(m, abstime);
+  if (res == 0)
+    MutexAfterLock(cur_thread(), (uptr)m, true, true, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_rwlock_unlock, pthread_rwlock_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexBeforeUnlock(cur_thread(), (uptr)m, false, pc);
+  return REAL(pthread_rwlock_unlock)(m);
+}
+
+static pthread_cond_t *init_cond(pthread_cond_t *c, bool force = false) {
+  if (!common_flags()->legacy_pthread_cond)
+    return c;
+  atomic_uintptr_t *p = (atomic_uintptr_t *)c;
+  uptr cond = atomic_load(p, memory_order_acquire);
+  if (!force && cond != 0)
+    return (pthread_cond_t *)cond;
+  void *newcond = InternalAlloc(sizeof(pthread_cond_t));
+  internal_memset(newcond, 0, sizeof(pthread_cond_t));
+  if (atomic_compare_exchange_strong(p, &cond, (uptr)newcond,
+                                     memory_order_acq_rel))
+    return (pthread_cond_t *)newcond;
+  InternalFree(newcond);
+  return (pthread_cond_t *)cond;
+}
+
+INTERCEPTOR(int, pthread_cond_init, pthread_cond_t *c,
+            const pthread_condattr_t *a) {
+  InitThread();
+  return REAL(pthread_cond_init)(init_cond(c, true), a);
+}
+
+INTERCEPTOR(int, pthread_cond_wait, pthread_cond_t *c, pthread_mutex_t *m) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc);
+  int res = REAL(pthread_cond_wait)(init_cond(c), m);
+  MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+  MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_cond_timedwait, pthread_cond_t *c, pthread_mutex_t *m,
+            const timespec *abstime) {
+  uptr pc = GET_CURRENT_PC();
+  InitThread();
+  MutexBeforeUnlock(cur_thread(), (uptr)m, true, pc);
+  int res = REAL(pthread_cond_timedwait)(init_cond(c), m, abstime);
+  MutexBeforeLock(cur_thread(), (uptr)m, true, pc);
+  MutexAfterLock(cur_thread(), (uptr)m, true, false, pc);
+  return res;
+}
+
+INTERCEPTOR(int, pthread_cond_signal, pthread_cond_t *c) {
+  InitThread();
+  return REAL(pthread_cond_signal)(init_cond(c));
+}
+
+INTERCEPTOR(int, pthread_cond_broadcast, pthread_cond_t *c) {
+  InitThread();
+  return REAL(pthread_cond_broadcast)(init_cond(c));
+}
+
+INTERCEPTOR(int, pthread_cond_destroy, pthread_cond_t *c) {
+  InitThread();
+  pthread_cond_t *cond = init_cond(c);
+  int res = REAL(pthread_cond_destroy)(cond);
+  if (common_flags()->legacy_pthread_cond) {
+    InternalFree(cond);
+    atomic_store((atomic_uintptr_t *)c, 0, memory_order_relaxed);
+  }
+  return res;
+}
+
+INTERCEPTOR(char *, realpath, const char *path, char *resolved_path) {
+  InitThread();
+  return REAL(realpath)(path, resolved_path);
+}
+
+INTERCEPTOR(SSIZE_T, read, int fd, void *ptr, SIZE_T count) {
+  InitThread();
+  return REAL(read)(fd, ptr, count);
+}
+
+INTERCEPTOR(SSIZE_T, pread, int fd, void *ptr, SIZE_T count, OFF_T offset) {
+  InitThread();
+  return REAL(pread)(fd, ptr, count, offset);
+}
+
+namespace __tsan_deadlock {
+
+void InitializeInterceptors() {
+  INTERCEPT_FUNCTION(pthread_create);
+  INTERCEPT_FUNCTION(pthread_join);
+  INTERCEPT_FUNCTION(pthread_exit);
+
+  INTERCEPT_FUNCTION(pthread_mutex_init);
+  INTERCEPT_FUNCTION(pthread_mutex_destroy);
+  INTERCEPT_FUNCTION(pthread_mutex_lock);
+  INTERCEPT_FUNCTION(pthread_mutex_trylock);
+  INTERCEPT_FUNCTION(pthread_mutex_timedlock);
+  INTERCEPT_FUNCTION(pthread_mutex_unlock);
+
+  INTERCEPT_FUNCTION(pthread_spin_init);
+  INTERCEPT_FUNCTION(pthread_spin_destroy);
+  INTERCEPT_FUNCTION(pthread_spin_lock);
+  INTERCEPT_FUNCTION(pthread_spin_trylock);
+  INTERCEPT_FUNCTION(pthread_spin_unlock);
+
+  INTERCEPT_FUNCTION(pthread_rwlock_init);
+  INTERCEPT_FUNCTION(pthread_rwlock_destroy);
+  INTERCEPT_FUNCTION(pthread_rwlock_rdlock);
+  INTERCEPT_FUNCTION(pthread_rwlock_tryrdlock);
+  INTERCEPT_FUNCTION(pthread_rwlock_timedrdlock);
+  INTERCEPT_FUNCTION(pthread_rwlock_wrlock);
+  INTERCEPT_FUNCTION(pthread_rwlock_trywrlock);
+  INTERCEPT_FUNCTION(pthread_rwlock_timedwrlock);
+  INTERCEPT_FUNCTION(pthread_rwlock_unlock);
+
+  // See the comment in tsan_interceptors_posix.cpp.
+#if SANITIZER_GLIBC && !__GLIBC_PREREQ(2, 36) &&                               \
+    (defined(__x86_64__) || defined(__mips__) || SANITIZER_PPC64V1 ||          \
+     defined(__s390x__))
+  INTERCEPT_FUNCTION_VER(pthread_cond_init, "GLIBC_2.3.2");
+  INTERCEPT_FUNCTION_VER(pthread_cond_signal, "GLIBC_2.3.2");
+  INTERCEPT_FUNCTION_VER(pthread_cond_broadcast, "GLIBC_2.3.2");
+  INTERCEPT_FUNCTION_VER(pthread_cond_wait, "GLIBC_2.3.2");
+  INTERCEPT_FUNCTION_VER(pthread_cond_timedwait, "GLIBC_2.3.2");
+  INTERCEPT_FUNCTION_VER(pthread_cond_destroy, "GLIBC_2.3.2");
+#else
+  INTERCEPT_FUNCTION(pthread_cond_init);
+  INTERCEPT_FUNCTION(pthread_cond_signal);
+  INTERCEPT_FUNCTION(pthread_cond_broadcast);
+  INTERCEPT_FUNCTION(pthread_cond_wait);
+  INTERCEPT_FUNCTION(pthread_cond_timedwait);
+  INTERCEPT_FUNCTION(pthread_cond_destroy);
+#endif
+
+  INTERCEPT_FUNCTION(realpath);
+  INTERCEPT_FUNCTION(read);
+  INTERCEPT_FUNCTION(pread);
+}
+
+} // namespace __tsan_deadlock
diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp
new file mode 100644
index 0000000000000..8a0d4c3295091
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_interface.cpp
@@ -0,0 +1,36 @@
+//===-- tsan_deadlock_interface.cpp ---------------------------------------===//
+//
+// 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 is a part of DeadlockSanitizer.
+//
+//===----------------------------------------------------------------------===//
+
+#include "tsan_deadlock_rtl.h"
+
+using namespace __tsan_deadlock;
+
+extern "C" {
+
+SANITIZER_INTERFACE_ATTRIBUTE void __tsan_init() { Initialize(); }
+
+SANITIZER_INTERFACE_ATTRIBUTE void __tsan_func_entry(void *pc) {
+  if (UNLIKELY(!thr_tls.is_inited))
+    return;
+  DCHECK_LT(thr_tls.shadow_stack_pos, thr_tls.shadow_stack_end);
+  thr_tls.shadow_stack_pos[0] = (uptr)pc;
+  thr_tls.shadow_stack_pos++;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE void __tsan_func_exit() {
+  if (UNLIKELY(!thr_tls.is_inited))
+    return;
+  if (LIKELY(thr_tls.shadow_stack_pos > thr_tls.shadow_stack))
+    thr_tls.shadow_stack_pos--;
+}
+
+} // extern "C"
diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp
new file mode 100644
index 0000000000000..fd56bb81c4174
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.cpp
@@ -0,0 +1,342 @@
+//===-- tsan_deadlock_rtl.cpp ---------------------------------------------===//
+//
+// 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 is a part of DeadlockSanitizer.
+//
+//===----------------------------------------------------------------------===//
+
+#include "tsan_deadlock_rtl.h"
+
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_flag_parser.h"
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_placement_new.h"
+#include "sanitizer_common/sanitizer_report_decorator.h"
+#include "sanitizer_common/sanitizer_stackdepot.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+namespace __tsan_deadlock {
+
+Flags tsan_deadlock_flags;
+
+static Context *ctx;
+
+static const uptr kShadowStackSize = 64 * 1024;
+
+__attribute__((tls_model("initial-exec"))) THREADLOCAL Thread thr_tls;
+
+Thread *cur_thread() { return thr_tls.is_inited ? &thr_tls : nullptr; }
+
+class Decorator : public __sanitizer::SanitizerCommonDecorator {
+public:
+  Decorator() : SanitizerCommonDecorator() {}
+  const char *Mutex() { return Magenta(); }
+  const char *ThreadDescription() { return Cyan(); }
+};
+
+static u32 CurrentStackId(Thread *thr, uptr pc) {
+  if (pc) {
+    DCHECK_LT(thr->shadow_stack_pos, thr->shadow_stack_end);
+    *thr->shadow_stack_pos = pc;
+    thr->shadow_stack_pos++;
+  }
+  u32 id = StackDepotPut(
+      StackTrace(thr->shadow_stack, thr->shadow_stack_pos - thr->shadow_stack));
+  if (pc)
+    thr->shadow_stack_pos--;
+  return id;
+}
+
+static void PrintStack(Thread *thr, u32 stk) {
+  if (!stk)
+    return;
+  StackTrace trace = StackDepotGet(stk);
+  thr->ignore_interceptors++;
+  trace.Print();
+  thr->ignore_interceptors--;
+}
+
+static void ReportDeadlock(Thread *thr, DDReport *rep) {
+  if (!rep)
+    return;
+  Decorator d;
+  Lock lock(&ctx->report_mutex);
+  Printf("==================\n");
+  Printf(
+      "%sWARNING: %s: lock-order-inversion (potential deadlock) (pid=%d)%s\n",
+      d.Warning(), SanitizerToolName, (int)internal_getpid(), d.Default());
+  Printf("  Cycle in lock order graph: ");
+  for (int i = 0; i < rep->n; i++)
+    Printf("%sM%d%s (0x%zx) => ", d.Mutex(), i, d.Default(),
+           (uptr)rep->loop[i].mtx_ctx0);
+  Printf("%sM0%s\n\n", d.Mutex(), d.Default());
+  for (int i = 0; i < rep->n; i++) {
+    int next = (i + 1) % rep->n;
+    Printf("  %sMutex M%d%s acquired here while holding %sMutex M%d%s"
+           " in %sthread T%lld%s:\n",
+           d.Mutex(), next, d.Default(), d.Mutex(), i, d.Default(),
+           d.ThreadDescription(), (s64)rep->loop[i].thr_ctx, d.Default());
+    PrintStack(thr, rep->loop[i].stk[0]);
+    if (flags()->second_deadlock_stack && rep->loop[i].stk[1]) {
+      Printf("  %sMutex M%d%s previously acquired by the same thread here:\n",
+             d.Mutex(), i, d.Default());
+      PrintStack(thr, rep->loop[i].stk[1]);
+    }
+    if (i == 0 && !flags()->second_deadlock_stack) {
+      Printf(
+          "    HINT: use TSAN_DEADLOCK_OPTIONS=second_deadlock_stack=1 to get "
+          "more informative warning message\n\n");
+    }
+  }
+  Printf("==================\n");
+  StackTrace empty;
+  ReportErrorSummary("lock-order-inversion", &empty);
+  if (flags()->halt_on_error)
+    Die();
+}
+
+static void ReportMutexMisuse(Thread *thr, const char *what, uptr addr, uptr pc,
+                              u32 creation_stk, u32 last_lock_stk = 0) {
+  Decorator d;
+  Lock lock(&ctx->report_mutex);
+  Printf("==================\n");
+  Printf("%sWARNING: %s: %s on %smutex 0x%zx%s (pid=%d)%s\n", d.Warning(),
+         SanitizerToolName, what, d.Mutex(), addr, d.Default(),
+         (int)internal_getpid(), d.Default());
+  PrintStack(thr, CurrentStackId(thr, pc));
+  if (last_lock_stk) {
+    Printf("  and:\n");
+    PrintStack(thr, last_lock_stk);
+  }
+  if (creation_stk) {
+    Printf("%s  Mutex M0 (%p) created at:%s\n", d.Mutex(),
+           reinterpret_cast<void *>(addr), d.Default());
+    PrintStack(thr, creation_stk);
+  }
+  Printf("==================\n");
+  StackTrace empty;
+  ReportErrorSummary(what, &empty);
+  if (flags()->halt_on_error)
+    Die();
+}
+
+Callback::Callback(Thread *thr, uptr pc) : thr(thr), pc(pc) {
+  DDCallback::pt = thr->dd_pt;
+  DDCallback::lt = thr->dd_lt;
+}
+
+u32 Callback::Unwind() { return CurrentStackId(thr, pc); }
+
+int Callback::UniqueTid() { return thr->unique_id; }
+
+static void InitializeFlags() {
+  Flags *f = flags();
+  f->SetDefaults();
+  SetCommonFlagsDefaults();
+  {
+    CommonFlags cf;
+    cf.CopyFrom(*common_flags());
+    cf.allow_addr2line = true;
+    cf.exitcode = 66;
+    OverrideCommonFlags(cf);
+  }
+  FlagParser parser;
+  RegisterFlag(&parser, "second_deadlock_stack",
+               "Report where each mutex is locked in deadlock reports",
+               &f->second_deadlock_stack);
+  RegisterFlag(&parser, "report_destroy_locked",
+               "Report destruction of a locked mutex?",
+               &f->report_destroy_locked);
+  RegisterFlag(&parser, "report_mutex_bugs",
+               "Report incorrect usages of mutexes and mutex annotations?",
+               &f->report_mutex_bugs);
+  RegisterFlag(&parser, "report_bugs",
+               "Turns off bug reporting entirely (useful for benchmarking).",
+               &f->report_bugs);
+  RegisterFlag(&parser, "halt_on_error", "Exit after first reported error.",
+               &f->halt_on_error);
+  RegisterCommonFlags(&parser);
+  parser.ParseStringFromEnv("TSAN_DEADLOCK_OPTIONS");
+  if (!f->report_bugs) {
+    f->report_destroy_locked = false;
+    f->report_mutex_bugs = false;
+  }
+  SetVerbosity(common_flags()->verbosity);
+}
+
+void Initialize() {
+  static atomic_uint32_t initialized;
+  if (atomic_fetch_add(&initialized, 1, memory_order_relaxed) != 0)
+    return;
+  static u64 ctx_mem[sizeof(Context) / sizeof(u64) + 1];
+  ctx = new (ctx_mem) Context();
+  SanitizerToolName = "DeadlockSanitizer";
+  InitializeFlags();
+  ctx->dd = DDetector::Create(flags());
+  InitializeInterceptors();
+}
+
+void ThreadInit(Thread *thr) {
+  static atomic_uintptr_t id_gen;
+  uptr id = atomic_fetch_add(&id_gen, 1, memory_order_relaxed);
+  thr->unique_id = (int)id;
+  thr->dd_pt = ctx->dd->CreatePhysicalThread();
+  thr->dd_lt = ctx->dd->CreateLogicalThread(id);
+  thr->shadow_stack = static_cast<uptr *>(
+      MmapNoReserveOrDie(kShadowStackSize * sizeof(uptr), "shadow stack"));
+  thr->shadow_stack_pos = thr->shadow_stack;
+  thr->shadow_stack_end = thr->shadow_stack + kShadowStackSize;
+  thr->is_inited = true;
+}
+
+void ThreadDestroy(Thread *thr) {
+  thr->is_inited = false;
+  ctx->dd->DestroyLogicalThread(thr->dd_lt);
+  thr->dd_lt = nullptr;
+  UnmapOrDie(thr->shadow_stack, kShadowStackSize * sizeof(uptr));
+  thr->shadow_stack = nullptr;
+  thr->shadow_stack_pos = nullptr;
+  thr->shadow_stack_end = nullptr;
+  ctx->dd->DestroyPhysicalThread(thr->dd_pt);
+  thr->dd_pt = nullptr;
+}
+
+static void EnsureMutexInit(Callback *cb, MutexHashMap::Handle *h, uptr m,
+                            uptr pc) {
+  if (h->created()) {
+    ctx->dd->MutexInit(cb, &(*h)->dd);
+    (*h)->dd.ctx = m;
+    (*h)->creation_stk = CurrentStackId(cb->thr, pc);
+  }
+}
+
+void MutexInit(Thread *thr, uptr m, bool reentrant, uptr pc) {
+  if (!thr || thr->ignore_interceptors)
+    return;
+  Callback cb(thr, pc);
+  MutexHashMap::Handle h(&ctx->mutex_map, m);
+  EnsureMutexInit(&cb, &h, m, pc);
+  h->reentrant = reentrant;
+}
+
+void MutexBeforeLock(Thread *thr, uptr m, bool writelock, uptr pc) {
+  if (!thr || thr->ignore_interceptors)
+    return;
+  Callback cb(thr, pc);
+  bool report_double_lock = false;
+  {
+    MutexHashMap::Handle h(&ctx->mutex_map, m);
+    EnsureMutexInit(&cb, &h, m, pc);
+    if (writelock) {
+      uptr owner = atomic_load(&h->owner_tid, memory_order_relaxed);
+      if (owner == (uptr)thr->dd_lt && !h->reentrant)
+        report_double_lock = true;
+      else if (owner != (uptr)thr->dd_lt)
+        ctx->dd->MutexBeforeLock(&cb, &h->dd, writelock);
+    } else {
+      ctx->dd->MutexBeforeLock(&cb, &h->dd, writelock);
+    }
+  }
+  if (report_double_lock && flags()->report_mutex_bugs) {
+    MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/false,
+                           /*create=*/false);
+    u32 cstk = h.exists() ? h->creation_stk : 0;
+    ReportMutexMisuse(thr, "double lock of a mutex", m, pc, cstk);
+  } else if (flags()->report_bugs) {
+    ReportDeadlock(thr, ctx->dd->GetReport(&cb));
+  }
+}
+
+void MutexAfterLock(Thread *thr, uptr m, bool writelock, bool trylock,
+                    uptr pc) {
+  if (!thr || thr->ignore_interceptors)
+    return;
+  Callback cb(thr, pc);
+  {
+    MutexHashMap::Handle h(&ctx->mutex_map, m);
+    EnsureMutexInit(&cb, &h, m, pc);
+    if (writelock) {
+      uptr owner = atomic_load(&h->owner_tid, memory_order_relaxed);
+      if (owner == (uptr)thr->dd_lt) {
+        DCHECK(h->reentrant);
+        h->recursion++;
+      } else {
+        DCHECK_EQ(owner, 0u);
+        CHECK_EQ(h->recursion, 0);
+        h->recursion = 1;
+        h->last_lock_stk = CurrentStackId(thr, pc);
+        atomic_store(&h->owner_tid, (uptr)thr->dd_lt, memory_order_relaxed);
+        ctx->dd->MutexAfterLock(&cb, &h->dd, writelock, trylock);
+      }
+    } else {
+      ctx->dd->MutexAfterLock(&cb, &h->dd, writelock, trylock);
+    }
+  }
+  if (flags()->report_bugs)
+    ReportDeadlock(thr, ctx->dd->GetReport(&cb));
+}
+
+void MutexBeforeUnlock(Thread *thr, uptr m, bool writelock, uptr pc) {
+  if (!thr || thr->ignore_interceptors)
+    return;
+  Callback cb(thr, pc);
+  bool report_bad_unlock = false;
+  {
+    MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/false,
+                           /*create=*/false);
+    if (!h.exists())
+      return;
+    if (writelock) {
+      uptr owner = atomic_load(&h->owner_tid, memory_order_relaxed);
+      if (owner != (uptr)thr->dd_lt) {
+        report_bad_unlock = true;
+      } else {
+        h->recursion--;
+        if (h->recursion == 0) {
+          atomic_store(&h->owner_tid, 0, memory_order_relaxed);
+          ctx->dd->MutexBeforeUnlock(&cb, &h->dd, writelock);
+        }
+      }
+    } else {
+      ctx->dd->MutexBeforeUnlock(&cb, &h->dd, writelock);
+    }
+  }
+  if (report_bad_unlock && flags()->report_mutex_bugs) {
+    MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/false,
+                           /*create=*/false);
+    u32 cstk = h.exists() ? h->creation_stk : 0;
+    ReportMutexMisuse(thr, "unlock of an unlocked mutex (or by a wrong thread)",
+                      m, pc, cstk);
+  } else if (flags()->report_bugs) {
+    ReportDeadlock(thr, ctx->dd->GetReport(&cb));
+  }
+}
+
+void MutexDestroy(Thread *thr, uptr m, uptr pc) {
+  if (!thr || thr->ignore_interceptors)
+    return;
+  Callback cb(thr, pc);
+  bool report_destroy_locked = false;
+  u32 creation_stk = 0;
+  u32 last_lock_stk = 0;
+  {
+    MutexHashMap::Handle h(&ctx->mutex_map, m, /*remove=*/true);
+    if (!h.exists())
+      return;
+    creation_stk = h->creation_stk;
+    last_lock_stk = h->last_lock_stk;
+    if (atomic_load(&h->owner_tid, memory_order_relaxed) != 0)
+      report_destroy_locked = true;
+    ctx->dd->MutexDestroy(&cb, &h->dd);
+  }
+  if (report_destroy_locked && flags()->report_destroy_locked)
+    ReportMutexMisuse(thr, "destroy of a locked mutex", m, pc, creation_stk,
+                      last_lock_stk);
+}
+
+} // namespace __tsan_deadlock
diff --git a/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h
new file mode 100644
index 0000000000000..ac9046c557c24
--- /dev/null
+++ b/compiler-rt/lib/tsan_deadlock/tsan_deadlock_rtl.h
@@ -0,0 +1,102 @@
+//===-- tsan_deadlock_rtl.h -------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is a part of DeadlockSanitizer.
+//
+// Main internal header.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TSAN_DEADLOCK_RTL_H
+#define TSAN_DEADLOCK_RTL_H
+
+#include "sanitizer_common/sanitizer_addrhashmap.h"
+#include "sanitizer_common/sanitizer_atomic.h"
+#include "sanitizer_common/sanitizer_deadlock_detector_interface.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_mutex.h"
+
+namespace __tsan_deadlock {
+
+struct Flags : DDFlags {
+  bool report_destroy_locked;
+  bool report_mutex_bugs;
+  bool report_bugs;
+  bool halt_on_error;
+
+  void SetDefaults() {
+    second_deadlock_stack = false;
+    report_destroy_locked = true;
+    report_mutex_bugs = true;
+    report_bugs = true;
+    halt_on_error = false;
+  }
+};
+
+struct UserMutex {
+  DDMutex dd;
+  atomic_uintptr_t owner_tid; // DDLogicalThread* cast to uptr; 0 = unlocked
+  u32 recursion;
+  u32 creation_stk;
+  u32 last_lock_stk;
+  bool reentrant;
+};
+
+// Stored in TLS; shadow_stack_pos is first for cache-line friendliness.
+struct Thread {
+  uptr *shadow_stack_pos;
+  uptr *shadow_stack;
+  uptr *shadow_stack_end;
+
+  DDPhysicalThread *dd_pt;
+  DDLogicalThread *dd_lt;
+
+  int ignore_interceptors;
+  int unique_id;
+  bool is_inited;
+};
+
+struct Callback final : public DDCallback {
+  Thread *thr;
+  uptr pc;
+
+  explicit Callback(Thread *thr, uptr pc);
+  u32 Unwind() override;
+  int UniqueTid() override;
+};
+
+typedef AddrHashMap<UserMutex, 31051> MutexHashMap;
+
+struct Context {
+  DDetector *dd;
+  Mutex report_mutex;
+  MutexHashMap mutex_map;
+};
+
+extern Flags tsan_deadlock_flags;
+inline Flags *flags() { return &tsan_deadlock_flags; }
+
+extern THREADLOCAL Thread thr_tls;
+
+Thread *cur_thread();
+
+void Initialize();
+void InitializeInterceptors();
+
+void ThreadInit(Thread *thr);
+void ThreadDestroy(Thread *thr);
+
+void MutexInit(Thread *thr, uptr m, bool reentrant, uptr pc = 0);
+void MutexBeforeLock(Thread *thr, uptr m, bool writelock, uptr pc);
+void MutexAfterLock(Thread *thr, uptr m, bool writelock, bool trylock, uptr pc);
+void MutexBeforeUnlock(Thread *thr, uptr m, bool writelock, uptr pc);
+void MutexDestroy(Thread *thr, uptr m, uptr pc);
+
+} // namespace __tsan_deadlock
+
+#endif // TSAN_DEADLOCK_RTL_H
diff --git a/compiler-rt/test/tsan_deadlock/CMakeLists.txt b/compiler-rt/test/tsan_deadlock/CMakeLists.txt
new file mode 100644
index 0000000000000..308780098af39
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/CMakeLists.txt
@@ -0,0 +1,29 @@
+set(TSAN_DEADLOCK_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+set(TSAN_DEADLOCK_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
+list(APPEND TSAN_DEADLOCK_TEST_DEPS tsan_deadlock)
+
+set(TSAN_DEADLOCK_TESTSUITES)
+set(TSAN_DEADLOCK_TEST_ARCH ${TSAN_DEADLOCK_SUPPORTED_ARCH})
+
+foreach(arch ${TSAN_DEADLOCK_TEST_ARCH})
+  set(TSAN_DEADLOCK_TEST_TARGET_ARCH ${arch})
+  string(TOLOWER "-${arch}" TSAN_DEADLOCK_TEST_CONFIG_SUFFIX)
+  get_test_cc_for_arch(${arch} TSAN_DEADLOCK_TEST_TARGET_CC TSAN_DEADLOCK_TEST_TARGET_CFLAGS)
+
+  string(TOUPPER ${arch} ARCH_UPPER_CASE)
+  set(CONFIG_NAME ${ARCH_UPPER_CASE}Config)
+
+  configure_lit_site_cfg(
+    ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
+    ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py
+    MAIN_CONFIG
+    ${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py)
+
+  list(APPEND TSAN_DEADLOCK_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+endforeach()
+
+add_lit_testsuite(check-tsan_deadlock "Running DeadlockSanitizer tests"
+  ${TSAN_DEADLOCK_TESTSUITES}
+  DEPENDS ${TSAN_DEADLOCK_TEST_DEPS})
+set_target_properties(check-tsan_deadlock PROPERTIES FOLDER "Compiler-RT Tests")
diff --git a/compiler-rt/test/tsan_deadlock/lit.cfg.py b/compiler-rt/test/tsan_deadlock/lit.cfg.py
new file mode 100644
index 0000000000000..0e98737c391cb
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/lit.cfg.py
@@ -0,0 +1,52 @@
+# -*- Python -*-
+
+import os
+
+# Setup config name.
+config.name = "DeadlockSanitizer" + config.name_suffix
+
+# Setup source root.
+config.test_source_root = os.path.dirname(__file__)
+
+default_tsan_deadlock_opts = "atexit_sleep_ms=0"
+
+if config.target_os == "Darwin":
+    # On Darwin, we default to `abort_on_error=1`, which would make tests run
+    # much slower. Let's override this and run lit tests with 'abort_on_error=0'.
+    default_tsan_deadlock_opts += ":abort_on_error=0"
+
+if default_tsan_deadlock_opts:
+    config.environment["TSAN_DEADLOCK_OPTIONS"] = default_tsan_deadlock_opts
+    default_tsan_deadlock_opts += ":"
+config.substitutions.append(
+    (
+        "%env_tsan_deadlock_opts=",
+        "env TSAN_DEADLOCK_OPTIONS=" + default_tsan_deadlock_opts,
+    )
+)
+
+clang_tsan_deadlock_cflags = (
+    ["-fsanitize=thread-deadlock", "-Wall", "-pthread"]
+    + [config.target_cflags]
+    + config.debug_info_flags
+)
+clang_tsan_deadlock_cxxflags = (
+    config.cxx_mode_flags + clang_tsan_deadlock_cflags + ["-std=c++14"]
+)
+
+
+def build_invocation(compile_flags):
+    return " " + " ".join([config.clang] + compile_flags) + " "
+
+
+config.substitutions.append(
+    ("%clang_tsan_deadlock ", build_invocation(clang_tsan_deadlock_cflags))
+)
+config.substitutions.append(
+    ("%clangxx_tsan_deadlock ", build_invocation(clang_tsan_deadlock_cxxflags))
+)
+
+config.suffixes = [".c", ".cpp"]
+
+if config.target_os not in ["FreeBSD", "Linux", "Darwin", "NetBSD"]:
+    config.unsupported = True
diff --git a/compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in b/compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in
new file mode 100644
index 0000000000000..2ba3b1af1c45e
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/lit.site.cfg.py.in
@@ -0,0 +1,12 @@
+ at LIT_SITE_CFG_IN_HEADER@
+
+config.name_suffix = "@TSAN_DEADLOCK_TEST_CONFIG_SUFFIX@"
+config.tsan_deadlock_lit_source_dir = "@TSAN_DEADLOCK_LIT_SOURCE_DIR@"
+config.target_cflags = "@TSAN_DEADLOCK_TEST_TARGET_CFLAGS@"
+config.target_arch = "@TSAN_DEADLOCK_TEST_TARGET_ARCH@"
+
+# Load common config for all compiler-rt lit tests.
+lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured")
+
+# Load tool-specific config that would do the real work.
+lit_config.load_config(config, "@CMAKE_CURRENT_SOURCE_DIR@/lit.cfg.py")
diff --git a/compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp b/compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp
new file mode 100644
index 0000000000000..16723d89ff033
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_bad_unlock.cpp
@@ -0,0 +1,26 @@
+// Adapted from tsan/mutex_bad_unlock.cpp
+// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+
+static pthread_mutex_t mu;
+
+static void *unlocker(void *) {
+  pthread_mutex_unlock(&mu);
+  return 0;
+}
+
+int main() {
+  pthread_mutex_init(&mu, 0);
+  pthread_mutex_lock(&mu);
+  pthread_t t;
+  pthread_create(&t, 0, unlocker, 0);
+  pthread_join(t, 0);
+  return 0;
+}
+
+// CHECK: WARNING: DeadlockSanitizer: unlock of an unlocked mutex (or by a wrong thread)
+// CHECK:     {{.*}} in pthread_mutex_unlock
+// CHECK:   Mutex M0 (0x{{.*}}) created at:
+// CHECK:     {{.*}} in pthread_mutex_init
+// CHECK: SUMMARY: DeadlockSanitizer: unlock of an unlocked mutex (or by a wrong thread)
diff --git a/compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp b/compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp
new file mode 100644
index 0000000000000..8b28da1175e97
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_cycle2.cpp
@@ -0,0 +1,31 @@
+// Equivalent to tsan/mutex_cycle2.c
+// RUN: %clangxx_tsan_deadlock %s -o %t
+// RUN:                                         not %run %t 2>&1 | FileCheck %s
+// RUN: %env_tsan_deadlock_opts=report_bugs=0       %run %t 2>&1 | FileCheck %s --check-prefix=DISABLED
+#include <pthread.h>
+#include <stdio.h>
+
+int main() {
+  pthread_mutex_t mu1, mu2;
+  pthread_mutex_init(&mu1, NULL);
+  pthread_mutex_init(&mu2, NULL);
+
+  // mu1 => mu2
+  pthread_mutex_lock(&mu1);
+  pthread_mutex_lock(&mu2);
+  pthread_mutex_unlock(&mu2);
+  pthread_mutex_unlock(&mu1);
+
+  // mu2 => mu1
+  pthread_mutex_lock(&mu2);
+  pthread_mutex_lock(&mu1);
+  // CHECK: DeadlockSanitizer: lock-order-inversion (potential deadlock)
+  // DISABLED-NOT: DeadlockSanitizer
+  // DISABLED: PASS
+  pthread_mutex_unlock(&mu1);
+  pthread_mutex_unlock(&mu2);
+
+  pthread_mutex_destroy(&mu1);
+  pthread_mutex_destroy(&mu2);
+  fprintf(stderr, "PASS\n");
+}
diff --git a/compiler-rt/test/tsan_deadlock/mutex_cycle_long.c b/compiler-rt/test/tsan_deadlock/mutex_cycle_long.c
new file mode 100644
index 0000000000000..587a58e451342
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_cycle_long.c
@@ -0,0 +1,45 @@
+// Equivalent to tsan/mutex_cycle_long.c
+// RUN: %clangxx_tsan_deadlock %s -o %t
+// RUN: not %run %t 5  2>&1 | FileCheck %s
+// RUN: not %run %t 10 2>&1 | FileCheck %s
+// RUN: not %run %t 15 2>&1 | FileCheck %s
+// RUN: not %run %t 20 2>&1 | FileCheck %s
+// RUN: %run %t 30 2>&1 | FileCheck %s --check-prefix=CHECK-TOO-LONG-CYCLE
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char *argv[]) {
+  int num_mutexes = 5;
+  if (argc > 1)
+    num_mutexes = atoi(argv[1]);
+
+  pthread_mutex_t m[num_mutexes];
+  for (int i = 0; i < num_mutexes; ++i)
+    pthread_mutex_init(&m[i], NULL);
+
+  for (int i = 0; i < num_mutexes - 1; ++i) {
+    pthread_mutex_lock(&m[i]);
+    pthread_mutex_lock(&m[i + 1]);
+
+    pthread_mutex_unlock(&m[i]);
+    pthread_mutex_unlock(&m[i + 1]);
+  }
+
+  pthread_mutex_lock(&m[num_mutexes - 1]);
+  pthread_mutex_lock(&m[0]);
+
+  pthread_mutex_unlock(&m[num_mutexes - 1]);
+  pthread_mutex_unlock(&m[0]);
+
+  for (int i = 0; i < num_mutexes; ++i)
+    pthread_mutex_destroy(&m[i]);
+
+  fprintf(stderr, "PASS\n");
+}
+
+// CHECK: DeadlockSanitizer: lock-order-inversion (potential deadlock)
+// CHECK-TOO-LONG-CYCLE: WARNING: too long mutex cycle found
+// CHECK: PASS
+// CHECK-TOO-LONG-CYCLE: PASS
diff --git a/compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp
new file mode 100644
index 0000000000000..96e813bebcd3f
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked.cpp
@@ -0,0 +1,20 @@
+// Equivalent to tsan/mutex_destroy_locked.cpp
+// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+
+int main() {
+  pthread_mutex_t m;
+  pthread_mutex_init(&m, 0);
+  pthread_mutex_lock(&m);
+  pthread_mutex_destroy(&m);
+  return 0;
+}
+
+// CHECK: WARNING: DeadlockSanitizer: destroy of a locked mutex
+// CHECK:     {{.*}} in pthread_mutex_destroy
+// CHECK:   and:
+// CHECK:     {{.*}} in pthread_mutex_lock
+// CHECK:   Mutex M0 (0x{{.*}}) created at:
+// CHECK:     {{.*}} in pthread_mutex_init
+// CHECK: SUMMARY: DeadlockSanitizer: destroy of a locked mutex
diff --git a/compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp
new file mode 100644
index 0000000000000..5aced7b4a7a0c
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_destroy_locked2.cpp
@@ -0,0 +1,27 @@
+// Equivalent to tsan/mutex_destroy_locked2.cpp
+// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+
+static void *thread(void *) {
+  pthread_mutex_t m;
+  pthread_mutex_init(&m, 0);
+  pthread_mutex_lock(&m);
+  pthread_mutex_destroy(&m);
+  return 0;
+}
+
+int main() {
+  pthread_t th;
+  pthread_create(&th, 0, thread, 0);
+  pthread_join(th, 0);
+  return 0;
+}
+
+// CHECK: WARNING: DeadlockSanitizer: destroy of a locked mutex
+// CHECK:     {{.*}} in pthread_mutex_destroy
+// CHECK:   and:
+// CHECK:     {{.*}} in pthread_mutex_lock
+// CHECK:   Mutex M0 (0x{{.*}}) created at:
+// CHECK:     {{.*}} in pthread_mutex_init
+// CHECK: SUMMARY: DeadlockSanitizer: destroy of a locked mutex
diff --git a/compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp b/compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp
new file mode 100644
index 0000000000000..7236ade4c33c3
--- /dev/null
+++ b/compiler-rt/test/tsan_deadlock/mutex_double_lock.cpp
@@ -0,0 +1,17 @@
+// Adapted from tsan/mutex_double_lock.cpp
+// RUN: %clangxx_tsan_deadlock -O1 %s -o %t && not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+
+int main() {
+  pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER;
+  pthread_mutex_lock(&mu);
+  pthread_mutex_lock(&mu);
+  return 0;
+}
+
+// CHECK: WARNING: DeadlockSanitizer: double lock of a mutex
+// CHECK:     {{.*}} in pthread_mutex_lock
+// CHECK:   Mutex M0 (0x{{.*}}) created at:
+// CHECK:     {{.*}} in pthread_mutex_lock
+// CHECK: SUMMARY: DeadlockSanitizer: double lock of a mutex
diff --git a/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h b/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h
index 39e09a458cba5..cad45cb4f8a4d 100644
--- a/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h
+++ b/llvm/include/llvm/Transforms/Instrumentation/ThreadSanitizer.h
@@ -20,13 +20,27 @@ namespace llvm {
 class Function;
 class Module;
 
+struct ThreadSanitizerOptions {
+  bool InstrumentMemoryAccesses = true;
+  bool InstrumentAtomics = true;
+  bool InstrumentMemIntrinsics = true;
+  bool AlwaysInstrumentFuncEntryExit = false;
+};
+
 /// A function pass for tsan instrumentation.
 ///
 /// Instruments functions to detect race conditions reads. This function pass
 /// inserts calls to runtime library functions. If the functions aren't declared
 /// yet, the pass inserts the declarations. Otherwise the existing globals are
-struct ThreadSanitizerPass : public RequiredPassInfoMixin<ThreadSanitizerPass> {
+class ThreadSanitizerPass : public RequiredPassInfoMixin<ThreadSanitizerPass> {
+public:
+  LLVM_ABI
+  ThreadSanitizerPass(const ThreadSanitizerOptions &Options = {})
+      : Options(Options) {}
   LLVM_ABI PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM);
+
+private:
+  ThreadSanitizerOptions Options;
 };
 
 /// A module pass for tsan instrumentation.
diff --git a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
index f05efd863fb74..0a863aa3ae559 100644
--- a/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/ThreadSanitizer.cpp
@@ -109,7 +109,7 @@ namespace {
 /// ensures the __tsan_init function is in the list of global constructors for
 /// the module.
 struct ThreadSanitizer {
-  ThreadSanitizer() {
+  ThreadSanitizer(ThreadSanitizerOptions Options) : Options(Options) {
     // Check options and warn user.
     if (ClInstrumentReadBeforeWrite && ClCompoundReadBeforeWrite) {
       errs()
@@ -172,6 +172,7 @@ struct ThreadSanitizer {
   FunctionCallee TsanVptrUpdate;
   FunctionCallee TsanVptrLoad;
   FunctionCallee MemmoveFn, MemcpyFn, MemsetFn;
+  ThreadSanitizerOptions Options;
 };
 
 void insertModuleCtor(Module &M) {
@@ -186,7 +187,7 @@ void insertModuleCtor(Module &M) {
 
 PreservedAnalyses ThreadSanitizerPass::run(Function &F,
                                            FunctionAnalysisManager &FAM) {
-  ThreadSanitizer TSan;
+  ThreadSanitizer TSan(Options);
   if (TSan.sanitizeFunction(F, FAM.getResult<TargetLibraryAnalysis>(F)))
     return PreservedAnalyses::none();
   return PreservedAnalyses::all();
@@ -550,19 +551,21 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
   // (e.g. variables that do not escape, etc).
 
   // Instrument memory accesses only if we want to report bugs in the function.
-  if (ClInstrumentMemoryAccesses && SanitizeFunction)
+  if (ClInstrumentMemoryAccesses && Options.InstrumentMemoryAccesses &&
+      SanitizeFunction)
     for (const auto &II : AllLoadsAndStores) {
       Res |= instrumentLoadOrStore(II, DL);
     }
 
   // Instrument atomic memory accesses in any case (they can be used to
   // implement synchronization).
-  if (ClInstrumentAtomics)
+  if (ClInstrumentAtomics && Options.InstrumentAtomics)
     for (auto *Inst : AtomicAccesses) {
       Res |= instrumentAtomic(Inst, DL);
     }
 
-  if (ClInstrumentMemIntrinsics && SanitizeFunction)
+  if (ClInstrumentMemIntrinsics && Options.InstrumentMemIntrinsics &&
+      SanitizeFunction)
     for (auto *Inst : MemIntrinCalls) {
       Res |= instrumentMemIntrinsic(Inst);
     }
@@ -573,8 +576,10 @@ bool ThreadSanitizer::sanitizeFunction(Function &F,
       InsertRuntimeIgnores(F);
   }
 
-  // Instrument function entry/exit points if there were instrumented accesses.
-  if ((Res || HasCalls) && ClInstrumentFuncEntryExit) {
+  // Instrument function entry/exit points if there were instrumented accesses,
+  // or if all functions should be tracked (e.g. for tsan_deadlock).
+  if ((Res || HasCalls || Options.AlwaysInstrumentFuncEntryExit) &&
+      ClInstrumentFuncEntryExit) {
     InstrumentationIRBuilder IRB(&F.getEntryBlock(),
                                  F.getEntryBlock().getFirstNonPHIIt());
     auto ProgramAsPtrTy = PointerType::get(F.getParent()->getContext(),



More information about the cfe-commits mailing list