[clang] [compiler-rt] [sanitizer] Add DoubleFreeSanitizer (DSan) (PR #213846)

Bojun Seo via cfe-commits cfe-commits at lists.llvm.org
Tue Aug 4 01:24:29 PDT 2026


https://github.com/Bojun-Seo updated https://github.com/llvm/llvm-project/pull/213846

>From bce72033cf349e62fe82004466c189369dc8b0af Mon Sep 17 00:00:00 2001
From: "bojun.seo" <bojun.seo at lge.com>
Date: Tue, 28 Apr 2026 15:13:19 +0900
Subject: [PATCH] [sanitizer] Add DoubleFreeSanitizer (DSan)

Add DoubleFreeSanitizer (DSan), a standalone runtime sanitizer
that detects double-free errors.

DSan intercepts allocation and free APIs and records per-allocation
state together with allocation and free stack traces. When an
allocation is freed again, it reports the invalid second free, the
first free, and the original allocation.

Unlike AddressSanitizer, DSan does not require compiler
instrumentation or shadow memory. This provides a focused option for
resource-constrained environments, including embedded devices, where
running ASan may not be feasible.

DSan is enabled with -fsanitize=doublefree.

RFC: DoubleFreeSanitizer proposal on Discourse (#91363)

Assisted-by: GPT-5.6 Sol
---
 clang/include/clang/Basic/Sanitizers.def      |   3 +
 clang/include/clang/Driver/SanitizerArgs.h    |   5 +
 clang/lib/Driver/SanitizerArgs.cpp            |   6 +-
 clang/lib/Driver/ToolChains/CommonArgs.cpp    |   2 +
 clang/lib/Driver/ToolChains/Darwin.cpp        |   3 +
 clang/lib/Driver/ToolChains/Fuchsia.cpp       |   1 +
 clang/lib/Driver/ToolChains/Linux.cpp         |   3 +
 clang/lib/Driver/ToolChains/NetBSD.cpp        |   1 +
 clang/test/Driver/fsanitize-doublefree.c      |  21 +
 .../cmake/Modules/AllSupportedArchDefs.cmake  |   6 +
 compiler-rt/cmake/config-ix.cmake             |  17 +
 compiler-rt/include/CMakeLists.txt            |   1 +
 .../include/sanitizer/dsan_interface.h        |  30 +
 compiler-rt/lib/CMakeLists.txt                |   2 +
 compiler-rt/lib/dsan/.clang-format            |   3 +
 compiler-rt/lib/dsan/CMakeLists.txt           |  85 +++
 compiler-rt/lib/dsan/dsan.cpp                 | 112 ++++
 compiler-rt/lib/dsan/dsan.h                   |  56 ++
 compiler-rt/lib/dsan/dsan_allocator.cpp       | 501 +++++++++++++++
 compiler-rt/lib/dsan/dsan_allocator.h         | 150 +++++
 compiler-rt/lib/dsan/dsan_common.cpp          |  31 +
 compiler-rt/lib/dsan/dsan_common.h            | 103 ++++
 compiler-rt/lib/dsan/dsan_fuchsia.cpp         | 131 ++++
 compiler-rt/lib/dsan/dsan_fuchsia.h           |  35 ++
 compiler-rt/lib/dsan/dsan_interceptors.cpp    | 575 ++++++++++++++++++
 compiler-rt/lib/dsan/dsan_linux.cpp           |  34 ++
 compiler-rt/lib/dsan/dsan_mac.cpp             | 234 +++++++
 compiler-rt/lib/dsan/dsan_malloc_mac.cpp      |  66 ++
 compiler-rt/lib/dsan/dsan_posix.cpp           | 121 ++++
 compiler-rt/lib/dsan/dsan_posix.h             |  49 ++
 compiler-rt/lib/dsan/dsan_preinit.cpp         |  21 +
 compiler-rt/lib/dsan/dsan_thread.cpp          | 123 ++++
 compiler-rt/lib/dsan/dsan_thread.h            |  66 ++
 compiler-rt/lib/dsan/weak_symbols.txt         |   1 +
 compiler-rt/test/CMakeLists.txt               |   3 +-
 compiler-rt/test/dsan/CMakeLists.txt          |  28 +
 .../dsan/TestCases/concurrent-double-free.cpp |  28 +
 compiler-rt/test/dsan/TestCases/double-free.c |  17 +
 .../test/dsan/TestCases/invalid-free.c        |  12 +
 .../test/dsan/TestCases/large-double-free.c   |  15 +
 compiler-rt/test/dsan/TestCases/realloc.c     |  16 +
 .../test/dsan/TestCases/reallocarray.c        |  18 +
 compiler-rt/test/dsan/TestCases/smoke.cpp     |  10 +
 compiler-rt/test/dsan/lit.common.cfg.py       | 114 ++++
 compiler-rt/test/dsan/lit.site.cfg.py.in      |  13 +
 45 files changed, 2870 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/Driver/fsanitize-doublefree.c
 create mode 100644 compiler-rt/include/sanitizer/dsan_interface.h
 create mode 100644 compiler-rt/lib/dsan/.clang-format
 create mode 100644 compiler-rt/lib/dsan/CMakeLists.txt
 create mode 100644 compiler-rt/lib/dsan/dsan.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan.h
 create mode 100644 compiler-rt/lib/dsan/dsan_allocator.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_allocator.h
 create mode 100644 compiler-rt/lib/dsan/dsan_common.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_common.h
 create mode 100644 compiler-rt/lib/dsan/dsan_fuchsia.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_fuchsia.h
 create mode 100644 compiler-rt/lib/dsan/dsan_interceptors.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_linux.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_mac.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_malloc_mac.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_posix.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_posix.h
 create mode 100644 compiler-rt/lib/dsan/dsan_preinit.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_thread.cpp
 create mode 100644 compiler-rt/lib/dsan/dsan_thread.h
 create mode 100644 compiler-rt/lib/dsan/weak_symbols.txt
 create mode 100644 compiler-rt/test/dsan/CMakeLists.txt
 create mode 100644 compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp
 create mode 100644 compiler-rt/test/dsan/TestCases/double-free.c
 create mode 100644 compiler-rt/test/dsan/TestCases/invalid-free.c
 create mode 100644 compiler-rt/test/dsan/TestCases/large-double-free.c
 create mode 100644 compiler-rt/test/dsan/TestCases/realloc.c
 create mode 100644 compiler-rt/test/dsan/TestCases/reallocarray.c
 create mode 100644 compiler-rt/test/dsan/TestCases/smoke.cpp
 create mode 100644 compiler-rt/test/dsan/lit.common.cfg.py
 create mode 100644 compiler-rt/test/dsan/lit.site.cfg.py.in

diff --git a/clang/include/clang/Basic/Sanitizers.def b/clang/include/clang/Basic/Sanitizers.def
index da85431625026..47ff011e82550 100644
--- a/clang/include/clang/Basic/Sanitizers.def
+++ b/clang/include/clang/Basic/Sanitizers.def
@@ -88,6 +88,9 @@ SANITIZER("realtime", Realtime)
 // LeakSanitizer
 SANITIZER("leak", Leak)
 
+// DoubleFreeSanitizer
+SANITIZER("doublefree", DoubleFree)
+
 // UndefinedBehaviorSanitizer
 SANITIZER("alignment", Alignment)
 SANITIZER("array-bounds", ArrayBounds)
diff --git a/clang/include/clang/Driver/SanitizerArgs.h b/clang/include/clang/Driver/SanitizerArgs.h
index 6a01b3e36d44c..6e774c5ef8c77 100644
--- a/clang/include/clang/Driver/SanitizerArgs.h
+++ b/clang/include/clang/Driver/SanitizerArgs.h
@@ -111,6 +111,11 @@ class SanitizerArgs {
            !Sanitizers.has(SanitizerKind::Address) &&
            !Sanitizers.has(SanitizerKind::HWAddress);
   }
+  bool needsDsanRt() const {
+    return Sanitizers.has(SanitizerKind::DoubleFree) &&
+           !Sanitizers.has(SanitizerKind::Address) &&
+           !Sanitizers.has(SanitizerKind::HWAddress);
+  }
   bool needsFuzzerInterceptors() const;
   bool needsUbsanRt() const;
   bool needsUbsanCXXRt() const;
diff --git a/clang/lib/Driver/SanitizerArgs.cpp b/clang/lib/Driver/SanitizerArgs.cpp
index c77ba78122a81..c1c113d91274a 100644
--- a/clang/lib/Driver/SanitizerArgs.cpp
+++ b/clang/lib/Driver/SanitizerArgs.cpp
@@ -48,7 +48,8 @@ static const SanitizerMask SupportsCoverage =
     SanitizerKind::Type | SanitizerKind::MemtagStack |
     SanitizerKind::MemtagHeap | SanitizerKind::MemtagGlobals |
     SanitizerKind::Memory | SanitizerKind::KernelMemory | SanitizerKind::Leak |
-    SanitizerKind::Undefined | SanitizerKind::Integer | SanitizerKind::Bounds |
+    SanitizerKind::DoubleFree | SanitizerKind::Undefined |
+    SanitizerKind::Integer | SanitizerKind::Bounds |
     SanitizerKind::ImplicitConversion | SanitizerKind::Nullability |
     SanitizerKind::DataFlow | SanitizerKind::Fuzzer |
     SanitizerKind::FuzzerNoLink | SanitizerKind::FloatDivideByZero |
@@ -709,6 +710,9 @@ SanitizerArgs::SanitizerArgs(const ToolChain &TC,
       std::make_pair(SanitizerKind::Thread, SanitizerKind::Memory),
       std::make_pair(SanitizerKind::Leak,
                      SanitizerKind::Thread | SanitizerKind::Memory),
+      std::make_pair(SanitizerKind::DoubleFree,
+                     SanitizerKind::Leak | SanitizerKind::Thread |
+                         SanitizerKind::Memory | SanitizerKind::Scudo),
       std::make_pair(SanitizerKind::KernelAddress,
                      SanitizerKind::Address | SanitizerKind::Leak |
                          SanitizerKind::Thread | SanitizerKind::Memory),
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 883296e43111b..019880ca24253 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -1747,6 +1747,8 @@ collectSanitizerRuntimes(const ToolChain &TC, const ArgList &Args,
     StaticRuntimes.push_back("dfsan");
   if (SanArgs.needsLsanRt())
     StaticRuntimes.push_back("lsan");
+  if (SanArgs.needsDsanRt())
+    StaticRuntimes.push_back("dsan");
   if (SanArgs.needsMsanRt()) {
     StaticRuntimes.push_back("msan");
     if (SanArgs.linkCXXRuntimes())
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index d3de04fc5155e..a491e0cf3a74f 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -1760,6 +1760,8 @@ void DarwinClang::AddLinkRuntimeLibArgs(const ArgList &Args,
     }
     if (Sanitize.needsLsanRt())
       AddLinkSanitizerLibArgs(Args, CmdArgs, "lsan");
+    if (Sanitize.needsDsanRt())
+      AddLinkSanitizerLibArgs(Args, CmdArgs, "dsan");
     if (Sanitize.needsUbsanRt()) {
       assert(Sanitize.needsSharedRt() &&
              "Static sanitizer runtimes not supported");
@@ -4064,6 +4066,7 @@ Darwin::getSupportedSanitizers(BoundArch BA,
   Res |= SanitizerKind::PointerSubtract;
   Res |= SanitizerKind::Realtime;
   Res |= SanitizerKind::Leak;
+  Res |= SanitizerKind::DoubleFree;
   Res |= SanitizerKind::Fuzzer;
   Res |= SanitizerKind::FuzzerNoLink;
   Res |= SanitizerKind::ObjCCast;
diff --git a/clang/lib/Driver/ToolChains/Fuchsia.cpp b/clang/lib/Driver/ToolChains/Fuchsia.cpp
index abde9fa10482d..36cadd7db9bf5 100644
--- a/clang/lib/Driver/ToolChains/Fuchsia.cpp
+++ b/clang/lib/Driver/ToolChains/Fuchsia.cpp
@@ -483,6 +483,7 @@ Fuchsia::getSupportedSanitizers(BoundArch BA,
   Res |= SanitizerKind::Fuzzer;
   Res |= SanitizerKind::FuzzerNoLink;
   Res |= SanitizerKind::Leak;
+  Res |= SanitizerKind::DoubleFree;
   Res |= SanitizerKind::Scudo;
   Res |= SanitizerKind::Thread;
   if (getTriple().getArch() == llvm::Triple::x86_64 ||
diff --git a/clang/lib/Driver/ToolChains/Linux.cpp b/clang/lib/Driver/ToolChains/Linux.cpp
index 1ab385a9ea001..486d22e16145a 100644
--- a/clang/lib/Driver/ToolChains/Linux.cpp
+++ b/clang/lib/Driver/ToolChains/Linux.cpp
@@ -997,6 +997,9 @@ Linux::getSupportedSanitizers(BoundArch BA,
   if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 ||
       IsRISCV64 || IsSystemZ || IsHexagon || IsLoongArch64)
     Res |= SanitizerKind::Leak;
+  if (IsX86_64 || IsMIPS64 || IsAArch64 || IsX86 || IsArmArch || IsPowerPC64 ||
+      IsRISCV64 || IsSystemZ || IsHexagon || IsLoongArch64)
+    Res |= SanitizerKind::DoubleFree;
   if (IsX86_64 || IsMIPS64 || IsAArch64 || IsPowerPC64 || IsSystemZ ||
       IsLoongArch64 || IsRISCV64)
     Res |= SanitizerKind::Thread;
diff --git a/clang/lib/Driver/ToolChains/NetBSD.cpp b/clang/lib/Driver/ToolChains/NetBSD.cpp
index f03114b53bb61..dab65e9b46995 100644
--- a/clang/lib/Driver/ToolChains/NetBSD.cpp
+++ b/clang/lib/Driver/ToolChains/NetBSD.cpp
@@ -521,6 +521,7 @@ NetBSD::getSupportedSanitizers(BoundArch BA,
     Res |= SanitizerKind::PointerCompare;
     Res |= SanitizerKind::PointerSubtract;
     Res |= SanitizerKind::Leak;
+    Res |= SanitizerKind::DoubleFree;
     Res |= SanitizerKind::SafeStack;
     Res |= SanitizerKind::Scudo;
     Res |= SanitizerKind::Vptr;
diff --git a/clang/test/Driver/fsanitize-doublefree.c b/clang/test/Driver/fsanitize-doublefree.c
new file mode 100644
index 0000000000000..f8a7bf26c0994
--- /dev/null
+++ b/clang/test/Driver/fsanitize-doublefree.c
@@ -0,0 +1,21 @@
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize=doublefree %s -### 2>&1 | FileCheck %s --check-prefix=DSAN
+// DSAN: "-fsanitize=doublefree"
+// DSAN: libclang_rt.dsan
+
+// RUN: %clang --target=x86_64-linux-gnu -fsanitize=doublefree,undefined %s -### 2>&1 | FileCheck %s --check-prefix=DSAN-UBSAN
+// DSAN-UBSAN: libclang_rt.dsan
+// DSAN-UBSAN: libclang_rt.ubsan_standalone
+
+// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=doublefree,leak %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=DSAN-LEAK
+// DSAN-LEAK: '-fsanitize=doublefree' not allowed with '-fsanitize=leak'
+
+// RUN: not %clang --target=x86_64-linux-gnu -fsanitize=doublefree,scudo %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=DSAN-SCUDO
+// DSAN-SCUDO: '-fsanitize=doublefree' not allowed with '-fsanitize=scudo'
+
+// RUN: not %clang --target=x86_64-unknown-freebsd -fsanitize=doublefree %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=FREEBSD
+// FREEBSD: unsupported option '-fsanitize=doublefree' for target 'x86_64-unknown-freebsd'
+
+// RUN: not %clang --target=wasm32-unknown-emscripten -fsanitize=doublefree %s -fsyntax-only 2>&1 | FileCheck %s --check-prefix=EMSCRIPTEN
+// EMSCRIPTEN: unsupported option '-fsanitize=doublefree' for target 'wasm32-unknown-emscripten'
+
+int main(void) { return 0; }
diff --git a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
index 9c9874d94a1f2..fffd2c69f03fb 100644
--- a/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
+++ b/compiler-rt/cmake/Modules/AllSupportedArchDefs.cmake
@@ -85,6 +85,12 @@ else()
   set(ALL_LSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64} ${ARM32}
       ${PPC64} ${S390X} ${RISCV64} ${HEXAGON} ${LOONGARCH64})
 endif()
+if(APPLE)
+  set(ALL_DSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64})
+else()
+  set(ALL_DSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64} ${ARM32}
+      ${PPC64} ${S390X} ${RISCV64} ${HEXAGON} ${LOONGARCH64})
+endif()
 if (OS_NAME MATCHES "FreeBSD")
   set(ALL_MSAN_SUPPORTED_ARCH ${X86_64} ${ARM64})
 else()
diff --git a/compiler-rt/cmake/config-ix.cmake b/compiler-rt/cmake/config-ix.cmake
index 083f1c98d0f16..a36fe8f2d27a0 100644
--- a/compiler-rt/cmake/config-ix.cmake
+++ b/compiler-rt/cmake/config-ix.cmake
@@ -482,6 +482,7 @@ if(APPLE)
   set(ORC_SUPPORTED_OS)
   set(UBSAN_SUPPORTED_OS osx)
   set(LSAN_SUPPORTED_OS osx)
+  set(DSAN_SUPPORTED_OS osx)
   set(STATS_SUPPORTED_OS osx)
 
   # FIXME: Support a general COMPILER_RT_ENABLE_OSX to match other platforms.
@@ -579,6 +580,7 @@ if(APPLE)
           list(APPEND ORC_SUPPORTED_OS ${platform}sim)
           list(APPEND UBSAN_SUPPORTED_OS ${platform}sim)
           list(APPEND LSAN_SUPPORTED_OS ${platform}sim)
+          list(APPEND DSAN_SUPPORTED_OS ${platform}sim)
           list(APPEND STATS_SUPPORTED_OS ${platform}sim)
         endif()
         foreach(arch ${DARWIN_${platform}sim_ARCHS})
@@ -614,6 +616,7 @@ if(APPLE)
           list(APPEND UBSAN_SUPPORTED_OS ${platform})
           list(APPEND TYSAN_SUPPORTED_OS ${platform})
           list(APPEND LSAN_SUPPORTED_OS ${platform})
+          list(APPEND DSAN_SUPPORTED_OS ${platform})
           list(APPEND STATS_SUPPORTED_OS ${platform})
         endif()
         foreach(arch ${DARWIN_${platform}_ARCHS})
@@ -636,6 +639,7 @@ if(APPLE)
     COMPILER_RT_SUPPORTED_ARCH
     )
   set(LSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH})
+  set(DSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH})
   set(UBSAN_COMMON_SUPPORTED_ARCH ${SANITIZER_COMMON_SUPPORTED_ARCH})
   set(ASAN_ABI_SUPPORTED_ARCH ${ALL_ASAN_ABI_SUPPORTED_ARCH})
   list_intersect(ASAN_SUPPORTED_ARCH
@@ -653,6 +657,9 @@ if(APPLE)
   list_intersect(LSAN_SUPPORTED_ARCH
     ALL_LSAN_SUPPORTED_ARCH
     SANITIZER_COMMON_SUPPORTED_ARCH)
+  list_intersect(DSAN_SUPPORTED_ARCH
+    ALL_DSAN_SUPPORTED_ARCH
+    SANITIZER_COMMON_SUPPORTED_ARCH)
   list_intersect(MSAN_SUPPORTED_ARCH
     ALL_MSAN_SUPPORTED_ARCH
     SANITIZER_COMMON_SUPPORTED_ARCH)
@@ -713,6 +720,8 @@ else()
   # supported by other sanitizers (even if they build into dummy object files).
   filter_available_targets(LSAN_COMMON_SUPPORTED_ARCH
     ${SANITIZER_COMMON_SUPPORTED_ARCH})
+  filter_available_targets(DSAN_COMMON_SUPPORTED_ARCH
+    ${SANITIZER_COMMON_SUPPORTED_ARCH})
   filter_available_targets(UBSAN_COMMON_SUPPORTED_ARCH
     ${ALL_UBSAN_SUPPORTED_ARCH})
   filter_available_targets(ASAN_SUPPORTED_ARCH ${ALL_ASAN_SUPPORTED_ARCH})
@@ -720,6 +729,7 @@ else()
   filter_available_targets(FUZZER_SUPPORTED_ARCH ${ALL_FUZZER_SUPPORTED_ARCH})
   filter_available_targets(DFSAN_SUPPORTED_ARCH ${ALL_DFSAN_SUPPORTED_ARCH})
   filter_available_targets(LSAN_SUPPORTED_ARCH ${ALL_LSAN_SUPPORTED_ARCH})
+  filter_available_targets(DSAN_SUPPORTED_ARCH ${ALL_DSAN_SUPPORTED_ARCH})
   filter_available_targets(MSAN_SUPPORTED_ARCH ${ALL_MSAN_SUPPORTED_ARCH})
   filter_available_targets(HWASAN_SUPPORTED_ARCH ${ALL_HWASAN_SUPPORTED_ARCH})
   filter_available_targets(MEMPROF_SUPPORTED_ARCH ${ALL_MEMPROF_SUPPORTED_ARCH})
@@ -832,6 +842,13 @@ else()
   set(COMPILER_RT_HAS_LSAN FALSE)
 endif()
 
+if (COMPILER_RT_HAS_SANITIZER_COMMON AND DSAN_SUPPORTED_ARCH AND
+    OS_NAME MATCHES "Android|Darwin|Linux|NetBSD|Fuchsia")
+  set(COMPILER_RT_HAS_DSAN TRUE)
+else()
+  set(COMPILER_RT_HAS_DSAN FALSE)
+endif()
+
 if (COMPILER_RT_HAS_SANITIZER_COMMON AND MSAN_SUPPORTED_ARCH AND
     OS_NAME MATCHES "Linux|FreeBSD|NetBSD")
   set(COMPILER_RT_HAS_MSAN TRUE)
diff --git a/compiler-rt/include/CMakeLists.txt b/compiler-rt/include/CMakeLists.txt
index eb998478b081b..1045a1836a9fb 100644
--- a/compiler-rt/include/CMakeLists.txt
+++ b/compiler-rt/include/CMakeLists.txt
@@ -5,6 +5,7 @@ if (COMPILER_RT_BUILD_SANITIZERS)
     sanitizer/common_interface_defs.h
     sanitizer/coverage_interface.h
     sanitizer/dfsan_interface.h
+    sanitizer/dsan_interface.h
     sanitizer/hwasan_interface.h
     sanitizer/linux_syscall_hooks.h
     sanitizer/lsan_interface.h
diff --git a/compiler-rt/include/sanitizer/dsan_interface.h b/compiler-rt/include/sanitizer/dsan_interface.h
new file mode 100644
index 0000000000000..a545b7206678e
--- /dev/null
+++ b/compiler-rt/include/sanitizer/dsan_interface.h
@@ -0,0 +1,30 @@
+//===-- sanitizer/dsan_interface.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 DoubleFreeSanitizer (DSan).
+//
+// Public interface header.
+//===----------------------------------------------------------------------===//
+#ifndef SANITIZER_DSAN_INTERFACE_H
+#define SANITIZER_DSAN_INTERFACE_H
+
+#include <sanitizer/common_interface_defs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This function may be optionally provided by user and should return
+// a string containing common sanitizer runtime options.
+const char *SANITIZER_CDECL __dsan_default_options(void);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // SANITIZER_DSAN_INTERFACE_H
diff --git a/compiler-rt/lib/CMakeLists.txt b/compiler-rt/lib/CMakeLists.txt
index a5b2fbb38762c..5fe86db866c33 100644
--- a/compiler-rt/lib/CMakeLists.txt
+++ b/compiler-rt/lib/CMakeLists.txt
@@ -45,6 +45,8 @@ if(COMPILER_RT_BUILD_SANITIZERS)
     add_subdirectory(stats)
     # Contains RTLSanCommon used even without COMPILER_RT_HAS_LSAN.
     add_subdirectory(lsan)
+    # Contains RTDSanCommon used even without COMPILER_RT_HAS_DSAN.
+    add_subdirectory(dsan)
     # Contains RTUbsan used even without COMPILER_RT_HAS_UBSAN.
     add_subdirectory(ubsan)
   endif()
diff --git a/compiler-rt/lib/dsan/.clang-format b/compiler-rt/lib/dsan/.clang-format
new file mode 100644
index 0000000000000..1f2a97030379d
--- /dev/null
+++ b/compiler-rt/lib/dsan/.clang-format
@@ -0,0 +1,3 @@
+BasedOnStyle: Google
+AllowShortIfStatementsOnASingleLine: false
+IndentPPDirectives: AfterHash
diff --git a/compiler-rt/lib/dsan/CMakeLists.txt b/compiler-rt/lib/dsan/CMakeLists.txt
new file mode 100644
index 0000000000000..616c28bc6add1
--- /dev/null
+++ b/compiler-rt/lib/dsan/CMakeLists.txt
@@ -0,0 +1,85 @@
+include_directories(..)
+
+set(DSAN_CFLAGS ${SANITIZER_COMMON_CFLAGS})
+append_rtti_flag(OFF DSAN_CFLAGS)
+
+# Too many existing bugs, needs cleanup.
+append_list_if(COMPILER_RT_HAS_WNO_FORMAT -Wno-format DSAN_CFLAGS)
+
+set(DSAN_COMMON_SOURCES
+  dsan_common.cpp
+  )
+
+set(DSAN_SOURCES
+  dsan.cpp
+  dsan_allocator.cpp
+  dsan_fuchsia.cpp
+  dsan_interceptors.cpp
+  dsan_linux.cpp
+  dsan_mac.cpp
+  dsan_malloc_mac.cpp
+  dsan_posix.cpp
+  dsan_preinit.cpp
+  dsan_thread.cpp
+  )
+
+set(DSAN_HEADERS
+  dsan.h
+  dsan_allocator.h
+  dsan_common.h
+  dsan_thread.h
+  )
+
+set(DSAN_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+# Shared DSan runtime functionality.
+add_compiler_rt_object_libraries(RTDSanCommon
+    OS ${SANITIZER_COMMON_SUPPORTED_OS}
+    ARCHS ${DSAN_COMMON_SUPPORTED_ARCH}
+    SOURCES ${DSAN_COMMON_SOURCES}
+    ADDITIONAL_HEADERS ${DSAN_HEADERS}
+    CFLAGS ${DSAN_CFLAGS})
+
+if(COMPILER_RT_HAS_DSAN)
+  add_compiler_rt_component(dsan)
+  if(APPLE)
+    set(DSAN_LINK_LIBS ${SANITIZER_COMMON_LINK_LIBS})
+
+    add_weak_symbols("dsan" WEAK_SYMBOL_LINK_FLAGS)
+    add_weak_symbols("sanitizer_common" WEAK_SYMBOL_LINK_FLAGS)
+
+    add_compiler_rt_runtime(clang_rt.dsan
+      SHARED
+      OS ${DSAN_SUPPORTED_OS}
+      ARCHS ${DSAN_SUPPORTED_ARCH}
+      SOURCES ${DSAN_SOURCES}
+      ADDITIONAL_HEADERS ${DSAN_HEADERS}
+      OBJECT_LIBS RTDSanCommon
+                  RTInterception
+                  RTSanitizerCommon
+                  RTSanitizerCommonLibc
+                  RTSanitizerCommonCoverage
+                  RTSanitizerCommonSymbolizer
+      CFLAGS ${DSAN_CFLAGS}
+      LINK_FLAGS ${SANITIZER_COMMON_LINK_FLAGS} ${WEAK_SYMBOL_LINK_FLAGS}
+      LINK_LIBS ${DSAN_LINK_LIBS}
+      PARENT_TARGET dsan)
+  else()
+    foreach(arch ${DSAN_SUPPORTED_ARCH})
+      add_compiler_rt_runtime(clang_rt.dsan
+        STATIC
+        ARCHS ${arch}
+        SOURCES ${DSAN_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}>
+                $<TARGET_OBJECTS:RTDSanCommon.${arch}>
+        ADDITIONAL_HEADERS ${DSAN_HEADERS}
+        CFLAGS ${DSAN_CFLAGS}
+        PARENT_TARGET dsan)
+    endforeach()
+  endif()
+endif()
diff --git a/compiler-rt/lib/dsan/dsan.cpp b/compiler-rt/lib/dsan/dsan.cpp
new file mode 100644
index 0000000000000..6c134122b120e
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan.cpp
@@ -0,0 +1,112 @@
+//=-- dsan.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 DoubleFreeSanitizer.
+// Standalone DSan RTL.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan.h"
+
+#include "dsan_allocator.h"
+#include "dsan_common.h"
+#include "dsan_thread.h"
+#include "sanitizer_common/sanitizer_flag_parser.h"
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_interface_internal.h"
+
+bool dsan_inited;
+bool dsan_init_is_running;
+
+namespace __dsan {
+
+///// Interface to the common DSan module. /////
+bool WordIsPoisoned(uptr addr) { return false; }
+
+}  // namespace __dsan
+
+void __sanitizer::BufferedStackTrace::UnwindImpl(uptr pc, uptr bp,
+                                                 void* context,
+                                                 bool request_fast,
+                                                 u32 max_depth) {
+  using namespace __dsan;
+  uptr stack_top = 0, stack_bottom = 0;
+  if (ThreadContextDsanBase* t = GetCurrentThread()) {
+    stack_top = t->stack_end();
+    stack_bottom = t->stack_begin();
+  }
+  if (SANITIZER_MIPS && !IsValidFrame(bp, stack_top, stack_bottom))
+    return;
+  bool fast = StackTrace::WillUseFastUnwind(request_fast);
+  Unwind(max_depth, pc, bp, context, stack_top, stack_bottom, fast);
+}
+
+using namespace __dsan;
+
+static void InitializeFlags() {
+  // Set all the default values.
+  SetCommonFlagsDefaults();
+  {
+    CommonFlags cf;
+    cf.CopyFrom(*common_flags());
+    cf.external_symbolizer_path = GetEnv("DSAN_SYMBOLIZER_PATH");
+    cf.malloc_context_size = 30;
+    cf.intercept_tls_get_addr = true;
+    cf.detect_leaks = false;
+    cf.exitcode = 77;
+    OverrideCommonFlags(cf);
+  }
+
+  FlagParser parser;
+  RegisterCommonFlags(&parser);
+
+  // Override from user-specified string.
+  const char* dsan_default_options = __dsan_default_options();
+  parser.ParseString(dsan_default_options);
+  parser.ParseStringFromEnv("DSAN_OPTIONS");
+
+  InitializeCommonFlags();
+
+  if (Verbosity())
+    ReportUnrecognizedFlags();
+
+  if (common_flags()->help)
+    parser.PrintFlagDescriptions();
+
+  __sanitizer_set_report_path(common_flags()->log_path);
+}
+
+extern "C" void __dsan_init() {
+  CHECK(!dsan_init_is_running);
+  if (dsan_inited)
+    return;
+  dsan_init_is_running = true;
+  SanitizerToolName = "DoubleFreeSanitizer";
+  CacheBinaryName();
+  AvoidCVE_2016_2143();
+  InitializeFlags();
+  InitializePlatformEarly();
+  InitCommonDsan();
+  InitializeAllocator();
+  ReplaceSystemMalloc();
+  InitializeInterceptors();
+  InitializeThreads();
+  InstallDeadlySignalHandlers(DsanOnDeadlySignal);
+  InitializeMainThread();
+  InstallAtForkHandler();
+
+  InitializeCoverage(common_flags()->coverage, common_flags()->coverage_dir);
+
+  dsan_inited = true;
+  dsan_init_is_running = false;
+}
+
+extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __sanitizer_print_stack_trace() {
+  GET_STACK_TRACE_FATAL;
+  stack.Print();
+}
diff --git a/compiler-rt/lib/dsan/dsan.h b/compiler-rt/lib/dsan/dsan.h
new file mode 100644
index 0000000000000..bd7d1eb9c24fe
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan.h
@@ -0,0 +1,56 @@
+//=-- dsan.h --------------------------------------------------------------===//
+//
+// 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 DoubleFreeSanitizer.
+// Private header for standalone DSan RTL.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan_thread.h"
+#if SANITIZER_POSIX
+#  include "dsan_posix.h"
+#elif SANITIZER_FUCHSIA
+#  include "dsan_fuchsia.h"
+#endif
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+#define GET_STACK_TRACE(max_size, fast)                                        \
+  __sanitizer::BufferedStackTrace stack;                                       \
+  stack.Unwind(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME(), nullptr, fast, \
+               max_size);
+
+#define GET_STACK_TRACE_FATAL \
+  GET_STACK_TRACE(kStackTraceMax, common_flags()->fast_unwind_on_fatal)
+
+#define GET_STACK_TRACE_MALLOC                                      \
+  GET_STACK_TRACE(__sanitizer::common_flags()->malloc_context_size, \
+                  common_flags()->fast_unwind_on_malloc)
+
+#define GET_STACK_TRACE_THREAD GET_STACK_TRACE(kStackTraceMax, true)
+
+namespace __dsan {
+
+void InitializeInterceptors();
+void ReplaceSystemMalloc();
+void DsanOnDeadlySignal(int signo, void* siginfo, void* context);
+void InstallAtForkHandler();
+
+#define ENSURE_DSAN_INITED        \
+  do {                            \
+    CHECK(!dsan_init_is_running); \
+    if (!dsan_inited)             \
+      __dsan_init();              \
+  } while (0)
+
+}  // namespace __dsan
+
+extern bool dsan_inited;
+extern bool dsan_init_is_running;
+
+extern "C" SANITIZER_INTERFACE_ATTRIBUTE void __dsan_init();
diff --git a/compiler-rt/lib/dsan/dsan_allocator.cpp b/compiler-rt/lib/dsan/dsan_allocator.cpp
new file mode 100644
index 0000000000000..471fb18415a03
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_allocator.cpp
@@ -0,0 +1,501 @@
+//=-- dsan_allocator.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 DoubleFreeSanitizer.
+// See dsan_allocator.h for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan_allocator.h"
+
+#include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
+#include "sanitizer_common/sanitizer_allocator_interface.h"
+#include "sanitizer_common/sanitizer_allocator_report.h"
+#include "sanitizer_common/sanitizer_atomic.h"
+#include "sanitizer_common/sanitizer_errno.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_report_decorator.h"
+#include "sanitizer_common/sanitizer_stackdepot.h"
+#include "sanitizer_common/sanitizer_stacktrace.h"
+
+extern "C" void* memset(void* ptr, int value, uptr num);
+
+namespace __dsan {
+#if defined(__i386__) || defined(__arm__)
+static const uptr kMaxAllowedMallocSize = 1ULL << 30;
+#elif defined(__mips64) || defined(__aarch64__)
+static const uptr kMaxAllowedMallocSize = 4ULL << 30;
+#else
+static const uptr kMaxAllowedMallocSize = 1ULL << 40;
+#endif
+
+static Allocator allocator;
+
+static uptr max_malloc_size;
+
+struct SecondaryTombstone {
+  void* ptr;
+  u32 alloc_stack_id;
+  u32 free_stack_id;
+};
+
+static constexpr uptr kSecondaryTombstoneLimit = 65536;
+static Mutex secondary_tombstones_mutex;
+static InternalMmapVector<SecondaryTombstone> secondary_tombstones;
+static uptr next_secondary_tombstone;
+
+static constexpr uptr kPrimaryQuarantineSize = 1024;
+static Mutex primary_quarantine_mutex;
+static InternalMmapVector<void*> primary_quarantine;
+static uptr next_primary_quarantine;
+
+static bool FindSecondaryTombstone(void* p, SecondaryTombstone* result) {
+  Lock lock(&secondary_tombstones_mutex);
+  for (uptr i = 0; i != secondary_tombstones.size(); ++i) {
+    if (secondary_tombstones[i].ptr == p) {
+      *result = secondary_tombstones[i];
+      return true;
+    }
+  }
+  return false;
+}
+
+static void AddSecondaryTombstone(void* p, const ChunkMetadata* m) {
+  Lock lock(&secondary_tombstones_mutex);
+  const SecondaryTombstone tombstone = {p, m->alloc_stack_id, m->free_stack_id};
+  if (secondary_tombstones.size() < kSecondaryTombstoneLimit) {
+    secondary_tombstones.push_back(tombstone);
+    return;
+  }
+  secondary_tombstones[next_secondary_tombstone] = tombstone;
+  next_secondary_tombstone =
+      (next_secondary_tombstone + 1) % kSecondaryTombstoneLimit;
+}
+
+static void RemoveSecondaryTombstone(void* p) {
+  Lock lock(&secondary_tombstones_mutex);
+  for (uptr i = 0; i != secondary_tombstones.size(); ++i) {
+    if (secondary_tombstones[i].ptr != p)
+      continue;
+    secondary_tombstones[i] = secondary_tombstones.back();
+    secondary_tombstones.pop_back();
+    return;
+  }
+}
+
+static void QuarantinePrimary(void* p) {
+  void* released = nullptr;
+  {
+    Lock lock(&primary_quarantine_mutex);
+    if (primary_quarantine.size() < kPrimaryQuarantineSize) {
+      primary_quarantine.push_back(p);
+    } else {
+      released = primary_quarantine[next_primary_quarantine];
+      primary_quarantine[next_primary_quarantine] = p;
+      next_primary_quarantine =
+          (next_primary_quarantine + 1) % kPrimaryQuarantineSize;
+    }
+  }
+  if (released)
+    allocator.Deallocate(GetAllocatorCache(), released);
+}
+
+void InitializeAllocator() {
+  SetAllocatorMayReturnNull(common_flags()->allocator_may_return_null);
+  allocator.InitLinkerInitialized(
+      common_flags()->allocator_release_to_os_interval_ms);
+  if (common_flags()->max_allocation_size_mb)
+    max_malloc_size = Min(common_flags()->max_allocation_size_mb << 20,
+                          kMaxAllowedMallocSize);
+  else
+    max_malloc_size = kMaxAllowedMallocSize;
+}
+
+void AllocatorThreadStart() { allocator.InitCache(GetAllocatorCache()); }
+
+void AllocatorThreadFinish() {
+  allocator.SwallowCache(GetAllocatorCache());
+  allocator.DestroyCache(GetAllocatorCache());
+}
+
+static ChunkMetadata* Metadata(const void* p) {
+  return reinterpret_cast<ChunkMetadata*>(allocator.GetMetaData(p));
+}
+
+static void RegisterAllocation(const StackTrace& stack, void* p, uptr size) {
+  if (!p)
+    return;
+  if (!allocator.FromPrimary(p))
+    RemoveSecondaryTombstone(p);
+  ChunkMetadata* m = Metadata(p);
+  CHECK(m);
+  m->alloc_stack_id = StackDepotPut(stack);
+  m->free_stack_id = 0;
+  m->requested_size = size;
+  atomic_store(reinterpret_cast<atomic_uint8_t*>(m), kChunkAllocated,
+               memory_order_release);
+  RunMallocHooks(p, size);
+}
+
+// Report double-free and terminate.
+static void NORETURN ReportDoubleFree(void* p, u32 alloc_stack_id,
+                                      u32 first_free_stack_id,
+                                      const StackTrace& free_stack) {
+  class Decorator : public __sanitizer::SanitizerCommonDecorator {
+   public:
+    Decorator() : SanitizerCommonDecorator() {}
+    const char* Error() { return Red(); }
+    const char* Info() { return Blue(); }
+  };
+
+  Decorator d;
+  Printf("\n");
+  Printf("%s", d.Error());
+  Report("ERROR: DoubleFreeSanitizer: double-free on address %p\n", p);
+  Printf("%s", d.Default());
+
+  // Print the second free (current) backtrace.
+  Printf("\n");
+  Printf("%s", d.Info());
+  Printf("Second free (the invalid free) of address %p:\n", p);
+  Printf("%s", d.Default());
+  free_stack.Print();
+
+  // Print the first free backtrace.
+  if (first_free_stack_id) {
+    Printf("\n");
+    Printf("%s", d.Info());
+    Printf("First free of address %p:\n", p);
+    Printf("%s", d.Default());
+    StackDepotGet(first_free_stack_id).Print();
+  }
+
+  // Print the original allocation backtrace.
+  if (alloc_stack_id) {
+    Printf("\n");
+    Printf("%s", d.Info());
+    Printf("Original allocation of address %p:\n", p);
+    Printf("%s", d.Default());
+    StackDepotGet(alloc_stack_id).Print();
+  }
+
+  Printf("\n");
+  Printf("SUMMARY: DoubleFreeSanitizer: double-free on address %p\n", p);
+  Die();
+}
+
+static void NORETURN ReportInvalidFree(void* p, const StackTrace& stack) {
+  Report("ERROR: DoubleFreeSanitizer: invalid free on address %p\n", p);
+  stack.Print();
+  Die();
+}
+
+static ChunkMetadata* GetChunkMetadata(void* p, const StackTrace& stack) {
+  if (!p)
+    return nullptr;
+  if (!allocator.PointerIsMine(p)) {
+    SecondaryTombstone tombstone = {};
+    if (FindSecondaryTombstone(p, &tombstone))
+      ReportDoubleFree(p, tombstone.alloc_stack_id, tombstone.free_stack_id,
+                       stack);
+    ReportInvalidFree(p, stack);
+  }
+  if (allocator.GetBlockBegin(p) != p)
+    ReportInvalidFree(p, stack);
+  ChunkMetadata* m = Metadata(p);
+  if (atomic_load(reinterpret_cast<atomic_uint8_t*>(m), memory_order_acquire) ==
+      kChunkInvalid)
+    ReportInvalidFree(p, stack);
+  return m;
+}
+
+static void RegisterDeallocation(const StackTrace& stack, void* p) {
+  ChunkMetadata* m = GetChunkMetadata(p, stack);
+  if (!m)
+    return;
+
+  u8 expected = kChunkAllocated;
+  if (!atomic_compare_exchange_strong(reinterpret_cast<atomic_uint8_t*>(m),
+                                      &expected, kChunkFreeing,
+                                      memory_order_acquire)) {
+    while (expected == kChunkFreeing)
+      expected = atomic_load(reinterpret_cast<atomic_uint8_t*>(m),
+                             memory_order_acquire);
+    if (expected == kChunkFreed)
+      ReportDoubleFree(p, m->alloc_stack_id, m->free_stack_id, stack);
+    ReportInvalidFree(p, stack);
+  }
+
+  m->free_stack_id = StackDepotPut(stack);
+  atomic_store(reinterpret_cast<atomic_uint8_t*>(m), kChunkFreed,
+               memory_order_release);
+  RunFreeHooks(p);
+}
+
+static void* ReportAllocationSizeTooBig(uptr size, const StackTrace& stack) {
+  if (AllocatorMayReturnNull()) {
+    Report("WARNING: DoubleFreeSanitizer failed to allocate 0x%zx bytes\n",
+           size);
+    return nullptr;
+  }
+  ReportAllocationSizeTooBig(size, max_malloc_size, &stack);
+}
+
+void* Allocate(const StackTrace& stack, uptr size, uptr alignment,
+               bool cleared) {
+  if (size == 0)
+    size = 1;
+  if (size > max_malloc_size)
+    return ReportAllocationSizeTooBig(size, stack);
+  if (UNLIKELY(IsRssLimitExceeded())) {
+    if (AllocatorMayReturnNull())
+      return nullptr;
+    ReportRssLimitExceeded(&stack);
+  }
+  void* p = allocator.Allocate(GetAllocatorCache(), size, alignment);
+  if (UNLIKELY(!p)) {
+    SetAllocatorOutOfMemory();
+    if (AllocatorMayReturnNull())
+      return nullptr;
+    ReportOutOfMemory(size, &stack);
+  }
+  if (cleared && allocator.FromPrimary(p))
+    memset(p, 0, size);
+  RegisterAllocation(stack, p, size);
+  return p;
+}
+
+static void* Calloc(uptr nmemb, uptr size, const StackTrace& stack) {
+  if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
+    if (AllocatorMayReturnNull())
+      return nullptr;
+    ReportCallocOverflow(nmemb, size, &stack);
+  }
+  size *= nmemb;
+  return Allocate(stack, size, 1, true);
+}
+
+void Deallocate(const StackTrace& stack, void* p) {
+  if (!p)
+    return;
+  RegisterDeallocation(stack, p);
+  if (allocator.FromPrimary(p)) {
+    QuarantinePrimary(p);
+  } else {
+    AddSecondaryTombstone(p, Metadata(p));
+    allocator.Deallocate(GetAllocatorCache(), p);
+  }
+}
+
+void* Reallocate(const StackTrace& stack, void* p, uptr new_size,
+                 uptr alignment) {
+  if (!p)
+    return Allocate(stack, new_size, alignment, kAlwaysClearMemory);
+  if (!new_size) {
+    Deallocate(stack, p);
+    return nullptr;
+  }
+  if (new_size > max_malloc_size) {
+    ReportAllocationSizeTooBig(new_size, stack);
+    return nullptr;
+  }
+  ChunkMetadata* m = GetChunkMetadata(p, stack);
+  if (atomic_load(reinterpret_cast<atomic_uint8_t*>(m), memory_order_acquire) !=
+      kChunkAllocated)
+    ReportDoubleFree(p, m->alloc_stack_id, m->free_stack_id, stack);
+  const uptr old_size = m->requested_size;
+  void* new_p = Allocate(stack, new_size, alignment, kAlwaysClearMemory);
+  if (!new_p)
+    return nullptr;
+  internal_memcpy(new_p, p, Min(old_size, new_size));
+  Deallocate(stack, p);
+  return new_p;
+}
+
+void GetAllocatorCacheRange(uptr* begin, uptr* end) {
+  *begin = (uptr)GetAllocatorCache();
+  *end = *begin + sizeof(AllocatorCache);
+}
+
+static const void* GetMallocBegin(const void* p) {
+  if (!p)
+    return nullptr;
+  void* beg = allocator.GetBlockBegin(p);
+  if (!beg)
+    return nullptr;
+  ChunkMetadata* m = Metadata(beg);
+  if (!m)
+    return nullptr;
+  if (atomic_load(reinterpret_cast<atomic_uint8_t*>(m), memory_order_acquire) !=
+      kChunkAllocated)
+    return nullptr;
+  if (m->requested_size == 0)
+    return nullptr;
+  return (const void*)beg;
+}
+
+uptr GetMallocUsableSize(const void* p) {
+  if (!p)
+    return 0;
+  ChunkMetadata* m = Metadata(p);
+  if (!m)
+    return 0;
+  return m->requested_size;
+}
+
+uptr GetMallocUsableSizeFast(const void* p) {
+  return Metadata(p)->requested_size;
+}
+
+int dsan_posix_memalign(void** memptr, uptr alignment, uptr size,
+                        const StackTrace& stack) {
+  if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) {
+    if (AllocatorMayReturnNull())
+      return errno_EINVAL;
+    ReportInvalidPosixMemalignAlignment(alignment, &stack);
+  }
+  void* ptr = Allocate(stack, size, alignment, kAlwaysClearMemory);
+  if (UNLIKELY(!ptr))
+    return errno_ENOMEM;
+  CHECK(IsAligned((uptr)ptr, alignment));
+  *memptr = ptr;
+  return 0;
+}
+
+void* dsan_aligned_alloc(uptr alignment, uptr size, const StackTrace& stack) {
+  if (UNLIKELY(!CheckAlignedAllocAlignmentAndSize(alignment, size))) {
+    errno = errno_EINVAL;
+    if (AllocatorMayReturnNull())
+      return nullptr;
+    ReportInvalidAlignedAllocAlignment(size, alignment, &stack);
+  }
+  return SetErrnoOnNull(Allocate(stack, size, alignment, kAlwaysClearMemory));
+}
+
+void* dsan_memalign(uptr alignment, uptr size, const StackTrace& stack) {
+  if (UNLIKELY(!IsPowerOfTwo(alignment))) {
+    errno = errno_EINVAL;
+    if (AllocatorMayReturnNull())
+      return nullptr;
+    ReportInvalidAllocationAlignment(alignment, &stack);
+  }
+  return SetErrnoOnNull(Allocate(stack, size, alignment, kAlwaysClearMemory));
+}
+
+void* dsan_malloc(uptr size, const StackTrace& stack) {
+  return SetErrnoOnNull(Allocate(stack, size, 1, kAlwaysClearMemory));
+}
+
+void dsan_free(void* p, const StackTrace& stack) { Deallocate(stack, p); }
+
+void dsan_free_sized(void* p, uptr, const StackTrace& stack) {
+  Deallocate(stack, p);
+}
+
+void dsan_free_aligned_sized(void* p, uptr, uptr, const StackTrace& stack) {
+  Deallocate(stack, p);
+}
+
+void* dsan_realloc(void* p, uptr size, const StackTrace& stack) {
+  return SetErrnoOnNull(Reallocate(stack, p, size, 1));
+}
+
+void* dsan_reallocarray(void* ptr, uptr nmemb, uptr size,
+                        const StackTrace& stack) {
+  if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
+    errno = errno_ENOMEM;
+    if (AllocatorMayReturnNull())
+      return nullptr;
+    ReportReallocArrayOverflow(nmemb, size, &stack);
+  }
+  return dsan_realloc(ptr, nmemb * size, stack);
+}
+
+void* dsan_calloc(uptr nmemb, uptr size, const StackTrace& stack) {
+  return SetErrnoOnNull(Calloc(nmemb, size, stack));
+}
+
+void* dsan_valloc(uptr size, const StackTrace& stack) {
+  return SetErrnoOnNull(
+      Allocate(stack, size, GetPageSizeCached(), kAlwaysClearMemory));
+}
+
+void* dsan_pvalloc(uptr size, const StackTrace& stack) {
+  uptr PageSize = GetPageSizeCached();
+  if (UNLIKELY(CheckForPvallocOverflow(size, PageSize))) {
+    errno = errno_ENOMEM;
+    if (AllocatorMayReturnNull())
+      return nullptr;
+    ReportPvallocOverflow(size, &stack);
+  }
+  size = size ? RoundUpTo(size, PageSize) : PageSize;
+  return SetErrnoOnNull(Allocate(stack, size, PageSize, kAlwaysClearMemory));
+}
+
+uptr dsan_mz_size(const void* p) { return GetMallocUsableSize(p); }
+
+void LockAllocator() { allocator.ForceLock(); }
+
+void UnlockAllocator() { allocator.ForceUnlock(); }
+
+}  // namespace __dsan
+
+using namespace __dsan;
+
+extern "C" {
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_current_allocated_bytes() {
+  uptr stats[AllocatorStatCount];
+  allocator.GetStats(stats);
+  return stats[AllocatorStatAllocated];
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_heap_size() {
+  uptr stats[AllocatorStatCount];
+  allocator.GetStats(stats);
+  return stats[AllocatorStatMapped];
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_free_bytes() { return 1; }
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_unmapped_bytes() { return 0; }
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_estimated_allocated_size(uptr size) { return size; }
+
+SANITIZER_INTERFACE_ATTRIBUTE
+int __sanitizer_get_ownership(const void* p) {
+  return GetMallocBegin(p) != nullptr;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+const void* __sanitizer_get_allocated_begin(const void* p) {
+  return GetMallocBegin(p);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_allocated_size(const void* p) {
+  return GetMallocUsableSize(p);
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+uptr __sanitizer_get_allocated_size_fast(const void* p) {
+  DCHECK_EQ(p, __sanitizer_get_allocated_begin(p));
+  uptr ret = GetMallocUsableSizeFast(p);
+  DCHECK_EQ(ret, __sanitizer_get_allocated_size(p));
+  return ret;
+}
+
+SANITIZER_INTERFACE_ATTRIBUTE
+void __sanitizer_purge_allocator() { allocator.ForceReleaseToOS(); }
+
+}  // extern "C"
diff --git a/compiler-rt/lib/dsan/dsan_allocator.h b/compiler-rt/lib/dsan/dsan_allocator.h
new file mode 100644
index 0000000000000..31ee1071b2811
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_allocator.h
@@ -0,0 +1,150 @@
+//=-- dsan_allocator.h ----------------------------------------------------===//
+//
+// 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 DoubleFreeSanitizer.
+// Allocator for standalone DSan.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef DSAN_ALLOCATOR_H
+#define DSAN_ALLOCATOR_H
+
+#include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+
+namespace __dsan {
+
+void* Allocate(const StackTrace& stack, uptr size, uptr alignment,
+               bool cleared);
+void Deallocate(const StackTrace& stack, void* p);
+void* Reallocate(const StackTrace& stack, void* p, uptr new_size,
+                 uptr alignment);
+uptr GetMallocUsableSize(const void* p);
+
+void GetAllocatorCacheRange(uptr* begin, uptr* end);
+void AllocatorThreadStart();
+void AllocatorThreadFinish();
+void InitializeAllocator();
+
+const bool kAlwaysClearMemory = true;
+
+struct ChunkMetadata {
+  u8 state;  // Must be first. See ChunkState.
+#if SANITIZER_WORDSIZE == 64
+  uptr requested_size : 56;
+#else
+  uptr requested_size : 32;
+  uptr padding2 : 24;
+#endif
+  u32 alloc_stack_id;  // Stack trace of the allocation
+  u32 free_stack_id;   // Stack trace of the first free
+};
+
+enum ChunkState : u8 {
+  kChunkInvalid,
+  kChunkAllocated,
+  kChunkFreeing,
+  kChunkFreed,
+};
+
+#if !SANITIZER_CAN_USE_ALLOCATOR64
+template <typename AddressSpaceViewTy>
+struct AP32 {
+  static const uptr kSpaceBeg = SANITIZER_MMAP_BEGIN;
+  static const u64 kSpaceSize = SANITIZER_MMAP_RANGE_SIZE;
+  static const uptr kMetadataSize = sizeof(ChunkMetadata);
+  typedef __sanitizer::CompactSizeClassMap SizeClassMap;
+  static const uptr kRegionSizeLog = 20;
+  using AddressSpaceView = AddressSpaceViewTy;
+  typedef NoOpMapUnmapCallback MapUnmapCallback;
+  static const uptr kFlags = 0;
+};
+template <typename AddressSpaceView>
+using PrimaryAllocatorASVT = SizeClassAllocator32<AP32<AddressSpaceView>>;
+using PrimaryAllocator = PrimaryAllocatorASVT<LocalAddressSpaceView>;
+#else
+#  if SANITIZER_FUCHSIA || defined(__powerpc64__)
+const uptr kAllocatorSpace = ~(uptr)0;
+#    if SANITIZER_RISCV64
+// See the comments in compiler-rt/lib/asan/asan_allocator.h for why these
+// values were chosen.
+const uptr kAllocatorSize = UINT64_C(1) << 33;  // 8GB
+using DSanSizeClassMap = SizeClassMap</*kNumBits=*/2,
+                                      /*kMinSizeLog=*/5,
+                                      /*kMidSizeLog=*/8,
+                                      /*kMaxSizeLog=*/18,
+                                      /*kNumCachedHintT=*/8,
+                                      /*kMaxBytesCachedLog=*/10>;
+static_assert(DSanSizeClassMap::kNumClassesRounded <= 32,
+              "32 size classes is the optimal number to ensure tests run "
+              "efficiently on Fuchsia.");
+#    else
+const uptr kAllocatorSize = 0x40000000000ULL;  // 4T.
+using DSanSizeClassMap = DefaultSizeClassMap;
+#    endif
+#  elif SANITIZER_RISCV64
+const uptr kAllocatorSpace = ~(uptr)0;
+const uptr kAllocatorSize = 0x2000000000ULL;  // 128G.
+using DSanSizeClassMap = DefaultSizeClassMap;
+#  elif SANITIZER_APPLE
+const uptr kAllocatorSpace = 0x600000000000ULL;
+const uptr kAllocatorSize = 0x40000000000ULL;  // 4T.
+using DSanSizeClassMap = DefaultSizeClassMap;
+#  elif SANITIZER_ANDROID && defined(__aarch64__)
+const uptr kAllocatorSpace = 0x3000000000ULL;
+const uptr kAllocatorSize = 0x2000000000ULL;
+using DSanSizeClassMap = VeryCompactSizeClassMap;
+#  else
+const uptr kAllocatorSpace = 0x500000000000ULL;
+const uptr kAllocatorSize = 0x40000000000ULL;  // 4T.
+using DSanSizeClassMap = DefaultSizeClassMap;
+#  endif
+template <typename AddressSpaceViewTy>
+struct AP64 {  // Allocator64 parameters. Deliberately using a short name.
+  static const uptr kSpaceBeg = kAllocatorSpace;
+  static const uptr kSpaceSize = kAllocatorSize;
+  static const uptr kMetadataSize = sizeof(ChunkMetadata);
+  using SizeClassMap = DSanSizeClassMap;
+  typedef NoOpMapUnmapCallback MapUnmapCallback;
+  static const uptr kFlags = 0;
+  using AddressSpaceView = AddressSpaceViewTy;
+};
+
+template <typename AddressSpaceView>
+using PrimaryAllocatorASVT = SizeClassAllocator64<AP64<AddressSpaceView>>;
+using PrimaryAllocator = PrimaryAllocatorASVT<LocalAddressSpaceView>;
+#endif
+
+template <typename AddressSpaceView>
+using AllocatorASVT = CombinedAllocator<PrimaryAllocatorASVT<AddressSpaceView>>;
+using Allocator = AllocatorASVT<LocalAddressSpaceView>;
+using AllocatorCache = Allocator::AllocatorCache;
+
+Allocator::AllocatorCache* GetAllocatorCache();
+
+int dsan_posix_memalign(void** memptr, uptr alignment, uptr size,
+                        const StackTrace& stack);
+void* dsan_aligned_alloc(uptr alignment, uptr size, const StackTrace& stack);
+void* dsan_memalign(uptr alignment, uptr size, const StackTrace& stack);
+void* dsan_malloc(uptr size, const StackTrace& stack);
+void dsan_free(void* p, const StackTrace& stack);
+void dsan_free_sized(void* p, uptr size, const StackTrace& stack);
+void dsan_free_aligned_sized(void* p, uptr alignment, uptr size,
+                             const StackTrace& stack);
+void* dsan_realloc(void* p, uptr size, const StackTrace& stack);
+void* dsan_reallocarray(void* p, uptr nmemb, uptr size,
+                        const StackTrace& stack);
+void* dsan_calloc(uptr nmemb, uptr size, const StackTrace& stack);
+void* dsan_valloc(uptr size, const StackTrace& stack);
+void* dsan_pvalloc(uptr size, const StackTrace& stack);
+uptr dsan_mz_size(const void* p);
+
+}  // namespace __dsan
+
+#endif  // DSAN_ALLOCATOR_H
diff --git a/compiler-rt/lib/dsan/dsan_common.cpp b/compiler-rt/lib/dsan/dsan_common.cpp
new file mode 100644
index 0000000000000..4a8a454075861
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_common.cpp
@@ -0,0 +1,31 @@
+//=-- dsan_common.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 DoubleFreeSanitizer.
+// Implementation of common double-free checking functionality.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan_common.h"
+
+#include "sanitizer_common/sanitizer_common.h"
+
+namespace __dsan {
+
+void InitCommonDsan() {
+  // DoubleFreeSanitizer doesn't need complex initialization.
+  // Detection is done inline in RegisterDeallocation.
+}
+
+}  // namespace __dsan
+
+extern "C" {
+SANITIZER_INTERFACE_WEAK_DEF(const char*, __dsan_default_options, void) {
+  return "";
+}
+}  // extern "C"
diff --git a/compiler-rt/lib/dsan/dsan_common.h b/compiler-rt/lib/dsan/dsan_common.h
new file mode 100644
index 0000000000000..63bf7fa5156ec
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_common.h
@@ -0,0 +1,103 @@
+//=-- dsan_common.h -------------------------------------------------------===//
+//
+// 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 DoubleFreeSanitizer.
+// Private DSan header.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef DSAN_COMMON_H
+#define DSAN_COMMON_H
+
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_platform.h"
+#include "sanitizer_common/sanitizer_range.h"
+#include "sanitizer_common/sanitizer_stackdepot.h"
+#include "sanitizer_common/sanitizer_symbolizer.h"
+#include "sanitizer_common/sanitizer_thread_registry.h"
+
+// DoubleFreeSanitizer can run on most platforms that support sanitizers.
+#if SANITIZER_ANDROID && (__ANDROID_API__ < 28 || defined(__arm__))
+#  define CAN_SANITIZE_DOUBLE_FREE 0
+#elif (SANITIZER_LINUX || SANITIZER_APPLE) && (SANITIZER_WORDSIZE == 64) && \
+    (defined(__x86_64__) || defined(__mips64) || defined(__aarch64__) ||    \
+     defined(__powerpc64__) || defined(__s390x__))
+#  define CAN_SANITIZE_DOUBLE_FREE 1
+#elif defined(__i386__) && (SANITIZER_LINUX || SANITIZER_APPLE)
+#  define CAN_SANITIZE_DOUBLE_FREE 1
+#elif defined(__arm__) && SANITIZER_LINUX
+#  define CAN_SANITIZE_DOUBLE_FREE 1
+#elif defined(__hexagon__) && SANITIZER_LINUX
+#  define CAN_SANITIZE_DOUBLE_FREE 1
+#elif SANITIZER_LOONGARCH64 && SANITIZER_LINUX
+#  define CAN_SANITIZE_DOUBLE_FREE 1
+#elif SANITIZER_RISCV64 && SANITIZER_LINUX
+#  define CAN_SANITIZE_DOUBLE_FREE 1
+#elif SANITIZER_NETBSD || SANITIZER_FUCHSIA
+#  define CAN_SANITIZE_DOUBLE_FREE 1
+#else
+#  define CAN_SANITIZE_DOUBLE_FREE 0
+#endif
+
+namespace __sanitizer {
+class ThreadRegistry;
+class ThreadContextBase;
+struct DTLS;
+}  // namespace __sanitizer
+
+namespace __dsan {
+
+// Returns true if [addr, addr + sizeof(void *)) is poisoned.
+bool WordIsPoisoned(uptr addr);
+
+//// --------------------------------------------------------------------------
+//// Thread prototypes.
+//// --------------------------------------------------------------------------
+
+void LockThreads() SANITIZER_NO_THREAD_SAFETY_ANALYSIS;
+void UnlockThreads() SANITIZER_NO_THREAD_SAFETY_ANALYSIS;
+void EnsureMainThreadIDIsCorrect();
+
+bool GetThreadRangesLocked(ThreadID os_id, uptr* stack_begin, uptr* stack_end,
+                           uptr* tls_begin, uptr* tls_end, uptr* cache_begin,
+                           uptr* cache_end, DTLS** dtls);
+void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr>* caches);
+void GetThreadExtraStackRangesLocked(InternalMmapVector<Range>* ranges);
+void GetThreadExtraStackRangesLocked(ThreadID os_id,
+                                     InternalMmapVector<Range>* ranges);
+void GetAdditionalThreadContextPtrsLocked(InternalMmapVector<uptr>* ptrs);
+void GetRunningThreadsLocked(InternalMmapVector<ThreadID>* threads);
+void PrintThreads();
+
+//// --------------------------------------------------------------------------
+//// Allocator prototypes.
+//// --------------------------------------------------------------------------
+
+void LockAllocator();
+void UnlockAllocator();
+
+void GetAllocatorCacheRange(uptr* begin, uptr* end);
+
+void InitCommonDsan();
+
+// Forward declaration - defined in dsan_thread.h
+class ThreadContextDsanBase;
+
+ThreadContextDsanBase* GetCurrentThread();
+void SetCurrentThread(ThreadContextDsanBase* tctx);
+
+}  // namespace __dsan
+
+extern "C" {
+SANITIZER_INTERFACE_ATTRIBUTE SANITIZER_WEAK_ATTRIBUTE const char*
+__dsan_default_options();
+
+}  // extern "C"
+
+#endif  // DSAN_COMMON_H
diff --git a/compiler-rt/lib/dsan/dsan_fuchsia.cpp b/compiler-rt/lib/dsan/dsan_fuchsia.cpp
new file mode 100644
index 0000000000000..f4c698e3574af
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_fuchsia.cpp
@@ -0,0 +1,131 @@
+//=-- dsan_fuchsia.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 DoubleFreeSanitizer.
+// Standalone DSan RTL code specific to Fuchsia.
+//
+//===---------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if SANITIZER_FUCHSIA
+#  include <zircon/sanitizer.h>
+
+#  include "dsan.h"
+#  include "dsan_allocator.h"
+
+using namespace __dsan;
+
+namespace __sanitizer {
+// DSan doesn't need to do anything else special in the startup hook.
+void EarlySanitizerInit() {}
+}  // namespace __sanitizer
+
+namespace __dsan {
+
+void DsanOnDeadlySignal(int signo, void* siginfo, void* context) {}
+
+ThreadContext::ThreadContext(int tid) : ThreadContextDsanBase(tid) {}
+
+struct OnCreatedArgs {
+  uptr stack_begin, stack_end;
+};
+
+// On Fuchsia, the stack bounds of a new thread are available before
+// the thread itself has started running.
+void ThreadContext::OnCreated(void* arg) {
+  // Stack bounds passed through from __sanitizer_before_thread_create_hook
+  // or InitializeMainThread.
+  auto args = reinterpret_cast<const OnCreatedArgs*>(arg);
+  stack_begin_ = args->stack_begin;
+  stack_end_ = args->stack_end;
+}
+
+struct OnStartedArgs {
+  uptr cache_begin, cache_end;
+};
+
+void ThreadContext::OnStarted(void* arg) {
+  ThreadContextDsanBase::OnStarted(arg);
+  auto args = reinterpret_cast<const OnStartedArgs*>(arg);
+  cache_begin_ = args->cache_begin;
+  cache_end_ = args->cache_end;
+}
+
+void ThreadStart(u32 tid) {
+  OnStartedArgs args;
+  GetAllocatorCacheRange(&args.cache_begin, &args.cache_end);
+  CHECK_EQ(args.cache_end - args.cache_begin, sizeof(AllocatorCache));
+  ThreadContextDsanBase::ThreadStart(tid, GetTid(), ThreadType::Regular, &args);
+}
+
+void InitializeMainThread() {
+  OnCreatedArgs args;
+  __sanitizer::GetThreadStackTopAndBottom(true, &args.stack_end,
+                                          &args.stack_begin);
+  u32 tid = ThreadCreate(kMainTid, true, &args);
+  CHECK_EQ(tid, 0);
+  ThreadStart(tid);
+}
+
+void GetAllThreadAllocatorCachesLocked(InternalMmapVector<uptr>* caches) {
+  GetDsanThreadRegistryLocked()->RunCallbackForEachThreadLocked(
+      [](ThreadContextBase* tctx, void* arg) {
+        auto ctx = static_cast<ThreadContext*>(tctx);
+        static_cast<decltype(caches)>(arg)->push_back(ctx->cache_begin());
+      },
+      caches);
+}
+
+// DoubleFreeSanitizer doesn't need atexit check.
+void InstallAtForkHandler() {}
+
+}  // namespace __dsan
+
+// These are declared (in extern "C") by <zircon/sanitizer.h>.
+// The system runtime will call our definitions directly.
+
+// This is called before each thread creation is attempted.  So, in
+// its first call, the calling thread is the initial and sole thread.
+void* __sanitizer_before_thread_create_hook(thrd_t thread, bool detached,
+                                            const char* name, void* stack_base,
+                                            size_t stack_size) {
+  ENSURE_DSAN_INITED;
+  EnsureMainThreadIDIsCorrect();
+  OnCreatedArgs args;
+  args.stack_begin = reinterpret_cast<uptr>(stack_base);
+  args.stack_end = args.stack_begin + stack_size;
+  u32 parent_tid = GetCurrentThreadId();
+  u32 tid = ThreadCreate(parent_tid, detached, &args);
+  return reinterpret_cast<void*>(static_cast<uptr>(tid));
+}
+
+// This is called after creating a new thread (in the creating thread),
+// with the pointer returned by __sanitizer_before_thread_create_hook (above).
+void __sanitizer_thread_create_hook(void* hook, thrd_t thread, int error) {
+  u32 tid = static_cast<u32>(reinterpret_cast<uptr>(hook));
+  // On success, there is nothing to do here.
+  if (error != thrd_success) {
+    // Clean up the thread registry for the thread creation that didn't happen.
+    GetDsanThreadRegistryLocked()->FinishThread(tid);
+  }
+}
+
+// This is called in the newly-created thread before it runs anything else,
+// with the pointer returned by __sanitizer_before_thread_create_hook (above).
+void __sanitizer_thread_start_hook(void* hook, thrd_t self) {
+  u32 tid = static_cast<u32>(reinterpret_cast<uptr>(hook));
+  ThreadStart(tid);
+}
+
+// Each thread runs this just before it exits,
+// with the pointer returned by BeforeThreadCreateHook (above).
+// All per-thread destructors have already been called.
+void __sanitizer_thread_exit_hook(void* hook, thrd_t self) { ThreadFinish(); }
+
+#endif  // SANITIZER_FUCHSIA
diff --git a/compiler-rt/lib/dsan/dsan_fuchsia.h b/compiler-rt/lib/dsan/dsan_fuchsia.h
new file mode 100644
index 0000000000000..e60fc4f2ac2d9
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_fuchsia.h
@@ -0,0 +1,35 @@
+//=-- dsan_fuchsia.h ---------------------------------------------------===//
+//
+// 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 DoubleFreeSanitizer.
+// Standalone DSan RTL code specific to Fuchsia.
+//
+//===---------------------------------------------------------------------===//
+
+#ifndef DSAN_FUCHSIA_H
+#define DSAN_FUCHSIA_H
+
+#include "dsan_thread.h"
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if !SANITIZER_FUCHSIA
+#  error "dsan_fuchsia.h is used only on Fuchsia systems (SANITIZER_FUCHSIA)"
+#endif
+
+namespace __dsan {
+
+class ThreadContext final : public ThreadContextDsanBase {
+ public:
+  explicit ThreadContext(int tid);
+  void OnCreated(void* arg) override;
+  void OnStarted(void* arg) override;
+};
+
+}  // namespace __dsan
+
+#endif  // DSAN_FUCHSIA_H
diff --git a/compiler-rt/lib/dsan/dsan_interceptors.cpp b/compiler-rt/lib/dsan/dsan_interceptors.cpp
new file mode 100644
index 0000000000000..8ddb6d58239e7
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_interceptors.cpp
@@ -0,0 +1,575 @@
+//=-- dsan_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 DoubleFreeSanitizer.
+// Interceptors for standalone DSan.
+//
+//===----------------------------------------------------------------------===//
+
+#include "interception/interception.h"
+#include "sanitizer_common/sanitizer_allocator.h"
+#include "sanitizer_common/sanitizer_allocator_checks.h"
+#include "sanitizer_common/sanitizer_allocator_dlsym.h"
+#include "sanitizer_common/sanitizer_allocator_report.h"
+#include "sanitizer_common/sanitizer_atomic.h"
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_errno.h"
+#include "sanitizer_common/sanitizer_flags.h"
+#include "sanitizer_common/sanitizer_internal_defs.h"
+#include "sanitizer_common/sanitizer_linux.h"
+#include "sanitizer_common/sanitizer_platform_interceptors.h"
+#include "sanitizer_common/sanitizer_platform_limits_netbsd.h"
+#include "sanitizer_common/sanitizer_platform_limits_posix.h"
+#if SANITIZER_POSIX
+#  include "sanitizer_common/sanitizer_posix.h"
+#endif
+#include <stddef.h>
+
+#include "dsan.h"
+#include "dsan_allocator.h"
+#include "dsan_common.h"
+#include "dsan_thread.h"
+
+using namespace __dsan;
+
+extern "C" {
+int pthread_attr_init(void* attr);
+int pthread_attr_destroy(void* attr);
+int pthread_attr_getdetachstate(void* attr, int* v);
+int pthread_key_create(unsigned* key, void (*destructor)(void* v));
+int pthread_setspecific(unsigned key, const void* v);
+}
+
+struct DlsymAlloc : DlSymAllocator<DlsymAlloc> {
+  static bool UseImpl() { return dsan_init_is_running; }
+  static void OnAllocate(const void* ptr, uptr size) {
+    (void)ptr;
+    (void)size;
+  }
+  static void OnFree(const void* ptr, uptr size) {
+    (void)ptr;
+    (void)size;
+  }
+};
+
+///// Malloc/free interceptors. /////
+
+namespace std {
+struct nothrow_t;
+enum class align_val_t : size_t;
+}  // namespace std
+
+#if !SANITIZER_APPLE
+INTERCEPTOR(void*, malloc, uptr size) {
+  if (DlsymAlloc::Use())
+    return DlsymAlloc::Allocate(size);
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_malloc(size, stack);
+}
+
+INTERCEPTOR(void, free, void* p) {
+  if (UNLIKELY(!p))
+    return;
+  if (DlsymAlloc::PointerIsMine(p))
+    return DlsymAlloc::Free(p);
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  dsan_free(p, stack);
+}
+
+#  if SANITIZER_INTERCEPT_FREE_SIZED
+INTERCEPTOR(void, free_sized, void* p, uptr size) {
+  if (UNLIKELY(!p))
+    return;
+  if (DlsymAlloc::PointerIsMine(p))
+    return DlsymAlloc::Free(p);
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  dsan_free_sized(p, size, stack);
+}
+#    define DSAN_MAYBE_INTERCEPT_FREE_SIZED INTERCEPT_FUNCTION(free_sized)
+#  else
+#    define DSAN_MAYBE_INTERCEPT_FREE_SIZED
+#  endif
+
+#  if SANITIZER_INTERCEPT_FREE_ALIGNED_SIZED
+INTERCEPTOR(void, free_aligned_sized, void* p, uptr alignment, uptr size) {
+  if (UNLIKELY(!p))
+    return;
+  if (DlsymAlloc::PointerIsMine(p))
+    return DlsymAlloc::Free(p);
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  dsan_free_aligned_sized(p, alignment, size, stack);
+}
+#    define DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED \
+      INTERCEPT_FUNCTION(free_aligned_sized)
+#  else
+#    define DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED
+#  endif
+
+INTERCEPTOR(void*, calloc, uptr nmemb, uptr size) {
+  if (DlsymAlloc::Use())
+    return DlsymAlloc::Callocate(nmemb, size);
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_calloc(nmemb, size, stack);
+}
+
+INTERCEPTOR(void*, realloc, void* ptr, uptr size) {
+  if (DlsymAlloc::Use() || DlsymAlloc::PointerIsMine(ptr))
+    return DlsymAlloc::Realloc(ptr, size);
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_realloc(ptr, size, stack);
+}
+
+INTERCEPTOR(void*, reallocarray, void* q, uptr nmemb, uptr size) {
+  if (DlsymAlloc::Use() || DlsymAlloc::PointerIsMine(q)) {
+    if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
+      errno = errno_ENOMEM;
+      return nullptr;
+    }
+    return DlsymAlloc::Realloc(q, nmemb * size);
+  }
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_reallocarray(q, nmemb, size, stack);
+}
+
+INTERCEPTOR(int, posix_memalign, void** memptr, uptr alignment, uptr size) {
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_posix_memalign(memptr, alignment, size, stack);
+}
+
+INTERCEPTOR(void*, valloc, uptr size) {
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_valloc(size, stack);
+}
+#else
+#  define DSAN_MAYBE_INTERCEPT_FREE_SIZED
+#  define DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED
+#endif  // !SANITIZER_APPLE
+
+#if SANITIZER_INTERCEPT_MEMALIGN
+INTERCEPTOR(void*, memalign, uptr alignment, uptr size) {
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_memalign(alignment, size, stack);
+}
+#  define DSAN_MAYBE_INTERCEPT_MEMALIGN INTERCEPT_FUNCTION(memalign)
+#else
+#  define DSAN_MAYBE_INTERCEPT_MEMALIGN
+#endif  // SANITIZER_INTERCEPT_MEMALIGN
+
+#if SANITIZER_INTERCEPT___LIBC_MEMALIGN
+INTERCEPTOR(void*, __libc_memalign, uptr alignment, uptr size) {
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_memalign(alignment, size, stack);
+}
+#  define DSAN_MAYBE_INTERCEPT___LIBC_MEMALIGN \
+    INTERCEPT_FUNCTION(__libc_memalign)
+#else
+#  define DSAN_MAYBE_INTERCEPT___LIBC_MEMALIGN
+#endif  // SANITIZER_INTERCEPT___LIBC_MEMALIGN
+
+#if SANITIZER_INTERCEPT_ALIGNED_ALLOC
+INTERCEPTOR(void*, aligned_alloc, uptr alignment, uptr size) {
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_aligned_alloc(alignment, size, stack);
+}
+#  define DSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC INTERCEPT_FUNCTION(aligned_alloc)
+#else
+#  define DSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC
+#endif
+
+#if SANITIZER_INTERCEPT_MALLOC_USABLE_SIZE
+INTERCEPTOR(uptr, malloc_usable_size, void* ptr) {
+  ENSURE_DSAN_INITED;
+  return GetMallocUsableSize(ptr);
+}
+#  define DSAN_MAYBE_INTERCEPT_MALLOC_USABLE_SIZE \
+    INTERCEPT_FUNCTION(malloc_usable_size)
+#else
+#  define DSAN_MAYBE_INTERCEPT_MALLOC_USABLE_SIZE
+#endif
+
+#if SANITIZER_INTERCEPT_MALLOPT_AND_MALLINFO
+struct fake_mallinfo {
+  int x[10];
+};
+
+INTERCEPTOR(struct fake_mallinfo, mallinfo, void) {
+  struct fake_mallinfo res;
+  internal_memset(&res, 0, sizeof(res));
+  return res;
+}
+#  define DSAN_MAYBE_INTERCEPT_MALLINFO INTERCEPT_FUNCTION(mallinfo)
+
+INTERCEPTOR(int, mallopt, int cmd, int value) { return 0; }
+#  define DSAN_MAYBE_INTERCEPT_MALLOPT INTERCEPT_FUNCTION(mallopt)
+#else
+#  define DSAN_MAYBE_INTERCEPT_MALLINFO
+#  define DSAN_MAYBE_INTERCEPT_MALLOPT
+#endif  // SANITIZER_INTERCEPT_MALLOPT_AND_MALLINFO
+
+#if SANITIZER_INTERCEPT_PVALLOC
+INTERCEPTOR(void*, pvalloc, uptr size) {
+  ENSURE_DSAN_INITED;
+  GET_STACK_TRACE_MALLOC;
+  return dsan_pvalloc(size, stack);
+}
+#  define DSAN_MAYBE_INTERCEPT_PVALLOC INTERCEPT_FUNCTION(pvalloc)
+#else
+#  define DSAN_MAYBE_INTERCEPT_PVALLOC
+#endif  // SANITIZER_INTERCEPT_PVALLOC
+
+#if SANITIZER_INTERCEPT_CFREE
+INTERCEPTOR(void, cfree, void* p) ALIAS(WRAP(free));
+#  define DSAN_MAYBE_INTERCEPT_CFREE INTERCEPT_FUNCTION(cfree)
+#else
+#  define DSAN_MAYBE_INTERCEPT_CFREE
+#endif  // SANITIZER_INTERCEPT_CFREE
+
+#if SANITIZER_INTERCEPT_MCHECK_MPROBE
+INTERCEPTOR(int, mcheck, void (*abortfunc)(int mstatus)) { return 0; }
+
+INTERCEPTOR(int, mcheck_pedantic, void (*abortfunc)(int mstatus)) { return 0; }
+
+INTERCEPTOR(int, mprobe, void* ptr) { return 0; }
+#endif  // SANITIZER_INTERCEPT_MCHECK_MPROBE
+
+// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
+#define OPERATOR_NEW_BODY(nothrow)      \
+  ENSURE_DSAN_INITED;                   \
+  GET_STACK_TRACE_MALLOC;               \
+  void* res = dsan_malloc(size, stack); \
+  if (!nothrow && UNLIKELY(!res))       \
+    ReportOutOfMemory(size, &stack);    \
+  return res;
+#define OPERATOR_NEW_BODY_ALIGN(nothrow)               \
+  ENSURE_DSAN_INITED;                                  \
+  GET_STACK_TRACE_MALLOC;                              \
+  void* res = dsan_memalign((uptr)align, size, stack); \
+  if (!nothrow && UNLIKELY(!res))                      \
+    ReportOutOfMemory(size, &stack);                   \
+  return res;
+
+#define OPERATOR_DELETE_BODY \
+  ENSURE_DSAN_INITED;        \
+  GET_STACK_TRACE_MALLOC;    \
+  dsan_free(ptr, stack);
+
+// On OS X it's not enough to just provide our own 'operator new' and
+// 'operator delete' implementations, because they're going to be in the runtime
+// dylib, and the main executable will depend on both the runtime dylib and
+// libstdc++, each of has its implementation of new and delete.
+// To make sure that C++ allocation/deallocation operators are overridden on
+// OS X we need to intercept them using their mangled names.
+#if !SANITIZER_APPLE
+
+INTERCEPTOR_ATTRIBUTE
+void* operator new(size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
+INTERCEPTOR_ATTRIBUTE
+void* operator new[](size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
+INTERCEPTOR_ATTRIBUTE
+void* operator new(size_t size, std::nothrow_t const&) {
+  OPERATOR_NEW_BODY(true /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new[](size_t size, std::nothrow_t const&) {
+  OPERATOR_NEW_BODY(true /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new(size_t size, std::align_val_t align) {
+  OPERATOR_NEW_BODY_ALIGN(false /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new[](size_t size, std::align_val_t align) {
+  OPERATOR_NEW_BODY_ALIGN(false /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new(size_t size, std::align_val_t align, std::nothrow_t const&) {
+  OPERATOR_NEW_BODY_ALIGN(true /*nothrow*/);
+}
+INTERCEPTOR_ATTRIBUTE
+void* operator new[](size_t size, std::align_val_t align,
+                     std::nothrow_t const&) {
+  OPERATOR_NEW_BODY_ALIGN(true /*nothrow*/);
+}
+
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr) NOEXCEPT { OPERATOR_DELETE_BODY; }
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr) NOEXCEPT { OPERATOR_DELETE_BODY; }
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, std::nothrow_t const&) { OPERATOR_DELETE_BODY; }
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, std::nothrow_t const&) {
+  OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, size_t size) NOEXCEPT { OPERATOR_DELETE_BODY; }
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, size_t size) NOEXCEPT {
+  OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, std::align_val_t) NOEXCEPT {
+  OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, std::align_val_t) NOEXCEPT {
+  OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, std::align_val_t, std::nothrow_t const&) {
+  OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, std::align_val_t, std::nothrow_t const&) {
+  OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete(void* ptr, size_t size, std::align_val_t) NOEXCEPT {
+  OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR_ATTRIBUTE
+void operator delete[](void* ptr, size_t size, std::align_val_t) NOEXCEPT {
+  OPERATOR_DELETE_BODY;
+}
+
+#else  // SANITIZER_APPLE
+
+INTERCEPTOR(void*, _Znwm, size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
+INTERCEPTOR(void*, _Znam, size_t size) { OPERATOR_NEW_BODY(false /*nothrow*/); }
+INTERCEPTOR(void*, _ZnwmRKSt9nothrow_t, size_t size, std::nothrow_t const&) {
+  OPERATOR_NEW_BODY(true /*nothrow*/);
+}
+INTERCEPTOR(void*, _ZnamRKSt9nothrow_t, size_t size, std::nothrow_t const&) {
+  OPERATOR_NEW_BODY(true /*nothrow*/);
+}
+
+INTERCEPTOR(void, _ZdlPv, void* ptr) { OPERATOR_DELETE_BODY; }
+INTERCEPTOR(void, _ZdaPv, void* ptr) { OPERATOR_DELETE_BODY; }
+INTERCEPTOR(void, _ZdlPvRKSt9nothrow_t, void* ptr, std::nothrow_t const&) {
+  OPERATOR_DELETE_BODY;
+}
+INTERCEPTOR(void, _ZdaPvRKSt9nothrow_t, void* ptr, std::nothrow_t const&) {
+  OPERATOR_DELETE_BODY;
+}
+
+#endif  // !SANITIZER_APPLE
+
+///// Thread initialization and finalization. /////
+
+#if !SANITIZER_NETBSD && !SANITIZER_FREEBSD && !SANITIZER_FUCHSIA
+static unsigned g_thread_finalize_key;
+
+static void thread_finalize(void* v) {
+  uptr iter = (uptr)v;
+  if (iter > 1) {
+    if (pthread_setspecific(g_thread_finalize_key, (void*)(iter - 1))) {
+      Report("DoubleFreeSanitizer: failed to set thread key.\n");
+      Die();
+    }
+    return;
+  }
+  ThreadFinish();
+}
+#endif
+
+#if SANITIZER_NETBSD
+INTERCEPTOR(void, _lwp_exit) {
+  ENSURE_DSAN_INITED;
+  ThreadFinish();
+  REAL(_lwp_exit)();
+}
+#  define DSAN_MAYBE_INTERCEPT__LWP_EXIT INTERCEPT_FUNCTION(_lwp_exit)
+#else
+#  define DSAN_MAYBE_INTERCEPT__LWP_EXIT
+#endif
+
+#if SANITIZER_INTERCEPT_THR_EXIT
+INTERCEPTOR(void, thr_exit, ThreadID* state) {
+  ENSURE_DSAN_INITED;
+  ThreadFinish();
+  REAL(thr_exit)(state);
+}
+#  define DSAN_MAYBE_INTERCEPT_THR_EXIT INTERCEPT_FUNCTION(thr_exit)
+#else
+#  define DSAN_MAYBE_INTERCEPT_THR_EXIT
+#endif
+
+#if SANITIZER_POSIX
+
+template <bool Detached>
+static void* ThreadStartFunc(void* arg) {
+  u32 parent_tid = (uptr)arg;
+  uptr tid = ThreadCreate(parent_tid, Detached);
+  // Wait until the last iteration to maximize the chance that we are the last
+  // destructor to run.
+#  if !SANITIZER_NETBSD && !SANITIZER_FREEBSD
+  if (pthread_setspecific(g_thread_finalize_key,
+                          (void*)GetPthreadDestructorIterations())) {
+    Report("DoubleFreeSanitizer: failed to set thread key.\n");
+    Die();
+  }
+#  endif
+  ThreadStart(tid, GetTid());
+  auto self = GetThreadSelf();
+  auto args = GetThreadArgRetval().GetArgs(self);
+  void* retval = (*args.routine)(args.arg_retval);
+  GetThreadArgRetval().Finish(self, retval);
+  return retval;
+}
+
+INTERCEPTOR(int, pthread_create, void* th, void* attr, void* (*callback)(void*),
+            void* param) {
+  ENSURE_DSAN_INITED;
+  EnsureMainThreadIDIsCorrect();
+
+  bool detached = [attr]() {
+    int d = 0;
+    return attr && !pthread_attr_getdetachstate(attr, &d) && IsStateDetached(d);
+  }();
+
+  __sanitizer_pthread_attr_t myattr;
+  if (!attr) {
+    pthread_attr_init(&myattr);
+    attr = &myattr;
+  }
+  AdjustStackSize(attr);
+  uptr this_tid = GetCurrentThreadId();
+  int result;
+  GetThreadArgRetval().Create(detached, {callback, param}, [&]() -> uptr {
+    result = REAL(pthread_create)(
+        th, attr, detached ? ThreadStartFunc<true> : ThreadStartFunc<false>,
+        (void*)this_tid);
+    return result ? 0 : *(uptr*)(th);
+  });
+  if (attr == &myattr)
+    pthread_attr_destroy(&myattr);
+  return result;
+}
+
+INTERCEPTOR(int, pthread_join, void* thread, void** retval) {
+  int result;
+  GetThreadArgRetval().Join((uptr)thread, [&]() {
+    result = REAL(pthread_join)(thread, retval);
+    return !result;
+  });
+  return result;
+}
+
+INTERCEPTOR(int, pthread_detach, void* thread) {
+  int result;
+  GetThreadArgRetval().Detach((uptr)thread, [&]() {
+    result = REAL(pthread_detach)(thread);
+    return !result;
+  });
+  return result;
+}
+
+INTERCEPTOR(void, pthread_exit, void* retval) {
+  GetThreadArgRetval().Finish(GetThreadSelf(), retval);
+  REAL(pthread_exit)(retval);
+}
+
+#  if SANITIZER_INTERCEPT_TRYJOIN
+INTERCEPTOR(int, pthread_tryjoin_np, void* thread, void** ret) {
+  int result;
+  GetThreadArgRetval().Join((uptr)thread, [&]() {
+    result = REAL(pthread_tryjoin_np)(thread, ret);
+    return !result;
+  });
+  return result;
+}
+#    define DSAN_MAYBE_INTERCEPT_TRYJOIN INTERCEPT_FUNCTION(pthread_tryjoin_np)
+#  else
+#    define DSAN_MAYBE_INTERCEPT_TRYJOIN
+#  endif  // SANITIZER_INTERCEPT_TRYJOIN
+
+#  if SANITIZER_INTERCEPT_TIMEDJOIN
+INTERCEPTOR(int, pthread_timedjoin_np, void* thread, void** ret,
+            const struct timespec* abstime) {
+  int result;
+  GetThreadArgRetval().Join((uptr)thread, [&]() {
+    result = REAL(pthread_timedjoin_np)(thread, ret, abstime);
+    return !result;
+  });
+  return result;
+}
+#    define DSAN_MAYBE_INTERCEPT_TIMEDJOIN \
+      INTERCEPT_FUNCTION(pthread_timedjoin_np)
+#  else
+#    define DSAN_MAYBE_INTERCEPT_TIMEDJOIN
+#  endif  // SANITIZER_INTERCEPT_TIMEDJOIN
+
+DEFINE_INTERNAL_PTHREAD_FUNCTIONS
+
+INTERCEPTOR(void, _exit, int status) { REAL(_exit)(status); }
+
+#  define COMMON_INTERCEPT_FUNCTION(name) INTERCEPT_FUNCTION(name)
+#  define SIGNAL_INTERCEPTOR_ENTER() ENSURE_DSAN_INITED
+#  include "sanitizer_common/sanitizer_signal_interceptors.inc"
+
+#endif  // SANITIZER_POSIX
+
+namespace __dsan {
+
+void InitializeInterceptors() {
+  // Fuchsia doesn't use interceptors that require any setup.
+#if !SANITIZER_FUCHSIA
+  __interception::DoesNotSupportStaticLinking();
+  InitializeSignalInterceptors();
+
+  INTERCEPT_FUNCTION(malloc);
+  INTERCEPT_FUNCTION(free);
+  DSAN_MAYBE_INTERCEPT_FREE_SIZED;
+  DSAN_MAYBE_INTERCEPT_FREE_ALIGNED_SIZED;
+  DSAN_MAYBE_INTERCEPT_CFREE;
+  INTERCEPT_FUNCTION(calloc);
+  INTERCEPT_FUNCTION(realloc);
+  INTERCEPT_FUNCTION(reallocarray);
+  DSAN_MAYBE_INTERCEPT_MEMALIGN;
+  DSAN_MAYBE_INTERCEPT___LIBC_MEMALIGN;
+  DSAN_MAYBE_INTERCEPT_ALIGNED_ALLOC;
+  INTERCEPT_FUNCTION(posix_memalign);
+  INTERCEPT_FUNCTION(valloc);
+  DSAN_MAYBE_INTERCEPT_PVALLOC;
+  DSAN_MAYBE_INTERCEPT_MALLOC_USABLE_SIZE;
+  DSAN_MAYBE_INTERCEPT_MALLINFO;
+  DSAN_MAYBE_INTERCEPT_MALLOPT;
+  INTERCEPT_FUNCTION(pthread_create);
+  INTERCEPT_FUNCTION(pthread_join);
+  INTERCEPT_FUNCTION(pthread_detach);
+  INTERCEPT_FUNCTION(pthread_exit);
+  DSAN_MAYBE_INTERCEPT_TIMEDJOIN;
+  DSAN_MAYBE_INTERCEPT_TRYJOIN;
+  INTERCEPT_FUNCTION(_exit);
+
+  DSAN_MAYBE_INTERCEPT__LWP_EXIT;
+  DSAN_MAYBE_INTERCEPT_THR_EXIT;
+
+#  if !SANITIZER_NETBSD && !SANITIZER_FREEBSD
+  if (pthread_key_create(&g_thread_finalize_key, &thread_finalize)) {
+    Report("DoubleFreeSanitizer: failed to create thread key.\n");
+    Die();
+  }
+#  endif
+
+#endif  // !SANITIZER_FUCHSIA
+}
+
+}  // namespace __dsan
diff --git a/compiler-rt/lib/dsan/dsan_linux.cpp b/compiler-rt/lib/dsan/dsan_linux.cpp
new file mode 100644
index 0000000000000..fa5bbffb9a5ec
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_linux.cpp
@@ -0,0 +1,34 @@
+//=-- dsan_linux.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 DoubleFreeSanitizer. Linux/NetBSD/Fuchsia-specific
+// code.
+//
+//===----------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_FUCHSIA
+
+#  include "dsan_allocator.h"
+#  include "dsan_thread.h"
+
+namespace __dsan {
+
+static THREADLOCAL ThreadContextDsanBase* current_thread = nullptr;
+ThreadContextDsanBase* GetCurrentThread() { return current_thread; }
+void SetCurrentThread(ThreadContextDsanBase* tctx) { current_thread = tctx; }
+
+static THREADLOCAL AllocatorCache allocator_cache;
+AllocatorCache* GetAllocatorCache() { return &allocator_cache; }
+
+void ReplaceSystemMalloc() {}
+
+}  // namespace __dsan
+
+#endif  // SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_FUCHSIA
diff --git a/compiler-rt/lib/dsan/dsan_mac.cpp b/compiler-rt/lib/dsan/dsan_mac.cpp
new file mode 100644
index 0000000000000..793689862bc00
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_mac.cpp
@@ -0,0 +1,234 @@
+//===-- dsan_mac.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 DoubleFreeSanitizer.
+//
+// Mac-specific details.
+//===----------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+#if SANITIZER_APPLE
+
+#  include <pthread.h>
+
+#  include "dsan.h"
+#  include "dsan_allocator.h"
+#  include "dsan_thread.h"
+#  include "interception/interception.h"
+#  include "sanitizer_common/sanitizer_allocator_internal.h"
+
+namespace __dsan {
+struct ThreadLocalData {
+  ThreadContextDsanBase* current_thread;
+  AllocatorCache cache;
+};
+
+static pthread_key_t thread_local_key;
+static pthread_once_t thread_local_key_once = PTHREAD_ONCE_INIT;
+
+static void RestoreThreadLocalData(void* ptr) {
+  ThreadLocalData* data = static_cast<ThreadLocalData*>(ptr);
+  if (data->current_thread)
+    pthread_setspecific(thread_local_key, data);
+}
+
+static void CreateThreadLocalKey() {
+  CHECK_EQ(pthread_key_create(&thread_local_key, RestoreThreadLocalData), 0);
+}
+
+static ThreadLocalData* GetThreadLocalData(bool allocate) {
+  pthread_once(&thread_local_key_once, CreateThreadLocalKey);
+  ThreadLocalData* data =
+      static_cast<ThreadLocalData*>(pthread_getspecific(thread_local_key));
+  if (!data && allocate) {
+    data = static_cast<ThreadLocalData*>(InternalAlloc(sizeof(*data)));
+    data->current_thread = nullptr;
+    data->cache = AllocatorCache();
+    pthread_setspecific(thread_local_key, data);
+  }
+  return data;
+}
+
+ThreadContextDsanBase* GetCurrentThread() {
+  ThreadLocalData* data = GetThreadLocalData(false);
+  return data ? data->current_thread : nullptr;
+}
+
+void SetCurrentThread(ThreadContextDsanBase* tctx) {
+  GetThreadLocalData(true)->current_thread = tctx;
+}
+
+AllocatorCache* GetAllocatorCache() { return &GetThreadLocalData(true)->cache; }
+
+// Support for the following functions from libdispatch on Mac OS:
+//   dispatch_async_f()
+//   dispatch_async()
+//   dispatch_sync_f()
+//   dispatch_sync()
+//   dispatch_after_f()
+//   dispatch_after()
+//   dispatch_group_async_f()
+//   dispatch_group_async()
+// TODO(glider): libdispatch API contains other functions that we don't support
+// yet.
+//
+// dispatch_sync() and dispatch_sync_f() are synchronous, although chances are
+// they can cause jobs to run on a thread different from the current one.
+// TODO(glider): if so, we need a test for this (otherwise we should remove
+// them).
+//
+// The following functions use dispatch_barrier_async_f() (which isn't a library
+// function but is exported) and are thus supported:
+//   dispatch_source_set_cancel_handler_f()
+//   dispatch_source_set_cancel_handler()
+//   dispatch_source_set_event_handler_f()
+//   dispatch_source_set_event_handler()
+//
+// The reference manual for Grand Central Dispatch is available at
+//   http://developer.apple.com/library/mac/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
+// The implementation details are at
+//   http://libdispatch.macosforge.org/trac/browser/trunk/src/queue.c
+
+typedef void* dispatch_group_t;
+typedef void* dispatch_queue_t;
+typedef void* dispatch_source_t;
+typedef u64 dispatch_time_t;
+typedef void (*dispatch_function_t)(void* block);
+typedef void* (*worker_t)(void* block);
+
+// A wrapper for the ObjC blocks used to support libdispatch.
+typedef struct {
+  void* block;
+  dispatch_function_t func;
+  u32 parent_tid;
+} dsan_block_context_t;
+
+ALWAYS_INLINE
+void dsan_register_worker_thread(int parent_tid) {
+  if (GetCurrentThreadId() == kInvalidTid) {
+    u32 tid = ThreadCreate(parent_tid, true);
+    ThreadStart(tid, GetTid());
+  }
+}
+
+// For use by only those functions that allocated the context via
+// alloc_dsan_context().
+extern "C" void dsan_dispatch_call_block_and_release(void* block) {
+  dsan_block_context_t* context = (dsan_block_context_t*)block;
+  VReport(2,
+          "dsan_dispatch_call_block_and_release(): "
+          "context: %p, pthread_self: %p\n",
+          block, (void*)pthread_self());
+  dsan_register_worker_thread(context->parent_tid);
+  // Call the original dispatcher for the block.
+  context->func(context->block);
+  GET_STACK_TRACE_MALLOC;
+  dsan_free(context, stack);
+}
+
+}  // namespace __dsan
+
+using namespace __dsan;
+
+// Wrap |ctxt| and |func| into an dsan_block_context_t.
+// The caller retains control of the allocated context.
+extern "C" dsan_block_context_t* alloc_dsan_context(void* ctxt,
+                                                    dispatch_function_t func) {
+  GET_STACK_TRACE_THREAD;
+  dsan_block_context_t* dsan_ctxt =
+      (dsan_block_context_t*)dsan_malloc(sizeof(dsan_block_context_t), stack);
+  dsan_ctxt->block = ctxt;
+  dsan_ctxt->func = func;
+  dsan_ctxt->parent_tid = GetCurrentThreadId();
+  return dsan_ctxt;
+}
+
+// Define interceptor for dispatch_*_f function with the three most common
+// parameters: dispatch_queue_t, context, dispatch_function_t.
+#  define INTERCEPT_DISPATCH_X_F_3(dispatch_x_f)                        \
+    INTERCEPTOR(void, dispatch_x_f, dispatch_queue_t dq, void* ctxt,    \
+                dispatch_function_t func) {                             \
+      dsan_block_context_t* dsan_ctxt = alloc_dsan_context(ctxt, func); \
+      return REAL(dispatch_x_f)(dq, (void*)dsan_ctxt,                   \
+                                dsan_dispatch_call_block_and_release);  \
+    }
+
+INTERCEPT_DISPATCH_X_F_3(dispatch_async_f)
+INTERCEPT_DISPATCH_X_F_3(dispatch_sync_f)
+INTERCEPT_DISPATCH_X_F_3(dispatch_barrier_async_f)
+
+INTERCEPTOR(void, dispatch_after_f, dispatch_time_t when, dispatch_queue_t dq,
+            void* ctxt, dispatch_function_t func) {
+  dsan_block_context_t* dsan_ctxt = alloc_dsan_context(ctxt, func);
+  return REAL(dispatch_after_f)(when, dq, (void*)dsan_ctxt,
+                                dsan_dispatch_call_block_and_release);
+}
+
+INTERCEPTOR(void, dispatch_group_async_f, dispatch_group_t group,
+            dispatch_queue_t dq, void* ctxt, dispatch_function_t func) {
+  dsan_block_context_t* dsan_ctxt = alloc_dsan_context(ctxt, func);
+  REAL(dispatch_group_async_f)(group, dq, (void*)dsan_ctxt,
+                               dsan_dispatch_call_block_and_release);
+}
+
+#  if !defined(MISSING_BLOCKS_SUPPORT)
+extern "C" {
+void dispatch_async(dispatch_queue_t dq, void (^work)(void));
+void dispatch_group_async(dispatch_group_t dg, dispatch_queue_t dq,
+                          void (^work)(void));
+void dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
+                    void (^work)(void));
+void dispatch_source_set_cancel_handler(dispatch_source_t ds,
+                                        void (^work)(void));
+void dispatch_source_set_event_handler(dispatch_source_t ds,
+                                       void (^work)(void));
+}
+
+#    define GET_DSAN_BLOCK(work)                 \
+      void (^dsan_block)(void);                  \
+      int parent_tid = GetCurrentThreadId();     \
+      dsan_block = ^(void) {                     \
+        dsan_register_worker_thread(parent_tid); \
+        work();                                  \
+      }
+
+INTERCEPTOR(void, dispatch_async, dispatch_queue_t dq, void (^work)(void)) {
+  GET_DSAN_BLOCK(work);
+  REAL(dispatch_async)(dq, dsan_block);
+}
+
+INTERCEPTOR(void, dispatch_group_async, dispatch_group_t dg,
+            dispatch_queue_t dq, void (^work)(void)) {
+  GET_DSAN_BLOCK(work);
+  REAL(dispatch_group_async)(dg, dq, dsan_block);
+}
+
+INTERCEPTOR(void, dispatch_after, dispatch_time_t when, dispatch_queue_t queue,
+            void (^work)(void)) {
+  GET_DSAN_BLOCK(work);
+  REAL(dispatch_after)(when, queue, dsan_block);
+}
+
+INTERCEPTOR(void, dispatch_source_set_cancel_handler, dispatch_source_t ds,
+            void (^work)(void)) {
+  if (!work) {
+    REAL(dispatch_source_set_cancel_handler)(ds, work);
+    return;
+  }
+  GET_DSAN_BLOCK(work);
+  REAL(dispatch_source_set_cancel_handler)(ds, dsan_block);
+}
+
+INTERCEPTOR(void, dispatch_source_set_event_handler, dispatch_source_t ds,
+            void (^work)(void)) {
+  GET_DSAN_BLOCK(work);
+  REAL(dispatch_source_set_event_handler)(ds, dsan_block);
+}
+#  endif
+
+#endif  // SANITIZER_APPLE
diff --git a/compiler-rt/lib/dsan/dsan_malloc_mac.cpp b/compiler-rt/lib/dsan/dsan_malloc_mac.cpp
new file mode 100644
index 0000000000000..515b26c49424d
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_malloc_mac.cpp
@@ -0,0 +1,66 @@
+//===-- dsan_malloc_mac.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 DoubleFreeSanitizer (DSan).
+//
+// Mac-specific malloc interception.
+//===----------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+#if SANITIZER_APPLE
+
+#  include "dsan.h"
+#  include "dsan_allocator.h"
+#  include "dsan_thread.h"
+
+using namespace __dsan;
+#  define COMMON_MALLOC_ZONE_NAME "dsan"
+#  define COMMON_MALLOC_ENTER() ENSURE_DSAN_INITED
+#  define COMMON_MALLOC_SANITIZER_INITIALIZED dsan_inited
+#  define COMMON_MALLOC_FORCE_LOCK()
+#  define COMMON_MALLOC_FORCE_UNLOCK()
+#  define COMMON_MALLOC_MEMALIGN(alignment, size) \
+    GET_STACK_TRACE_MALLOC;                       \
+    void* p = dsan_memalign(alignment, size, stack)
+#  define COMMON_MALLOC_MALLOC(size) \
+    GET_STACK_TRACE_MALLOC;          \
+    void* p = dsan_malloc(size, stack)
+#  define COMMON_MALLOC_REALLOC(ptr, size) \
+    GET_STACK_TRACE_MALLOC;                \
+    void* p = dsan_realloc(ptr, size, stack)
+#  define COMMON_MALLOC_CALLOC(count, size) \
+    GET_STACK_TRACE_MALLOC;                 \
+    void* p = dsan_calloc(count, size, stack)
+#  define COMMON_MALLOC_POSIX_MEMALIGN(memptr, alignment, size) \
+    GET_STACK_TRACE_MALLOC;                                     \
+    int res = dsan_posix_memalign(memptr, alignment, size, stack)
+#  define COMMON_MALLOC_VALLOC(size) \
+    GET_STACK_TRACE_MALLOC;          \
+    void* p = dsan_valloc(size, stack)
+#  define COMMON_MALLOC_FREE(ptr) \
+    GET_STACK_TRACE_MALLOC;       \
+    dsan_free(ptr, stack)
+#  define COMMON_MALLOC_FREE_SIZED(ptr, size) \
+    GET_STACK_TRACE_MALLOC;                   \
+    dsan_free_sized(ptr, size, stack)
+#  define COMMON_MALLOC_FREE_ALIGNED_SIZED(ptr, alignment, size) \
+    GET_STACK_TRACE_MALLOC;                                      \
+    dsan_free_aligned_sized(ptr, alignment, size, stack)
+#  define COMMON_MALLOC_SIZE(ptr) uptr size = dsan_mz_size(ptr)
+#  define COMMON_MALLOC_FILL_STATS(zone, stats)
+#  define COMMON_MALLOC_REPORT_UNKNOWN_REALLOC(ptr, zone_ptr, zone_name)    \
+    (void)zone_name;                                                        \
+    Report("mz_realloc(%p) -- attempting to realloc unallocated memory.\n", \
+           ptr);
+#  define COMMON_MALLOC_NAMESPACE __dsan
+#  define COMMON_MALLOC_HAS_ZONE_ENUMERATOR 0
+#  define COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT 0
+
+#  include "sanitizer_common/sanitizer_malloc_mac.inc"
+
+#endif  // SANITIZER_APPLE
diff --git a/compiler-rt/lib/dsan/dsan_posix.cpp b/compiler-rt/lib/dsan/dsan_posix.cpp
new file mode 100644
index 0000000000000..b96488f49fd75
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_posix.cpp
@@ -0,0 +1,121 @@
+//=-- dsan_posix.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 DoubleFreeSanitizer.
+// Standalone DSan RTL code common to POSIX-like systems.
+//
+//===---------------------------------------------------------------------===//
+
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if SANITIZER_POSIX
+#  include <pthread.h>
+
+#  include "dsan.h"
+#  include "dsan_allocator.h"
+#  include "dsan_common.h"
+#  include "dsan_thread.h"
+#  include "sanitizer_common/sanitizer_stackdepot.h"
+#  include "sanitizer_common/sanitizer_stacktrace.h"
+#  include "sanitizer_common/sanitizer_tls_get_addr.h"
+
+namespace __dsan {
+
+ThreadContext::ThreadContext(int tid) : ThreadContextDsanBase(tid) {}
+
+struct OnStartedArgs {
+  uptr stack_begin;
+  uptr stack_end;
+  uptr cache_begin;
+  uptr cache_end;
+  uptr tls_begin;
+  uptr tls_end;
+  DTLS* dtls;
+};
+
+void ThreadContext::OnStarted(void* arg) {
+  ThreadContextDsanBase::OnStarted(arg);
+  auto args = reinterpret_cast<const OnStartedArgs*>(arg);
+  stack_begin_ = args->stack_begin;
+  stack_end_ = args->stack_end;
+  tls_begin_ = args->tls_begin;
+  tls_end_ = args->tls_end;
+  cache_begin_ = args->cache_begin;
+  cache_end_ = args->cache_end;
+  dtls_ = args->dtls;
+}
+
+void ThreadStart(u32 tid, ThreadID os_id, ThreadType thread_type) {
+  OnStartedArgs args;
+  GetThreadStackAndTls(tid == kMainTid, &args.stack_begin, &args.stack_end,
+                       &args.tls_begin, &args.tls_end);
+  GetAllocatorCacheRange(&args.cache_begin, &args.cache_end);
+  args.dtls = DTLS_Get();
+  ThreadContextDsanBase::ThreadStart(tid, os_id, thread_type, &args);
+}
+
+bool GetThreadRangesLocked(ThreadID os_id, uptr* stack_begin, uptr* stack_end,
+                           uptr* tls_begin, uptr* tls_end, uptr* cache_begin,
+                           uptr* cache_end, DTLS** dtls) {
+  ThreadContext* context = static_cast<ThreadContext*>(
+      GetDsanThreadRegistryLocked()->FindThreadContextByOsIDLocked(os_id));
+  if (!context)
+    return false;
+  *stack_begin = context->stack_begin();
+  *stack_end = context->stack_end();
+  *tls_begin = context->tls_begin();
+  *tls_end = context->tls_end();
+  *cache_begin = context->cache_begin();
+  *cache_end = context->cache_end();
+  *dtls = context->dtls();
+  return true;
+}
+
+void InitializeMainThread() {
+  u32 tid = ThreadCreate(kMainTid, true);
+  CHECK_EQ(tid, kMainTid);
+  ThreadStart(tid, GetTid());
+}
+
+static void OnStackUnwind(const SignalContext& sig, const void*,
+                          BufferedStackTrace* stack) {
+  stack->Unwind(StackTrace::GetNextInstructionPc(sig.pc), sig.bp, sig.context,
+                common_flags()->fast_unwind_on_fatal);
+}
+
+void DsanOnDeadlySignal(int signo, void* siginfo, void* context) {
+  HandleDeadlySignal(siginfo, context, GetCurrentThreadId(), &OnStackUnwind,
+                     nullptr);
+}
+
+static void BeforeFork() {
+  VReport(2, "BeforeFork tid: %llu\n", GetTid());
+  LockThreads();
+  LockAllocator();
+  StackDepotLockBeforeFork();
+}
+
+static void AfterFork(bool fork_child) {
+  StackDepotUnlockAfterFork(fork_child);
+  UnlockAllocator();
+  UnlockThreads();
+  VReport(2, "AfterFork tid: %llu\n", GetTid());
+}
+
+void InstallAtForkHandler() {
+#  if SANITIZER_SOLARIS || SANITIZER_NETBSD || SANITIZER_APPLE
+  return;  // FIXME: Implement FutexWait.
+#  endif
+  pthread_atfork(
+      &BeforeFork, []() { AfterFork(/* fork_child= */ false); },
+      []() { AfterFork(/* fork_child= */ true); });
+}
+
+}  // namespace __dsan
+
+#endif  // SANITIZER_POSIX
diff --git a/compiler-rt/lib/dsan/dsan_posix.h b/compiler-rt/lib/dsan/dsan_posix.h
new file mode 100644
index 0000000000000..03b804704db4e
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_posix.h
@@ -0,0 +1,49 @@
+//=-- dsan_posix.h -----------------------------------------------------===//
+//
+// 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 DoubleFreeSanitizer.
+// Standalone DSan RTL code common to POSIX-like systems.
+//
+//===---------------------------------------------------------------------===//
+
+#ifndef DSAN_POSIX_H
+#define DSAN_POSIX_H
+
+#include "dsan_thread.h"
+#include "sanitizer_common/sanitizer_platform.h"
+
+#if !SANITIZER_POSIX
+#  error "dsan_posix.h is used only on POSIX-like systems (SANITIZER_POSIX)"
+#endif
+
+namespace __sanitizer {
+struct DTLS;
+}
+
+namespace __dsan {
+
+class ThreadContext final : public ThreadContextDsanBase {
+ public:
+  explicit ThreadContext(int tid);
+  void OnStarted(void* arg) override;
+  uptr tls_begin() { return tls_begin_; }
+  uptr tls_end() { return tls_end_; }
+  DTLS* dtls() { return dtls_; }
+
+ private:
+  uptr tls_begin_ = 0;
+  uptr tls_end_ = 0;
+  DTLS* dtls_ = nullptr;
+};
+
+void ThreadStart(u32 tid, ThreadID os_id,
+                 ThreadType thread_type = ThreadType::Regular);
+
+}  // namespace __dsan
+
+#endif  // DSAN_POSIX_H
diff --git a/compiler-rt/lib/dsan/dsan_preinit.cpp b/compiler-rt/lib/dsan/dsan_preinit.cpp
new file mode 100644
index 0000000000000..4a936fd6e6422
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_preinit.cpp
@@ -0,0 +1,21 @@
+//===-- dsan_preinit.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 DoubleFreeSanitizer.
+//
+// Call __dsan_init at the very early stage of process startup.
+//===----------------------------------------------------------------------===//
+
+#include "dsan.h"
+
+#if SANITIZER_CAN_USE_PREINIT_ARRAY
+// This section is linked into the main executable when -fsanitize=doublefree is
+// specified to perform initialization at a very early stage.
+__attribute__((section(".preinit_array"), used)) static auto preinit =
+    __dsan_init;
+#endif
diff --git a/compiler-rt/lib/dsan/dsan_thread.cpp b/compiler-rt/lib/dsan/dsan_thread.cpp
new file mode 100644
index 0000000000000..617c1c998ad80
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_thread.cpp
@@ -0,0 +1,123 @@
+//=-- dsan_thread.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 DoubleFreeSanitizer.
+// See dsan_thread.h for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "dsan_thread.h"
+
+#include "dsan.h"
+#include "dsan_allocator.h"
+#include "dsan_common.h"
+#include "sanitizer_common/sanitizer_common.h"
+#include "sanitizer_common/sanitizer_placement_new.h"
+#include "sanitizer_common/sanitizer_thread_history.h"
+#include "sanitizer_common/sanitizer_thread_registry.h"
+#include "sanitizer_common/sanitizer_tls_get_addr.h"
+
+namespace __dsan {
+
+static ThreadRegistry* thread_registry;
+static ThreadArgRetval* thread_arg_retval;
+
+static Mutex mu_for_thread_context;
+static LowLevelAllocator allocator_for_thread_context;
+
+static ThreadContextBase* CreateThreadContext(u32 tid) {
+  Lock lock(&mu_for_thread_context);
+  return new (allocator_for_thread_context) ThreadContext(tid);
+}
+
+void InitializeThreads() {
+  alignas(alignof(ThreadRegistry)) static char
+      thread_registry_placeholder[sizeof(ThreadRegistry)];
+  thread_registry =
+      new (thread_registry_placeholder) ThreadRegistry(CreateThreadContext);
+
+  alignas(alignof(ThreadArgRetval)) static char
+      thread_arg_retval_placeholder[sizeof(ThreadArgRetval)];
+  thread_arg_retval = new (thread_arg_retval_placeholder) ThreadArgRetval();
+}
+
+ThreadArgRetval& GetThreadArgRetval() { return *thread_arg_retval; }
+
+ThreadContextDsanBase::ThreadContextDsanBase(int tid)
+    : ThreadContextBase(tid) {}
+
+void ThreadContextDsanBase::OnStarted(void* arg) {
+  SetCurrentThread(this);
+  AllocatorThreadStart();
+}
+
+void ThreadContextDsanBase::OnFinished() {
+  AllocatorThreadFinish();
+  DTLS_Destroy();
+  SetCurrentThread(nullptr);
+}
+
+u32 ThreadCreate(u32 parent_tid, bool detached, void* arg) {
+  return thread_registry->CreateThread(0, detached, parent_tid, arg);
+}
+
+void ThreadContextDsanBase::ThreadStart(u32 tid, ThreadID os_id,
+                                        ThreadType thread_type, void* arg) {
+  thread_registry->StartThread(tid, os_id, thread_type, arg);
+}
+
+void ThreadFinish() { thread_registry->FinishThread(GetCurrentThreadId()); }
+
+void EnsureMainThreadIDIsCorrect() {
+  if (GetCurrentThreadId() == kMainTid)
+    GetCurrentThread()->os_id = GetTid();
+}
+
+///// Interface to the common DSan module. /////
+
+void GetThreadExtraStackRangesLocked(ThreadID os_id,
+                                     InternalMmapVector<Range>* ranges) {}
+void GetThreadExtraStackRangesLocked(InternalMmapVector<Range>* ranges) {}
+
+void LockThreads() {
+  thread_registry->Lock();
+  thread_arg_retval->Lock();
+}
+
+void UnlockThreads() {
+  thread_arg_retval->Unlock();
+  thread_registry->Unlock();
+}
+
+ThreadRegistry* GetDsanThreadRegistryLocked() {
+  thread_registry->CheckLocked();
+  return thread_registry;
+}
+
+void GetRunningThreadsLocked(InternalMmapVector<ThreadID>* threads) {
+  GetDsanThreadRegistryLocked()->RunCallbackForEachThreadLocked(
+      [](ThreadContextBase* tctx, void* threads) {
+        if (tctx->status == ThreadStatusRunning) {
+          reinterpret_cast<InternalMmapVector<ThreadID>*>(threads)->push_back(
+              tctx->os_id);
+        }
+      },
+      threads);
+}
+
+void PrintThreads() {
+  InternalScopedString out;
+  PrintThreadHistory(*thread_registry, out);
+  Report("%s\n", out.data());
+}
+
+void GetAdditionalThreadContextPtrsLocked(InternalMmapVector<uptr>* ptrs) {
+  GetThreadArgRetval().GetAllPtrsLocked(ptrs);
+}
+
+}  // namespace __dsan
diff --git a/compiler-rt/lib/dsan/dsan_thread.h b/compiler-rt/lib/dsan/dsan_thread.h
new file mode 100644
index 0000000000000..4e252bfc31f35
--- /dev/null
+++ b/compiler-rt/lib/dsan/dsan_thread.h
@@ -0,0 +1,66 @@
+//=-- dsan_thread.h -------------------------------------------------------===//
+//
+// 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 DoubleFreeSanitizer.
+// Thread registry for standalone DSan.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef DSAN_THREAD_H
+#define DSAN_THREAD_H
+
+#include "sanitizer_common/sanitizer_thread_arg_retval.h"
+#include "sanitizer_common/sanitizer_thread_registry.h"
+
+namespace __dsan {
+
+class ThreadContextDsanBase : public ThreadContextBase {
+ public:
+  explicit ThreadContextDsanBase(int tid);
+  void OnStarted(void* arg) override;
+  void OnFinished() override;
+  uptr stack_begin() { return stack_begin_; }
+  uptr stack_end() { return stack_end_; }
+  uptr cache_begin() { return cache_begin_; }
+  uptr cache_end() { return cache_end_; }
+
+  // The argument is passed on to the subclass's OnStarted member function.
+  static void ThreadStart(u32 tid, ThreadID os_id, ThreadType thread_type,
+                          void* onstarted_arg);
+
+ protected:
+  ~ThreadContextDsanBase() {}
+  uptr stack_begin_ = 0;
+  uptr stack_end_ = 0;
+  uptr cache_begin_ = 0;
+  uptr cache_end_ = 0;
+};
+
+// This subclass of ThreadContextDsanBase is declared in an OS-specific header.
+class ThreadContext;
+
+void InitializeThreads();
+void InitializeMainThread();
+
+ThreadRegistry* GetDsanThreadRegistryLocked();
+ThreadArgRetval& GetThreadArgRetval();
+
+u32 ThreadCreate(u32 tid, bool detached, void* arg = nullptr);
+void ThreadFinish();
+
+ThreadContextDsanBase* GetCurrentThread();
+inline u32 GetCurrentThreadId() {
+  ThreadContextDsanBase* ctx = GetCurrentThread();
+  return ctx ? ctx->tid : kInvalidTid;
+}
+void SetCurrentThread(ThreadContextDsanBase* tctx);
+void EnsureMainThreadIDIsCorrect();
+
+}  // namespace __dsan
+
+#endif  // DSAN_THREAD_H
diff --git a/compiler-rt/lib/dsan/weak_symbols.txt b/compiler-rt/lib/dsan/weak_symbols.txt
new file mode 100644
index 0000000000000..c60959f813550
--- /dev/null
+++ b/compiler-rt/lib/dsan/weak_symbols.txt
@@ -0,0 +1 @@
+___dsan_default_options
diff --git a/compiler-rt/test/CMakeLists.txt b/compiler-rt/test/CMakeLists.txt
index 3fab82518e75f..5e8ce9f13b670 100644
--- a/compiler-rt/test/CMakeLists.txt
+++ b/compiler-rt/test/CMakeLists.txt
@@ -58,7 +58,7 @@ endif()
 umbrella_lit_testsuite_begin(check-compiler-rt)
 
 set(COMPILER_RT_KNOWN_TEST_SUITES
-    builtins;ctx_profile;fuzzer;interception;lsan;memprof;metadata
+    builtins;ctx_profile;fuzzer;interception;lsan;dsan;memprof;metadata
     ;orc;profile;sanitizer_common;shadowcallstack
     ;ubsan;xray)
 list(APPEND COMPILER_RT_KNOWN_TEST_SUITES ${ALL_SANITIZERS})
@@ -136,6 +136,7 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
     compiler_rt_test_runtime(interception)
 
     compiler_rt_test_runtime(lsan)
+    compiler_rt_test_runtime(dsan)
     compiler_rt_test_runtime(ubsan)
     compiler_rt_test_runtime(sanitizer_common)
 
diff --git a/compiler-rt/test/dsan/CMakeLists.txt b/compiler-rt/test/dsan/CMakeLists.txt
new file mode 100644
index 0000000000000..858c2bbc4c8d4
--- /dev/null
+++ b/compiler-rt/test/dsan/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(DSAN_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
+
+set(DSAN_TESTSUITES)
+
+set(DSAN_TEST_ARCH ${DSAN_SUPPORTED_ARCH})
+if(APPLE)
+  darwin_filter_host_archs(DSAN_SUPPORTED_ARCH DSAN_TEST_ARCH)
+endif()
+
+foreach(arch ${DSAN_TEST_ARCH})
+  set(DSAN_TEST_TARGET_ARCH ${arch})
+  string(TOLOWER "-${arch}" DSAN_TEST_CONFIG_SUFFIX)
+  get_test_cc_for_arch(${arch} DSAN_TEST_TARGET_CC DSAN_TEST_TARGET_CFLAGS)
+  string(TOUPPER ${arch} ARCH_UPPER_CASE)
+
+  set(DSAN_LIT_TEST_MODE "Standalone")
+  set(CONFIG_NAME ${ARCH_UPPER_CASE}DsanConfig)
+  configure_lit_site_cfg(
+    ${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
+    ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME}/lit.site.cfg.py)
+  list(APPEND DSAN_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_NAME})
+endforeach()
+
+set(DSAN_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS})
+list(APPEND DSAN_TEST_DEPS dsan)
+add_lit_testsuite(check-dsan "Running the DoubleFreeSanitizer tests"
+  ${DSAN_TESTSUITES}
+  DEPENDS ${DSAN_TEST_DEPS})
diff --git a/compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp b/compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp
new file mode 100644
index 0000000000000..e1061900f85d6
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/concurrent-double-free.cpp
@@ -0,0 +1,28 @@
+// REQUIRES: linux
+// RUN: %clangxx_dsan %s -pthread -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#include <pthread.h>
+#include <stdlib.h>
+
+static pthread_barrier_t barrier;
+static void *p;
+
+static void *Free(void *) {
+  pthread_barrier_wait(&barrier);
+  free(p);
+  return nullptr;
+}
+
+int main() {
+  pthread_t first, second;
+  p = malloc(16);
+  pthread_barrier_init(&barrier, nullptr, 2);
+  pthread_create(&first, nullptr, Free, nullptr);
+  pthread_create(&second, nullptr, Free, nullptr);
+  pthread_join(first, nullptr);
+  pthread_join(second, nullptr);
+  return 0;
+}
+
+// CHECK: ERROR: DoubleFreeSanitizer: double-free on address
diff --git a/compiler-rt/test/dsan/TestCases/double-free.c b/compiler-rt/test/dsan/TestCases/double-free.c
new file mode 100644
index 0000000000000..2f7e3a3cc7bfe
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/double-free.c
@@ -0,0 +1,17 @@
+// RUN: %clang_dsan %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main(void) {
+  void *p = malloc(16);
+  free(p);
+  free(p);
+  return 0;
+}
+
+// CHECK: ERROR: DoubleFreeSanitizer: double-free on address
+// CHECK: Second free (the invalid free) of address
+// CHECK: First free of address
+// CHECK: Original allocation of address
+// CHECK: SUMMARY: DoubleFreeSanitizer: double-free on address
diff --git a/compiler-rt/test/dsan/TestCases/invalid-free.c b/compiler-rt/test/dsan/TestCases/invalid-free.c
new file mode 100644
index 0000000000000..975d46510e9dc
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/invalid-free.c
@@ -0,0 +1,12 @@
+// RUN: %clang_dsan %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main(void) {
+  char *p = malloc(16);
+  free(p + 1);
+  return 0;
+}
+
+// CHECK: ERROR: DoubleFreeSanitizer: invalid free on address
diff --git a/compiler-rt/test/dsan/TestCases/large-double-free.c b/compiler-rt/test/dsan/TestCases/large-double-free.c
new file mode 100644
index 0000000000000..e563d31302c5d
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/large-double-free.c
@@ -0,0 +1,15 @@
+// RUN: %clang_dsan %s -o %t
+// RUN: not %run %t 2>&1 | FileCheck %s
+
+#include <stdlib.h>
+
+int main(void) {
+  void *p = malloc(1 << 20);
+  free(p);
+  free(p);
+  return 0;
+}
+
+// CHECK: ERROR: DoubleFreeSanitizer: double-free on address
+// CHECK: First free of address
+// CHECK: Original allocation of address
diff --git a/compiler-rt/test/dsan/TestCases/realloc.c b/compiler-rt/test/dsan/TestCases/realloc.c
new file mode 100644
index 0000000000000..1e33af2dd5e93
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/realloc.c
@@ -0,0 +1,16 @@
+// RUN: %clang_dsan %s -o %t
+// RUN: %env_dsan_opts=allocator_may_return_null=1 %run %t
+
+#include <stdint.h>
+#include <stdlib.h>
+
+int main(void) {
+  char *p = malloc(16);
+  p[0] = 42;
+  if (realloc(p, SIZE_MAX) != NULL)
+    return 1;
+  if (p[0] != 42)
+    return 2;
+  free(p);
+  return 0;
+}
diff --git a/compiler-rt/test/dsan/TestCases/reallocarray.c b/compiler-rt/test/dsan/TestCases/reallocarray.c
new file mode 100644
index 0000000000000..444cbe86b0686
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/reallocarray.c
@@ -0,0 +1,18 @@
+// REQUIRES: linux
+// RUN: %clang_dsan %s -o %t
+// RUN: %env_dsan_opts=allocator_may_return_null=1 %run %t
+
+#define _GNU_SOURCE
+#include <stdint.h>
+#include <stdlib.h>
+
+int main(void) {
+  char *p = malloc(16);
+  p[0] = 42;
+  if (reallocarray(p, SIZE_MAX, 2) != NULL)
+    return 1;
+  if (p[0] != 42)
+    return 2;
+  free(p);
+  return 0;
+}
diff --git a/compiler-rt/test/dsan/TestCases/smoke.cpp b/compiler-rt/test/dsan/TestCases/smoke.cpp
new file mode 100644
index 0000000000000..b5713624eb542
--- /dev/null
+++ b/compiler-rt/test/dsan/TestCases/smoke.cpp
@@ -0,0 +1,10 @@
+// RUN: %clangxx_dsan %s -o %t
+// RUN: %run %t
+
+#include <cstdlib>
+
+int main() {
+  void *p = std::malloc(16);
+  std::free(p);
+  return 0;
+}
diff --git a/compiler-rt/test/dsan/lit.common.cfg.py b/compiler-rt/test/dsan/lit.common.cfg.py
new file mode 100644
index 0000000000000..95f00315d7b6f
--- /dev/null
+++ b/compiler-rt/test/dsan/lit.common.cfg.py
@@ -0,0 +1,114 @@
+# -*- Python -*-
+
+# Common configuration for running double-free detection tests under DSan.
+
+import os
+import re
+
+import lit.util
+
+
+def get_required_attr(config, attr_name):
+    attr_value = getattr(config, attr_name, None)
+    if attr_value is None:
+        lit_config.fatal(
+            "No attribute %r in test configuration! You may need to run "
+            "tests from your build directory or add this attribute "
+            "to lit.site.cfg.py " % attr_name
+        )
+    return attr_value
+
+
+# Setup source root.
+config.test_source_root = os.path.dirname(__file__)
+
+dsan_lit_test_mode = get_required_attr(config, "dsan_lit_test_mode")
+
+if dsan_lit_test_mode == "Standalone":
+    config.name = "DoubleFreeSanitizer-Standalone"
+    dsan_cflags = ["-fsanitize=doublefree"]
+    config.available_features.add("dsan-standalone")
+else:
+    lit_config.fatal("Unknown DSan test mode: %r" % dsan_lit_test_mode)
+config.name += config.name_suffix
+
+# Platform-specific default DSAN_OPTIONS for lit tests.
+default_common_opts_str = ":".join(list(config.default_sanitizer_opts))
+default_dsan_opts = default_common_opts_str
+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'.
+    # Also, make sure we do not overwhelm the syslog while testing.
+    default_dsan_opts += ":abort_on_error=0"
+    default_dsan_opts += ":log_to_syslog=0"
+
+if default_dsan_opts:
+    config.environment["DSAN_OPTIONS"] = default_dsan_opts
+    default_dsan_opts += ":"
+config.substitutions.append(
+    ("%env_dsan_opts=", "env DSAN_OPTIONS=" + default_dsan_opts)
+)
+
+if lit.util.which("strace"):
+    config.available_features.add("strace")
+
+clang_cflags = ["-O0", config.target_cflags] + config.debug_info_flags
+if config.android:
+    clang_cflags = clang_cflags + ["-fno-emulated-tls"]
+clang_cxxflags = config.cxx_mode_flags + clang_cflags
+dsan_incdir = config.test_source_root + "/../"
+clang_dsan_cflags = clang_cflags + dsan_cflags + ["-I%s" % dsan_incdir]
+clang_dsan_cxxflags = clang_cxxflags + dsan_cflags + ["-I%s" % dsan_incdir]
+
+config.clang_cflags = clang_cflags
+config.clang_cxxflags = clang_cxxflags
+
+
+def build_invocation(compile_flags):
+    return " " + " ".join([config.clang] + compile_flags) + " "
+
+
+config.substitutions.append(("%clang ", build_invocation(clang_cflags)))
+config.substitutions.append(("%clangxx ", build_invocation(clang_cxxflags)))
+config.substitutions.append(("%clang_dsan ", build_invocation(clang_dsan_cflags)))
+config.substitutions.append(("%clangxx_dsan ", build_invocation(clang_dsan_cxxflags)))
+
+
+# DoubleFreeSanitizer tests are currently supported on
+# Android{aarch64, x86, x86_64}, x86-64 Linux, PowerPC64 Linux, arm Linux, mips64 Linux, s390x Linux, loongarch64 Linux and x86_64 Darwin.
+supported_android = (
+    config.android
+    and config.target_arch in ["x86_64", "i386", "aarch64"]
+    and "android-thread-properties-api" in config.available_features
+)
+supported_linux = (
+    (not config.android)
+    and config.target_os == "Linux"
+    and config.host_arch
+    in [
+        "aarch64",
+        "x86_64",
+        "ppc64",
+        "ppc64le",
+        "mips64",
+        "riscv64",
+        "arm",
+        "armhf",
+        "armv7l",
+        "s390x",
+        "loongarch64",
+    ]
+)
+supported_darwin = config.target_os == "Darwin" and config.target_arch in ["x86_64"]
+supported_netbsd = config.target_os == "NetBSD" and config.target_arch in [
+    "x86_64",
+    "i386",
+]
+if not (supported_android or supported_linux or supported_darwin or supported_netbsd):
+    config.unsupported = True
+
+# Don't support Thumb due to broken fast unwinder
+if re.search("mthumb", config.target_cflags) is not None:
+    config.unsupported = True
+
+config.suffixes = [".c", ".cpp", ".mm"]
diff --git a/compiler-rt/test/dsan/lit.site.cfg.py.in b/compiler-rt/test/dsan/lit.site.cfg.py.in
new file mode 100644
index 0000000000000..a493baf439446
--- /dev/null
+++ b/compiler-rt/test/dsan/lit.site.cfg.py.in
@@ -0,0 +1,13 @@
+ at LIT_SITE_CFG_IN_HEADER@
+
+# Tool-specific config options.
+config.name_suffix = "@DSAN_TEST_CONFIG_SUFFIX@"
+config.target_cflags = "@DSAN_TEST_TARGET_CFLAGS@"
+config.dsan_lit_test_mode = "@DSAN_LIT_TEST_MODE@"
+config.target_arch = "@DSAN_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, "@DSAN_LIT_SOURCE_DIR@/lit.common.cfg.py")



More information about the cfe-commits mailing list