diff --git a/Cargo.lock b/Cargo.lock
index 339389f8569..680a454fb68 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3345,17 +3345,6 @@ dependencies = [
  "smallvec 1.0.0",
 ]
 
-[[package]]
-name = "rustc_asan"
-version = "0.0.0"
-dependencies = [
- "alloc",
- "build_helper",
- "cmake",
- "compiler_builtins",
- "core",
-]
-
 [[package]]
 name = "rustc_ast_lowering"
 version = "0.0.0"
@@ -3674,17 +3663,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "rustc_lsan"
-version = "0.0.0"
-dependencies = [
- "alloc",
- "build_helper",
- "cmake",
- "compiler_builtins",
- "core",
-]
-
 [[package]]
 name = "rustc_macros"
 version = "0.1.0"
@@ -3746,17 +3724,6 @@ dependencies = [
  "syntax",
 ]
 
-[[package]]
-name = "rustc_msan"
-version = "0.0.0"
-dependencies = [
- "alloc",
- "build_helper",
- "cmake",
- "compiler_builtins",
- "core",
-]
-
 [[package]]
 name = "rustc_parse"
 version = "0.0.0"
@@ -3929,17 +3896,6 @@ dependencies = [
  "syntax",
 ]
 
-[[package]]
-name = "rustc_tsan"
-version = "0.0.0"
-dependencies = [
- "alloc",
- "build_helper",
- "cmake",
- "compiler_builtins",
- "core",
-]
-
 [[package]]
 name = "rustc_typeck"
 version = "0.0.0"
@@ -4301,10 +4257,6 @@ dependencies = [
  "panic_unwind",
  "profiler_builtins",
  "rand 0.7.0",
- "rustc_asan",
- "rustc_lsan",
- "rustc_msan",
- "rustc_tsan",
  "unwind",
  "wasi 0.9.0+wasi-snapshot-preview1",
 ]
diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs
index 3a14b3e71c4..00c8e72a8f6 100644
--- a/src/bootstrap/builder.rs
+++ b/src/bootstrap/builder.rs
@@ -343,6 +343,7 @@ impl<'a> Builder<'a> {
                 tool::Rustdoc,
                 tool::Clippy,
                 native::Llvm,
+                native::Sanitizers,
                 tool::Rustfmt,
                 tool::Miri,
                 native::Lld
diff --git a/src/bootstrap/check.rs b/src/bootstrap/check.rs
index d4016f16fa9..b76515763fb 100644
--- a/src/bootstrap/check.rs
+++ b/src/bootstrap/check.rs
@@ -45,7 +45,7 @@ impl Step for Std {
         let compiler = builder.compiler(0, builder.config.build);
 
         let mut cargo = builder.cargo(compiler, Mode::Std, target, cargo_subcommand(builder.kind));
-        std_cargo(builder, &compiler, target, &mut cargo);
+        std_cargo(builder, target, &mut cargo);
 
         builder.info(&format!("Checking std artifacts ({} -> {})", &compiler.host, target));
         run_cargo(
diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs
index 7f0bb5813a4..eced03506ab 100644
--- a/src/bootstrap/compile.rs
+++ b/src/bootstrap/compile.rs
@@ -87,7 +87,7 @@ impl Step for Std {
         target_deps.extend(copy_third_party_objects(builder, &compiler, target).into_iter());
 
         let mut cargo = builder.cargo(compiler, Mode::Std, target, "build");
-        std_cargo(builder, &compiler, target, &mut cargo);
+        std_cargo(builder, target, &mut cargo);
 
         builder.info(&format!(
             "Building stage{} std artifacts ({} -> {})",
@@ -153,17 +153,18 @@ fn copy_third_party_objects(
         copy_and_stamp(Path::new(&src), "libunwind.a");
     }
 
+    if builder.config.sanitizers && compiler.stage != 0 {
+        // The sanitizers are only copied in stage1 or above,
+        // to avoid creating dependency on LLVM.
+        target_deps.extend(copy_sanitizers(builder, &compiler, target));
+    }
+
     target_deps
 }
 
 /// Configure cargo to compile the standard library, adding appropriate env vars
 /// and such.
-pub fn std_cargo(
-    builder: &Builder<'_>,
-    compiler: &Compiler,
-    target: Interned<String>,
-    cargo: &mut Cargo,
-) {
+pub fn std_cargo(builder: &Builder<'_>, target: Interned<String>, cargo: &mut Cargo) {
     if let Some(target) = env::var_os("MACOSX_STD_DEPLOYMENT_TARGET") {
         cargo.env("MACOSX_DEPLOYMENT_TARGET", target);
     }
@@ -206,19 +207,6 @@ pub fn std_cargo(
         let mut features = builder.std_features();
         features.push_str(&compiler_builtins_c_feature);
 
-        if compiler.stage != 0 && builder.config.sanitizers {
-            // This variable is used by the sanitizer runtime crates, e.g.
-            // rustc_lsan, to build the sanitizer runtime from C code
-            // When this variable is missing, those crates won't compile the C code,
-            // so we don't set this variable during stage0 where llvm-config is
-            // missing
-            // We also only build the runtimes when --enable-sanitizers (or its
-            // config.toml equivalent) is used
-            let llvm_config = builder.ensure(native::Llvm { target: builder.config.build });
-            cargo.env("LLVM_CONFIG", llvm_config);
-            cargo.env("RUSTC_BUILD_SANITIZERS", "1");
-        }
-
         cargo
             .arg("--features")
             .arg(features)
@@ -276,31 +264,43 @@ impl Step for StdLink {
         let libdir = builder.sysroot_libdir(target_compiler, target);
         let hostdir = builder.sysroot_libdir(target_compiler, compiler.host);
         add_to_sysroot(builder, &libdir, &hostdir, &libstd_stamp(builder, compiler, target));
-
-        if builder.config.sanitizers && compiler.stage != 0 && target == "x86_64-apple-darwin" {
-            // The sanitizers are only built in stage1 or above, so the dylibs will
-            // be missing in stage0 and causes panic. See the `std()` function above
-            // for reason why the sanitizers are not built in stage0.
-            copy_apple_sanitizer_dylibs(builder, &builder.native_dir(target), "osx", &libdir);
-        }
     }
 }
 
-fn copy_apple_sanitizer_dylibs(
+/// Copies sanitizer runtime libraries into target libdir.
+fn copy_sanitizers(
     builder: &Builder<'_>,
-    native_dir: &Path,
-    platform: &str,
-    into: &Path,
-) {
-    for &sanitizer in &["asan", "tsan"] {
-        let filename = format!("lib__rustc__clang_rt.{}_{}_dynamic.dylib", sanitizer, platform);
-        let mut src_path = native_dir.join(sanitizer);
-        src_path.push("build");
-        src_path.push("lib");
-        src_path.push("darwin");
-        src_path.push(&filename);
-        builder.copy(&src_path, &into.join(filename));
+    compiler: &Compiler,
+    target: Interned<String>,
+) -> Vec<PathBuf> {
+    let runtimes: Vec<native::SanitizerRuntime> = builder.ensure(native::Sanitizers { target });
+
+    if builder.config.dry_run {
+        return Vec::new();
     }
+
+    let mut target_deps = Vec::new();
+    let libdir = builder.sysroot_libdir(*compiler, target);
+
+    for runtime in &runtimes {
+        let dst = libdir.join(&runtime.name);
+        builder.copy(&runtime.path, &dst);
+
+        if target == "x86_64-apple-darwin" {
+            // Update the library install name reflect the fact it has been renamed.
+            let status = Command::new("install_name_tool")
+                .arg("-id")
+                .arg(format!("@rpath/{}", runtime.name))
+                .arg(&dst)
+                .status()
+                .expect("failed to execute `install_name_tool`");
+            assert!(status.success());
+        }
+
+        target_deps.push(dst);
+    }
+
+    target_deps
 }
 
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
diff --git a/src/bootstrap/dist.rs b/src/bootstrap/dist.rs
index e64d4c8637d..8d13df3ee21 100644
--- a/src/bootstrap/dist.rs
+++ b/src/bootstrap/dist.rs
@@ -984,10 +984,6 @@ impl Step for Src {
             "src/libcore",
             "src/libpanic_abort",
             "src/libpanic_unwind",
-            "src/librustc_asan",
-            "src/librustc_lsan",
-            "src/librustc_msan",
-            "src/librustc_tsan",
             "src/libstd",
             "src/libunwind",
             "src/libtest",
diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs
index a87b44c7989..204056598d9 100644
--- a/src/bootstrap/doc.rs
+++ b/src/bootstrap/doc.rs
@@ -391,7 +391,7 @@ impl Step for Std {
 
         let run_cargo_rustdoc_for = |package: &str| {
             let mut cargo = builder.cargo(compiler, Mode::Std, target, "rustdoc");
-            compile::std_cargo(builder, &compiler, target, &mut cargo);
+            compile::std_cargo(builder, target, &mut cargo);
 
             // Keep a whitelist so we do not build internal stdlib crates, these will be
             // build by the rustc step later if enabled.
diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs
index 2a4e9903e55..ce977f1bbc4 100644
--- a/src/bootstrap/native.rs
+++ b/src/bootstrap/native.rs
@@ -546,3 +546,118 @@ impl Step for TestHelpers {
             .compile("rust_test_helpers");
     }
 }
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
+pub struct Sanitizers {
+    pub target: Interned<String>,
+}
+
+impl Step for Sanitizers {
+    type Output = Vec<SanitizerRuntime>;
+
+    fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
+        run.path("src/llvm-project/compiler-rt").path("src/sanitizers")
+    }
+
+    fn make_run(run: RunConfig<'_>) {
+        run.builder.ensure(Sanitizers { target: run.target });
+    }
+
+    /// Builds sanitizer runtime libraries.
+    fn run(self, builder: &Builder<'_>) -> Self::Output {
+        let compiler_rt_dir = builder.src.join("src/llvm-project/compiler-rt");
+        if !compiler_rt_dir.exists() {
+            return Vec::new();
+        }
+
+        let out_dir = builder.native_dir(self.target).join("sanitizers");
+        let runtimes = supported_sanitizers(&out_dir, self.target);
+        if runtimes.is_empty() {
+            return runtimes;
+        }
+
+        let llvm_config = builder.ensure(Llvm { target: builder.config.build });
+        if builder.config.dry_run {
+            return runtimes;
+        }
+
+        let done_stamp = out_dir.join("sanitizers-finished-building");
+        if done_stamp.exists() {
+            builder.info(&format!(
+                "Assuming that sanitizers rebuild is not necessary. \
+                To force a rebuild, remove the file `{}`",
+                done_stamp.display()
+            ));
+            return runtimes;
+        }
+
+        builder.info(&format!("Building sanitizers for {}", self.target));
+        let _time = util::timeit(&builder);
+
+        let mut cfg = cmake::Config::new(&compiler_rt_dir);
+        cfg.target(&self.target);
+        cfg.host(&builder.config.build);
+        cfg.profile("Release");
+
+        cfg.define("CMAKE_C_COMPILER_TARGET", self.target);
+        cfg.define("COMPILER_RT_BUILD_BUILTINS", "OFF");
+        cfg.define("COMPILER_RT_BUILD_CRT", "OFF");
+        cfg.define("COMPILER_RT_BUILD_LIBFUZZER", "OFF");
+        cfg.define("COMPILER_RT_BUILD_PROFILE", "OFF");
+        cfg.define("COMPILER_RT_BUILD_SANITIZERS", "ON");
+        cfg.define("COMPILER_RT_BUILD_XRAY", "OFF");
+        cfg.define("COMPILER_RT_DEFAULT_TARGET_ONLY", "ON");
+        cfg.define("COMPILER_RT_USE_LIBCXX", "OFF");
+        cfg.define("LLVM_CONFIG_PATH", &llvm_config);
+
+        t!(fs::create_dir_all(&out_dir));
+        cfg.out_dir(out_dir);
+
+        for runtime in &runtimes {
+            cfg.build_target(&runtime.cmake_target);
+            cfg.build();
+        }
+
+        t!(fs::write(&done_stamp, b""));
+
+        runtimes
+    }
+}
+
+#[derive(Clone, Debug)]
+pub struct SanitizerRuntime {
+    /// CMake target used to build the runtime.
+    pub cmake_target: String,
+    /// Path to the built runtime library.
+    pub path: PathBuf,
+    /// Library filename that will be used rustc.
+    pub name: String,
+}
+
+/// Returns sanitizers available on a given target.
+fn supported_sanitizers(out_dir: &Path, target: Interned<String>) -> Vec<SanitizerRuntime> {
+    let mut result = Vec::new();
+    match &*target {
+        "x86_64-apple-darwin" => {
+            for s in &["asan", "lsan", "tsan"] {
+                result.push(SanitizerRuntime {
+                    cmake_target: format!("clang_rt.{}_osx_dynamic", s),
+                    path: out_dir
+                        .join(&format!("build/lib/darwin/libclang_rt.{}_osx_dynamic.dylib", s)),
+                    name: format!("librustc_rt.{}.dylib", s),
+                });
+            }
+        }
+        "x86_64-unknown-linux-gnu" => {
+            for s in &["asan", "lsan", "msan", "tsan"] {
+                result.push(SanitizerRuntime {
+                    cmake_target: format!("clang_rt.{}-x86_64", s),
+                    path: out_dir.join(&format!("build/lib/linux/libclang_rt.{}-x86_64.a", s)),
+                    name: format!("librustc_rt.{}.a", s),
+                });
+            }
+        }
+        _ => {}
+    }
+    result
+}
diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs
index b5c8de057d0..10e07489e12 100644
--- a/src/bootstrap/test.rs
+++ b/src/bootstrap/test.rs
@@ -1659,7 +1659,7 @@ impl Step for Crate {
         let mut cargo = builder.cargo(compiler, mode, target, test_kind.subcommand());
         match mode {
             Mode::Std => {
-                compile::std_cargo(builder, &compiler, target, &mut cargo);
+                compile::std_cargo(builder, target, &mut cargo);
             }
             Mode::Rustc => {
                 builder.ensure(compile::Rustc { compiler, target });
diff --git a/src/build_helper/lib.rs b/src/build_helper/lib.rs
index 3f42533238a..43c3c5773ce 100644
--- a/src/build_helper/lib.rs
+++ b/src/build_helper/lib.rs
@@ -1,7 +1,5 @@
-use std::fs::File;
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
-use std::thread;
 use std::time::{SystemTime, UNIX_EPOCH};
 use std::{env, fs};
 
@@ -181,108 +179,6 @@ pub fn up_to_date(src: &Path, dst: &Path) -> bool {
     }
 }
 
-#[must_use]
-pub struct NativeLibBoilerplate {
-    pub src_dir: PathBuf,
-    pub out_dir: PathBuf,
-}
-
-impl NativeLibBoilerplate {
-    /// On macOS we don't want to ship the exact filename that compiler-rt builds.
-    /// This conflicts with the system and ours is likely a wildly different
-    /// version, so they can't be substituted.
-    ///
-    /// As a result, we rename it here but we need to also use
-    /// `install_name_tool` on macOS to rename the commands listed inside of it to
-    /// ensure it's linked against correctly.
-    pub fn fixup_sanitizer_lib_name(&self, sanitizer_name: &str) {
-        if env::var("TARGET").unwrap() != "x86_64-apple-darwin" {
-            return;
-        }
-
-        let dir = self.out_dir.join("build/lib/darwin");
-        let name = format!("clang_rt.{}_osx_dynamic", sanitizer_name);
-        let src = dir.join(&format!("lib{}.dylib", name));
-        let new_name = format!("lib__rustc__{}.dylib", name);
-        let dst = dir.join(&new_name);
-
-        println!("{} => {}", src.display(), dst.display());
-        fs::rename(&src, &dst).unwrap();
-        let status = Command::new("install_name_tool")
-            .arg("-id")
-            .arg(format!("@rpath/{}", new_name))
-            .arg(&dst)
-            .status()
-            .expect("failed to execute `install_name_tool`");
-        assert!(status.success());
-    }
-}
-
-impl Drop for NativeLibBoilerplate {
-    fn drop(&mut self) {
-        if !thread::panicking() {
-            t!(File::create(self.out_dir.join("rustbuild.timestamp")));
-        }
-    }
-}
-
-// Perform standard preparations for native libraries that are build only once for all stages.
-// Emit rerun-if-changed and linking attributes for Cargo, check if any source files are
-// updated, calculate paths used later in actual build with CMake/make or C/C++ compiler.
-// If Err is returned, then everything is up-to-date and further build actions can be skipped.
-// Timestamps are created automatically when the result of `native_lib_boilerplate` goes out
-// of scope, so all the build actions should be completed until then.
-pub fn native_lib_boilerplate(
-    src_dir: &Path,
-    out_name: &str,
-    link_name: &str,
-    search_subdir: &str,
-) -> Result<NativeLibBoilerplate, ()> {
-    rerun_if_changed_anything_in_dir(src_dir);
-
-    let out_dir =
-        env::var_os("RUSTBUILD_NATIVE_DIR").unwrap_or_else(|| env::var_os("OUT_DIR").unwrap());
-    let out_dir = PathBuf::from(out_dir).join(out_name);
-    t!(fs::create_dir_all(&out_dir));
-    if link_name.contains('=') {
-        println!("cargo:rustc-link-lib={}", link_name);
-    } else {
-        println!("cargo:rustc-link-lib=static={}", link_name);
-    }
-    println!("cargo:rustc-link-search=native={}", out_dir.join(search_subdir).display());
-
-    let timestamp = out_dir.join("rustbuild.timestamp");
-    if !up_to_date(Path::new("build.rs"), &timestamp) || !up_to_date(src_dir, &timestamp) {
-        Ok(NativeLibBoilerplate { src_dir: src_dir.to_path_buf(), out_dir })
-    } else {
-        Err(())
-    }
-}
-
-pub fn sanitizer_lib_boilerplate(
-    sanitizer_name: &str,
-) -> Result<(NativeLibBoilerplate, String), ()> {
-    let (link_name, search_path, apple) = match &*env::var("TARGET").unwrap() {
-        "x86_64-unknown-linux-gnu" => {
-            (format!("clang_rt.{}-x86_64", sanitizer_name), "build/lib/linux", false)
-        }
-        "x86_64-apple-darwin" => {
-            (format!("clang_rt.{}_osx_dynamic", sanitizer_name), "build/lib/darwin", true)
-        }
-        _ => return Err(()),
-    };
-    let to_link = if apple {
-        format!("dylib=__rustc__{}", link_name)
-    } else {
-        format!("static={}", link_name)
-    };
-    // This env var is provided by rustbuild to tell us where `compiler-rt`
-    // lives.
-    let dir = env::var_os("RUST_COMPILER_RT_ROOT").unwrap();
-    let lib = native_lib_boilerplate(dir.as_ref(), sanitizer_name, &to_link, search_path)?;
-    Ok((lib, link_name))
-}
-
 fn dir_up_to_date(src: &Path, threshold: SystemTime) -> bool {
     t!(fs::read_dir(src)).map(|e| t!(e)).all(|e| {
         let meta = t!(e.metadata());
diff --git a/src/doc/unstable-book/src/compiler-flags/sanitizer.md b/src/doc/unstable-book/src/compiler-flags/sanitizer.md
new file mode 100644
index 00000000000..cbb90bd3bb3
--- /dev/null
+++ b/src/doc/unstable-book/src/compiler-flags/sanitizer.md
@@ -0,0 +1,163 @@
+# `sanitizer`
+
+The tracking issue for this feature is: [#39699](https://github.com/rust-lang/rust/issues/39699).
+
+------------------------
+
+This feature allows for use of one of following sanitizers:
+
+* [AddressSanitizer][clang-asan] a faster memory error detector. Can
+  detect out-of-bounds access to heap, stack, and globals, use after free, use
+  after return, double free, invalid free, memory leaks.
+* [LeakSanitizer][clang-lsan] a run-time memory leak detector.
+* [MemorySanitizer][clang-msan] a detector of uninitialized reads.
+* [ThreadSanitizer][clang-tsan] a fast data race detector.
+
+To enable a sanitizer compile with `-Zsanitizer=...` option, where value is one
+of `address`, `leak`, `memory` or `thread`.
+
+# Examples
+
+This sections show various issues that can be detected with sanitizers.  For
+simplicity, the examples are prepared under assumption that optimization level
+used is zero.
+
+## AddressSanitizer
+
+Stack buffer overflow:
+
+```shell
+$ cat a.rs
+fn main() {
+    let xs = [0, 1, 2, 3];
+    let _y = unsafe { *xs.as_ptr().offset(4) };
+}
+$ rustc -Zsanitizer=address a.rs
+$ ./a
+=================================================================
+==10029==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffcc15f43d0 at pc 0x55f77dc015c5 bp 0x7ffcc15f4390 sp 0x7ffcc15f4388
+READ of size 4 at 0x7ffcc15f43d0 thread T0
+    #0 0x55f77dc015c4 in a::main::hab3bd2a745c2d0ac (/tmp/a+0xa5c4)
+    #1 0x55f77dc01cdb in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::haa8c76d1faa7b7ca (/tmp/a+0xacdb)
+    #2 0x55f77dc90f02 in std::rt::lang_start_internal::_$u7b$$u7b$closure$u7d$$u7d$::hfeb9a1aef9ac820d /rustc/c27f7568bc74c418996892028a629eed5a7f5f00/src/libstd/rt.rs:48:12
+    #3 0x55f77dc90f02 in std::panicking::try::do_call::h12f0919717b8e0a6 /rustc/c27f7568bc74c418996892028a629eed5a7f5f00/src/libstd/panicking.rs:288:39
+    #4 0x55f77dc926c9 in __rust_maybe_catch_panic /rustc/c27f7568bc74c418996892028a629eed5a7f5f00/src/libpanic_unwind/lib.rs:80:7
+    #5 0x55f77dc9197c in std::panicking::try::h413b21cdcd6cfd86 /rustc/c27f7568bc74c418996892028a629eed5a7f5f00/src/libstd/panicking.rs:267:12
+    #6 0x55f77dc9197c in std::panic::catch_unwind::hc5cc8ef2fd73424d /rustc/c27f7568bc74c418996892028a629eed5a7f5f00/src/libstd/panic.rs:396:8
+    #7 0x55f77dc9197c in std::rt::lang_start_internal::h2039f418ab92218f /rustc/c27f7568bc74c418996892028a629eed5a7f5f00/src/libstd/rt.rs:47:24
+    #8 0x55f77dc01c61 in std::rt::lang_start::ha905d28f6b61d691 (/tmp/a+0xac61)
+    #9 0x55f77dc0163a in main (/tmp/a+0xa63a)
+    #10 0x7f9b3cf5bbba in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26bba)
+    #11 0x55f77dc01289 in _start (/tmp/a+0xa289)
+
+Address 0x7ffcc15f43d0 is located in stack of thread T0 at offset 48 in frame
+    #0 0x55f77dc0135f in a::main::hab3bd2a745c2d0ac (/tmp/a+0xa35f)
+
+  This frame has 1 object(s):
+    [32, 48) 'xs' <== Memory access at offset 48 overflows this variable
+HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
+      (longjmp and C++ exceptions *are* supported)
+SUMMARY: AddressSanitizer: stack-buffer-overflow (/tmp/a+0xa5c4) in a::main::hab3bd2a745c2d0ac
+Shadow bytes around the buggy address:
+  0x1000182b6820: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+  0x1000182b6830: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+  0x1000182b6840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+  0x1000182b6850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+  0x1000182b6860: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+=>0x1000182b6870: 00 00 00 00 f1 f1 f1 f1 00 00[f3]f3 00 00 00 00
+  0x1000182b6880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+  0x1000182b6890: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+  0x1000182b68a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+  0x1000182b68b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+  0x1000182b68c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+Shadow byte legend (one shadow byte represents 8 application bytes):
+  Addressable:           00
+  Partially addressable: 01 02 03 04 05 06 07 
+  Heap left redzone:       fa
+  Freed heap region:       fd
+  Stack left redzone:      f1
+  Stack mid redzone:       f2
+  Stack right redzone:     f3
+  Stack after return:      f5
+  Stack use after scope:   f8
+  Global redzone:          f9
+  Global init order:       f6
+  Poisoned by user:        f7
+  Container overflow:      fc
+  Array cookie:            ac
+  Intra object redzone:    bb
+  ASan internal:           fe
+  Left alloca redzone:     ca
+  Right alloca redzone:    cb
+  Shadow gap:              cc
+==10029==ABORTING
+```
+
+## MemorySanitizer
+
+Use of uninitialized memory. Note that we are using `-Zbuild-std` to instrument
+standard library, and passing `-msan-track-origins=2` to the LLVM to track
+origins of uninitialized memory:
+
+```shell
+$ cat src/main.rs
+use std::mem::MaybeUninit;
+
+fn main() {
+    unsafe {
+        let a = MaybeUninit::<[usize; 4]>::uninit();
+        let a = a.assume_init();
+        println!("{}", a[2]);
+    }
+}
+
+$ env RUSTFLAGS="-Zsanitizer=memory -Cllvm-args=-msan-track-origins=2" cargo -Zbuild-std run --target x86_64-unknown-linux-gnu
+==9416==WARNING: MemorySanitizer: use-of-uninitialized-value
+    #0 0x560c04f7488a in core::fmt::num::imp::fmt_u64::haa293b0b098501ca $RUST/build/x86_64-unknown-linux-gnu/stage1/lib/rustlib/src/rust/src/libcore/fmt/num.rs:202:16
+...
+  Uninitialized value was stored to memory at
+    #0 0x560c04ae898a in __msan_memcpy.part.0 $RUST/src/llvm-project/compiler-rt/lib/msan/msan_interceptors.cc:1558:3
+    #1 0x560c04b2bf88 in memory::main::hd2333c1899d997f5 $CWD/src/main.rs:6:16
+
+  Uninitialized value was created by an allocation of 'a' in the stack frame of function '_ZN6memory4main17hd2333c1899d997f5E'
+    #0 0x560c04b2bc50 in memory::main::hd2333c1899d997f5 $CWD/src/main.rs:3
+```
+
+
+# Instrumentation of external dependencies and std
+
+The sanitizers to varying degrees work correctly with partially instrumented
+code. On the one extreme is LeakSanitizer that doesn't use any compile time
+instrumentation, on the other is MemorySanitizer that requires that all program
+code to be instrumented (failing to achieve that will inevitably result in
+false positives).
+
+It is strongly recommended to combine sanitizers with recompiled and
+instrumented standard library, for example using [cargo `-Zbuild-std`
+functionality][build-std].
+
+[build-std]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std
+
+# Build scripts and procedural macros
+
+Use of sanitizers together with build scripts and procedural macros is
+technically possible, but in almost all cases it would be best avoided.  This
+is especially true for procedural macros which would require an instrumented
+version of rustc.
+
+In more practical terms when using cargo always remember to pass `--target`
+flag, so that rustflags will not be applied to build scripts and procedural
+macros.
+
+# Additional Information
+
+* [Sanitizers project page](https://github.com/google/sanitizers/wiki/)
+* [AddressSanitizer in Clang][clang-asan]
+* [LeakSanitizer in Clang][clang-lsan]
+* [MemorySanitizer in Clang][clang-msan]
+* [ThreadSanitizer in Clang][clang-tsan]
+
+[clang-asan]: https://clang.llvm.org/docs/AddressSanitizer.html
+[clang-lsan]: https://clang.llvm.org/docs/LeakSanitizer.html
+[clang-msan]: https://clang.llvm.org/docs/MemorySanitizer.html
+[clang-tsan]: https://clang.llvm.org/docs/ThreadSanitizer.html
diff --git a/src/doc/unstable-book/src/library-features/sanitizer-runtime-lib.md b/src/doc/unstable-book/src/library-features/sanitizer-runtime-lib.md
deleted file mode 100644
index 82ae67fc05a..00000000000
--- a/src/doc/unstable-book/src/library-features/sanitizer-runtime-lib.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# `sanitizer_runtime_lib`
-
-This feature is internal to the Rust compiler and is not intended for general use.
-
-------------------------
diff --git a/src/librustc/query/mod.rs b/src/librustc/query/mod.rs
index 4a2ec9b9687..9de46f86200 100644
--- a/src/librustc/query/mod.rs
+++ b/src/librustc/query/mod.rs
@@ -686,10 +686,6 @@ rustc_queries! {
             fatal_cycle
             desc { "checking if the crate has_panic_handler" }
         }
-        query is_sanitizer_runtime(_: CrateNum) -> bool {
-            fatal_cycle
-            desc { "query a crate is `#![sanitizer_runtime]`" }
-        }
         query is_profiler_runtime(_: CrateNum) -> bool {
             fatal_cycle
             desc { "query a crate is `#![profiler_runtime]`" }
diff --git a/src/librustc_asan/Cargo.toml b/src/librustc_asan/Cargo.toml
deleted file mode 100644
index df117de8720..00000000000
--- a/src/librustc_asan/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-authors = ["The Rust Project Developers"]
-build = "build.rs"
-name = "rustc_asan"
-version = "0.0.0"
-edition = "2018"
-
-[lib]
-name = "rustc_asan"
-path = "lib.rs"
-test = false
-
-[build-dependencies]
-build_helper = { path = "../build_helper" }
-cmake = "0.1.38"
-
-[dependencies]
-alloc = { path = "../liballoc" }
-core = { path = "../libcore" }
-compiler_builtins = "0.1.0"
diff --git a/src/librustc_asan/build.rs b/src/librustc_asan/build.rs
deleted file mode 100644
index e276dc16c35..00000000000
--- a/src/librustc_asan/build.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use build_helper::sanitizer_lib_boilerplate;
-use std::env;
-
-use cmake::Config;
-
-fn main() {
-    println!("cargo:rerun-if-env-changed=RUSTC_BUILD_SANITIZERS");
-    if env::var("RUSTC_BUILD_SANITIZERS") != Ok("1".to_string()) {
-        return;
-    }
-    if let Some(llvm_config) = env::var_os("LLVM_CONFIG") {
-        build_helper::restore_library_path();
-
-        let (native, target) = match sanitizer_lib_boilerplate("asan") {
-            Ok(native) => native,
-            _ => return,
-        };
-
-        Config::new(&native.src_dir)
-            .define("COMPILER_RT_BUILD_SANITIZERS", "ON")
-            .define("COMPILER_RT_BUILD_BUILTINS", "OFF")
-            .define("COMPILER_RT_BUILD_XRAY", "OFF")
-            .define("LLVM_CONFIG_PATH", llvm_config)
-            .out_dir(&native.out_dir)
-            .build_target(&target)
-            .build();
-        native.fixup_sanitizer_lib_name("asan");
-    }
-    println!("cargo:rerun-if-env-changed=LLVM_CONFIG");
-}
diff --git a/src/librustc_asan/lib.rs b/src/librustc_asan/lib.rs
deleted file mode 100644
index bdbc154f4e8..00000000000
--- a/src/librustc_asan/lib.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-#![sanitizer_runtime]
-#![feature(nll)]
-#![feature(sanitizer_runtime)]
-#![feature(staged_api)]
-#![no_std]
-#![unstable(
-    feature = "sanitizer_runtime_lib",
-    reason = "internal implementation detail of sanitizers",
-    issue = "none"
-)]
diff --git a/src/librustc_codegen_ssa/back/link.rs b/src/librustc_codegen_ssa/back/link.rs
index 253225f308e..17582862c4d 100644
--- a/src/librustc_codegen_ssa/back/link.rs
+++ b/src/librustc_codegen_ssa/back/link.rs
@@ -531,6 +531,7 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(
 
     {
         let mut linker = codegen_results.linker_info.to_linker(cmd, &sess, flavor, target_cpu);
+        link_sanitizer_runtime(sess, crate_type, &mut *linker);
         link_args::<B>(
             &mut *linker,
             flavor,
@@ -735,6 +736,47 @@ fn link_natively<'a, B: ArchiveBuilder<'a>>(
     }
 }
 
+fn link_sanitizer_runtime(sess: &Session, crate_type: config::CrateType, linker: &mut dyn Linker) {
+    let sanitizer = match &sess.opts.debugging_opts.sanitizer {
+        Some(s) => s,
+        None => return,
+    };
+
+    if crate_type != config::CrateType::Executable {
+        return;
+    }
+
+    let name = match sanitizer {
+        Sanitizer::Address => "asan",
+        Sanitizer::Leak => "lsan",
+        Sanitizer::Memory => "msan",
+        Sanitizer::Thread => "tsan",
+    };
+
+    let default_sysroot = filesearch::get_or_default_sysroot();
+    let default_tlib =
+        filesearch::make_target_lib_path(&default_sysroot, sess.opts.target_triple.triple());
+
+    match sess.opts.target_triple.triple() {
+        "x86_64-apple-darwin" => {
+            // On Apple platforms, the sanitizer is always built as a dylib, and
+            // LLVM will link to `@rpath/*.dylib`, so we need to specify an
+            // rpath to the library as well (the rpath should be absolute, see
+            // PR #41352 for details).
+            let libname = format!("rustc_rt.{}", name);
+            let rpath = default_tlib.to_str().expect("non-utf8 component in path");
+            linker.args(&["-Wl,-rpath".into(), "-Xlinker".into(), rpath.into()]);
+            linker.link_dylib(Symbol::intern(&libname));
+        }
+        "x86_64-unknown-linux-gnu" => {
+            let filename = format!("librustc_rt.{}.a", name);
+            let path = default_tlib.join(&filename);
+            linker.link_whole_rlib(&path);
+        }
+        _ => {}
+    }
+}
+
 /// Returns a boolean indicating whether the specified crate should be ignored
 /// during LTO.
 ///
@@ -1415,12 +1457,6 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>(
             _ if codegen_results.crate_info.profiler_runtime == Some(cnum) => {
                 add_static_crate::<B>(cmd, sess, codegen_results, tmpdir, crate_type, cnum);
             }
-            _ if codegen_results.crate_info.sanitizer_runtime == Some(cnum)
-                && crate_type == config::CrateType::Executable =>
-            {
-                // Link the sanitizer runtimes only if we are actually producing an executable
-                link_sanitizer_runtime::<B>(cmd, sess, codegen_results, tmpdir, cnum);
-            }
             // compiler-builtins are always placed last to ensure that they're
             // linked correctly.
             _ if codegen_results.crate_info.compiler_builtins == Some(cnum) => {
@@ -1457,47 +1493,6 @@ fn add_upstream_rust_crates<'a, B: ArchiveBuilder<'a>>(
         }
     }
 
-    // We must link the sanitizer runtime using -Wl,--whole-archive but since
-    // it's packed in a .rlib, it contains stuff that are not objects that will
-    // make the linker error. So we must remove those bits from the .rlib before
-    // linking it.
-    fn link_sanitizer_runtime<'a, B: ArchiveBuilder<'a>>(
-        cmd: &mut dyn Linker,
-        sess: &'a Session,
-        codegen_results: &CodegenResults,
-        tmpdir: &Path,
-        cnum: CrateNum,
-    ) {
-        let src = &codegen_results.crate_info.used_crate_source[&cnum];
-        let cratepath = &src.rlib.as_ref().unwrap().0;
-
-        if sess.target.target.options.is_like_osx {
-            // On Apple platforms, the sanitizer is always built as a dylib, and
-            // LLVM will link to `@rpath/*.dylib`, so we need to specify an
-            // rpath to the library as well (the rpath should be absolute, see
-            // PR #41352 for details).
-            //
-            // FIXME: Remove this logic into librustc_*san once Cargo supports it
-            let rpath = cratepath.parent().unwrap();
-            let rpath = rpath.to_str().expect("non-utf8 component in path");
-            cmd.args(&["-Wl,-rpath".into(), "-Xlinker".into(), rpath.into()]);
-        }
-
-        let dst = tmpdir.join(cratepath.file_name().unwrap());
-        let mut archive = <B as ArchiveBuilder>::new(sess, &dst, Some(cratepath));
-        archive.update_symbols();
-
-        for f in archive.src_files() {
-            if f.ends_with(RLIB_BYTECODE_EXTENSION) || f == METADATA_FILENAME {
-                archive.remove_file(&f);
-            }
-        }
-
-        archive.build();
-
-        cmd.link_whole_rlib(&dst);
-    }
-
     // Adds the static "rlib" versions of all crates to the command line.
     // There's a bit of magic which happens here specifically related to LTO and
     // dynamic libraries. Specifically:
diff --git a/src/librustc_codegen_ssa/base.rs b/src/librustc_codegen_ssa/base.rs
index 9b9434539a8..ab5d67e5783 100644
--- a/src/librustc_codegen_ssa/base.rs
+++ b/src/librustc_codegen_ssa/base.rs
@@ -730,7 +730,6 @@ impl CrateInfo {
             panic_runtime: None,
             compiler_builtins: None,
             profiler_runtime: None,
-            sanitizer_runtime: None,
             is_no_builtins: Default::default(),
             native_libraries: Default::default(),
             used_libraries: tcx.native_libraries(LOCAL_CRATE),
@@ -766,9 +765,6 @@ impl CrateInfo {
             if tcx.is_profiler_runtime(cnum) {
                 info.profiler_runtime = Some(cnum);
             }
-            if tcx.is_sanitizer_runtime(cnum) {
-                info.sanitizer_runtime = Some(cnum);
-            }
             if tcx.is_no_builtins(cnum) {
                 info.is_no_builtins.insert(cnum);
             }
diff --git a/src/librustc_codegen_ssa/lib.rs b/src/librustc_codegen_ssa/lib.rs
index a8d051db8b4..ee527ecb509 100644
--- a/src/librustc_codegen_ssa/lib.rs
+++ b/src/librustc_codegen_ssa/lib.rs
@@ -122,7 +122,6 @@ pub struct CrateInfo {
     pub panic_runtime: Option<CrateNum>,
     pub compiler_builtins: Option<CrateNum>,
     pub profiler_runtime: Option<CrateNum>,
-    pub sanitizer_runtime: Option<CrateNum>,
     pub is_no_builtins: FxHashSet<CrateNum>,
     pub native_libraries: FxHashMap<CrateNum, Lrc<Vec<NativeLibrary>>>,
     pub crate_name: FxHashMap<CrateNum, String>,
diff --git a/src/librustc_feature/active.rs b/src/librustc_feature/active.rs
index d3876ecc7c3..319cd88f245 100644
--- a/src/librustc_feature/active.rs
+++ b/src/librustc_feature/active.rs
@@ -192,9 +192,6 @@ declare_features! (
     /// Allows using the `unadjusted` ABI; perma-unstable.
     (active, abi_unadjusted, "1.16.0", None, None),
 
-    /// Allows identifying crates that contain sanitizer runtimes.
-    (active, sanitizer_runtime, "1.17.0", None, None),
-
     /// Used to identify crates that contain the profiler runtime.
     (active, profiler_runtime, "1.18.0", None, None),
 
diff --git a/src/librustc_feature/builtin_attrs.rs b/src/librustc_feature/builtin_attrs.rs
index 3cbf96b6d29..a38726e3de8 100644
--- a/src/librustc_feature/builtin_attrs.rs
+++ b/src/librustc_feature/builtin_attrs.rs
@@ -409,11 +409,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         "the `#[compiler_builtins]` attribute is used to identify the `compiler_builtins` crate \
         which contains compiler-rt intrinsics and will never be stable",
     ),
-    gated!(
-        sanitizer_runtime, Whitelisted, template!(Word),
-        "the `#[sanitizer_runtime]` attribute is used to identify crates that contain the runtime \
-        of a sanitizer and will never be stable",
-    ),
     gated!(
         profiler_runtime, Whitelisted, template!(Word),
         "the `#[profiler_runtime]` attribute is used to identify the `profiler_builtins` crate \
diff --git a/src/librustc_feature/removed.rs b/src/librustc_feature/removed.rs
index 1eeedd77214..d5b6fe81c7b 100644
--- a/src/librustc_feature/removed.rs
+++ b/src/librustc_feature/removed.rs
@@ -74,6 +74,8 @@ declare_features! (
     (removed, pushpop_unsafe, "1.2.0", None, None, None),
     (removed, needs_allocator, "1.4.0", Some(27389), None,
      Some("subsumed by `#![feature(allocator_internals)]`")),
+    /// Allows identifying crates that contain sanitizer runtimes.
+    (removed, sanitizer_runtime, "1.17.0", None, None, None),
     (removed, proc_macro_mod, "1.27.0", Some(54727), None,
      Some("subsumed by `#![feature(proc_macro_hygiene)]`")),
     (removed, proc_macro_expr, "1.27.0", Some(54727), None,
diff --git a/src/librustc_lsan/Cargo.toml b/src/librustc_lsan/Cargo.toml
deleted file mode 100644
index 9a24361f44e..00000000000
--- a/src/librustc_lsan/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-authors = ["The Rust Project Developers"]
-build = "build.rs"
-name = "rustc_lsan"
-version = "0.0.0"
-edition = "2018"
-
-[lib]
-name = "rustc_lsan"
-path = "lib.rs"
-test = false
-
-[build-dependencies]
-build_helper = { path = "../build_helper" }
-cmake = "0.1.38"
-
-[dependencies]
-alloc = { path = "../liballoc" }
-core = { path = "../libcore" }
-compiler_builtins = "0.1.0"
diff --git a/src/librustc_lsan/build.rs b/src/librustc_lsan/build.rs
deleted file mode 100644
index 6201bc9356d..00000000000
--- a/src/librustc_lsan/build.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use build_helper::sanitizer_lib_boilerplate;
-use std::env;
-
-use cmake::Config;
-
-fn main() {
-    println!("cargo:rerun-if-env-changed=RUSTC_BUILD_SANITIZERS");
-    if env::var("RUSTC_BUILD_SANITIZERS") != Ok("1".to_string()) {
-        return;
-    }
-    if let Some(llvm_config) = env::var_os("LLVM_CONFIG") {
-        build_helper::restore_library_path();
-
-        let (native, target) = match sanitizer_lib_boilerplate("lsan") {
-            Ok(native) => native,
-            _ => return,
-        };
-
-        Config::new(&native.src_dir)
-            .define("COMPILER_RT_BUILD_SANITIZERS", "ON")
-            .define("COMPILER_RT_BUILD_BUILTINS", "OFF")
-            .define("COMPILER_RT_BUILD_XRAY", "OFF")
-            .define("LLVM_CONFIG_PATH", llvm_config)
-            .out_dir(&native.out_dir)
-            .build_target(&target)
-            .build();
-    }
-    println!("cargo:rerun-if-env-changed=LLVM_CONFIG");
-}
diff --git a/src/librustc_lsan/lib.rs b/src/librustc_lsan/lib.rs
deleted file mode 100644
index bdbc154f4e8..00000000000
--- a/src/librustc_lsan/lib.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-#![sanitizer_runtime]
-#![feature(nll)]
-#![feature(sanitizer_runtime)]
-#![feature(staged_api)]
-#![no_std]
-#![unstable(
-    feature = "sanitizer_runtime_lib",
-    reason = "internal implementation detail of sanitizers",
-    issue = "none"
-)]
diff --git a/src/librustc_metadata/creader.rs b/src/librustc_metadata/creader.rs
index 17c2e3303cb..181f872154c 100644
--- a/src/librustc_metadata/creader.rs
+++ b/src/librustc_metadata/creader.rs
@@ -6,7 +6,7 @@ use crate::rmeta::{CrateDep, CrateMetadata, CrateNumMap, CrateRoot, MetadataBlob
 use rustc::hir::map::Definitions;
 use rustc::middle::cstore::DepKind;
 use rustc::middle::cstore::{CrateSource, ExternCrate, ExternCrateSource, MetadataLoaderDyn};
-use rustc::session::config::{self, Sanitizer};
+use rustc::session::config;
 use rustc::session::search_paths::PathKind;
 use rustc::session::{CrateDisambiguator, Session};
 use rustc::ty::TyCtxt;
@@ -671,108 +671,6 @@ impl<'a> CrateLoader<'a> {
         self.inject_dependency_if(cnum, "a panic runtime", &|data| data.needs_panic_runtime());
     }
 
-    fn inject_sanitizer_runtime(&mut self) {
-        if let Some(ref sanitizer) = self.sess.opts.debugging_opts.sanitizer {
-            // Sanitizers can only be used on some tested platforms with
-            // executables linked to `std`
-            const ASAN_SUPPORTED_TARGETS: &[&str] =
-                &["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
-            const TSAN_SUPPORTED_TARGETS: &[&str] =
-                &["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
-            const LSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"];
-            const MSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"];
-
-            let supported_targets = match *sanitizer {
-                Sanitizer::Address => ASAN_SUPPORTED_TARGETS,
-                Sanitizer::Thread => TSAN_SUPPORTED_TARGETS,
-                Sanitizer::Leak => LSAN_SUPPORTED_TARGETS,
-                Sanitizer::Memory => MSAN_SUPPORTED_TARGETS,
-            };
-            if !supported_targets.contains(&&*self.sess.opts.target_triple.triple()) {
-                self.sess.err(&format!(
-                    "{:?}Sanitizer only works with the `{}` target",
-                    sanitizer,
-                    supported_targets.join("` or `")
-                ));
-                return;
-            }
-
-            // firstyear 2017 - during testing I was unable to access an OSX machine
-            // to make this work on different crate types. As a result, today I have
-            // only been able to test and support linux as a target.
-            if self.sess.opts.target_triple.triple() == "x86_64-unknown-linux-gnu" {
-                if !self.sess.crate_types.borrow().iter().all(|ct| {
-                    match *ct {
-                        // Link the runtime
-                        config::CrateType::Executable => true,
-                        // This crate will be compiled with the required
-                        // instrumentation pass
-                        config::CrateType::Staticlib
-                        | config::CrateType::Rlib
-                        | config::CrateType::Dylib
-                        | config::CrateType::Cdylib => false,
-                        _ => {
-                            self.sess.err(&format!(
-                                "Only executables, staticlibs, \
-                                cdylibs, dylibs and rlibs can be compiled with \
-                                `-Z sanitizer`"
-                            ));
-                            false
-                        }
-                    }
-                }) {
-                    return;
-                }
-            } else {
-                if !self.sess.crate_types.borrow().iter().all(|ct| {
-                    match *ct {
-                        // Link the runtime
-                        config::CrateType::Executable => true,
-                        // This crate will be compiled with the required
-                        // instrumentation pass
-                        config::CrateType::Rlib => false,
-                        _ => {
-                            self.sess.err(&format!(
-                                "Only executables and rlibs can be \
-                                                    compiled with `-Z sanitizer`"
-                            ));
-                            false
-                        }
-                    }
-                }) {
-                    return;
-                }
-            }
-
-            let mut uses_std = false;
-            self.cstore.iter_crate_data(|_, data| {
-                if data.name() == sym::std {
-                    uses_std = true;
-                }
-            });
-
-            if uses_std {
-                let name = Symbol::intern(match sanitizer {
-                    Sanitizer::Address => "rustc_asan",
-                    Sanitizer::Leak => "rustc_lsan",
-                    Sanitizer::Memory => "rustc_msan",
-                    Sanitizer::Thread => "rustc_tsan",
-                });
-                info!("loading sanitizer: {}", name);
-
-                let cnum = self.resolve_crate(name, DUMMY_SP, DepKind::Explicit, None);
-                let data = self.cstore.get_crate_data(cnum);
-
-                // Sanity check the loaded crate to ensure it is indeed a sanitizer runtime
-                if !data.is_sanitizer_runtime() {
-                    self.sess.err(&format!("the crate `{}` is not a sanitizer runtime", name));
-                }
-            } else {
-                self.sess.err("Must link std to be compiled with `-Z sanitizer`");
-            }
-        }
-    }
-
     fn inject_profiler_runtime(&mut self) {
         if self.sess.opts.debugging_opts.profile || self.sess.opts.cg.profile_generate.enabled() {
             info!("loading profiler");
@@ -924,7 +822,6 @@ impl<'a> CrateLoader<'a> {
     }
 
     pub fn postprocess(&mut self, krate: &ast::Crate) {
-        self.inject_sanitizer_runtime();
         self.inject_profiler_runtime();
         self.inject_allocator_crate(krate);
         self.inject_panic_runtime(krate);
diff --git a/src/librustc_metadata/rmeta/decoder.rs b/src/librustc_metadata/rmeta/decoder.rs
index f5a05751f4c..eb3dcfa7227 100644
--- a/src/librustc_metadata/rmeta/decoder.rs
+++ b/src/librustc_metadata/rmeta/decoder.rs
@@ -1587,10 +1587,6 @@ impl<'a, 'tcx> CrateMetadata {
         self.root.panic_runtime
     }
 
-    crate fn is_sanitizer_runtime(&self) -> bool {
-        self.root.sanitizer_runtime
-    }
-
     crate fn is_profiler_runtime(&self) -> bool {
         self.root.profiler_runtime
     }
diff --git a/src/librustc_metadata/rmeta/decoder/cstore_impl.rs b/src/librustc_metadata/rmeta/decoder/cstore_impl.rs
index eb5754bf99b..c45e1994bc5 100644
--- a/src/librustc_metadata/rmeta/decoder/cstore_impl.rs
+++ b/src/librustc_metadata/rmeta/decoder/cstore_impl.rs
@@ -161,7 +161,6 @@ provide! { <'tcx> tcx, def_id, other, cdata,
     is_compiler_builtins => { cdata.root.compiler_builtins }
     has_global_allocator => { cdata.root.has_global_allocator }
     has_panic_handler => { cdata.root.has_panic_handler }
-    is_sanitizer_runtime => { cdata.root.sanitizer_runtime }
     is_profiler_runtime => { cdata.root.profiler_runtime }
     panic_strategy => { cdata.root.panic_strategy }
     extern_crate => {
diff --git a/src/librustc_metadata/rmeta/encoder.rs b/src/librustc_metadata/rmeta/encoder.rs
index 952d3bb8582..7f8791d0c34 100644
--- a/src/librustc_metadata/rmeta/encoder.rs
+++ b/src/librustc_metadata/rmeta/encoder.rs
@@ -514,7 +514,6 @@ impl<'tcx> EncodeContext<'tcx> {
             no_builtins: attr::contains_name(&attrs, sym::no_builtins),
             panic_runtime: attr::contains_name(&attrs, sym::panic_runtime),
             profiler_runtime: attr::contains_name(&attrs, sym::profiler_runtime),
-            sanitizer_runtime: attr::contains_name(&attrs, sym::sanitizer_runtime),
             symbol_mangling_version: tcx.sess.opts.debugging_opts.symbol_mangling_version,
 
             crate_deps,
diff --git a/src/librustc_metadata/rmeta/mod.rs b/src/librustc_metadata/rmeta/mod.rs
index 6309f1c260e..426ea62b8cd 100644
--- a/src/librustc_metadata/rmeta/mod.rs
+++ b/src/librustc_metadata/rmeta/mod.rs
@@ -209,7 +209,6 @@ crate struct CrateRoot<'tcx> {
     no_builtins: bool,
     panic_runtime: bool,
     profiler_runtime: bool,
-    sanitizer_runtime: bool,
     symbol_mangling_version: SymbolManglingVersion,
 }
 
diff --git a/src/librustc_msan/Cargo.toml b/src/librustc_msan/Cargo.toml
deleted file mode 100644
index bda40785725..00000000000
--- a/src/librustc_msan/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-authors = ["The Rust Project Developers"]
-build = "build.rs"
-name = "rustc_msan"
-version = "0.0.0"
-edition = "2018"
-
-[lib]
-name = "rustc_msan"
-path = "lib.rs"
-test = false
-
-[build-dependencies]
-build_helper = { path = "../build_helper" }
-cmake = "0.1.38"
-
-[dependencies]
-alloc = { path = "../liballoc" }
-core = { path = "../libcore" }
-compiler_builtins = "0.1.0"
diff --git a/src/librustc_msan/build.rs b/src/librustc_msan/build.rs
deleted file mode 100644
index dc08d51b51c..00000000000
--- a/src/librustc_msan/build.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use build_helper::sanitizer_lib_boilerplate;
-use std::env;
-
-use cmake::Config;
-
-fn main() {
-    println!("cargo:rerun-if-env-changed=RUSTC_BUILD_SANITIZERS");
-    if env::var("RUSTC_BUILD_SANITIZERS") != Ok("1".to_string()) {
-        return;
-    }
-    if let Some(llvm_config) = env::var_os("LLVM_CONFIG") {
-        build_helper::restore_library_path();
-
-        let (native, target) = match sanitizer_lib_boilerplate("msan") {
-            Ok(native) => native,
-            _ => return,
-        };
-
-        Config::new(&native.src_dir)
-            .define("COMPILER_RT_BUILD_SANITIZERS", "ON")
-            .define("COMPILER_RT_BUILD_BUILTINS", "OFF")
-            .define("COMPILER_RT_BUILD_XRAY", "OFF")
-            .define("LLVM_CONFIG_PATH", llvm_config)
-            .out_dir(&native.out_dir)
-            .build_target(&target)
-            .build();
-    }
-    println!("cargo:rerun-if-env-changed=LLVM_CONFIG");
-}
diff --git a/src/librustc_msan/lib.rs b/src/librustc_msan/lib.rs
deleted file mode 100644
index bdbc154f4e8..00000000000
--- a/src/librustc_msan/lib.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-#![sanitizer_runtime]
-#![feature(nll)]
-#![feature(sanitizer_runtime)]
-#![feature(staged_api)]
-#![no_std]
-#![unstable(
-    feature = "sanitizer_runtime_lib",
-    reason = "internal implementation detail of sanitizers",
-    issue = "none"
-)]
diff --git a/src/librustc_session/session.rs b/src/librustc_session/session.rs
index dba5b9f3f14..d979247b46d 100644
--- a/src/librustc_session/session.rs
+++ b/src/librustc_session/session.rs
@@ -1124,6 +1124,32 @@ fn validate_commandline_args_with_session_available(sess: &Session) {
                   See https://github.com/rust-lang/rust/issues/61002 for details.",
         );
     }
+
+    // Sanitizers can only be used on some tested platforms.
+    if let Some(ref sanitizer) = sess.opts.debugging_opts.sanitizer {
+        const ASAN_SUPPORTED_TARGETS: &[&str] =
+            &["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
+        const TSAN_SUPPORTED_TARGETS: &[&str] =
+            &["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
+        const LSAN_SUPPORTED_TARGETS: &[&str] =
+            &["x86_64-unknown-linux-gnu", "x86_64-apple-darwin"];
+        const MSAN_SUPPORTED_TARGETS: &[&str] = &["x86_64-unknown-linux-gnu"];
+
+        let supported_targets = match *sanitizer {
+            Sanitizer::Address => ASAN_SUPPORTED_TARGETS,
+            Sanitizer::Thread => TSAN_SUPPORTED_TARGETS,
+            Sanitizer::Leak => LSAN_SUPPORTED_TARGETS,
+            Sanitizer::Memory => MSAN_SUPPORTED_TARGETS,
+        };
+
+        if !supported_targets.contains(&&*sess.opts.target_triple.triple()) {
+            sess.err(&format!(
+                "{:?}Sanitizer only works with the `{}` target",
+                sanitizer,
+                supported_targets.join("` or `")
+            ));
+        }
+    }
 }
 
 /// Hash value constructed out of all the `-C metadata` arguments passed to the
diff --git a/src/librustc_tsan/Cargo.toml b/src/librustc_tsan/Cargo.toml
deleted file mode 100644
index 82045dd0cdd..00000000000
--- a/src/librustc_tsan/Cargo.toml
+++ /dev/null
@@ -1,20 +0,0 @@
-[package]
-authors = ["The Rust Project Developers"]
-build = "build.rs"
-name = "rustc_tsan"
-version = "0.0.0"
-edition = "2018"
-
-[lib]
-name = "rustc_tsan"
-path = "lib.rs"
-test = false
-
-[build-dependencies]
-build_helper = { path = "../build_helper" }
-cmake = "0.1.38"
-
-[dependencies]
-alloc = { path = "../liballoc" }
-core = { path = "../libcore" }
-compiler_builtins = "0.1.0"
diff --git a/src/librustc_tsan/build.rs b/src/librustc_tsan/build.rs
deleted file mode 100644
index 570642a2eaa..00000000000
--- a/src/librustc_tsan/build.rs
+++ /dev/null
@@ -1,30 +0,0 @@
-use build_helper::sanitizer_lib_boilerplate;
-use std::env;
-
-use cmake::Config;
-
-fn main() {
-    println!("cargo:rerun-if-env-changed=RUSTC_BUILD_SANITIZERS");
-    if env::var("RUSTC_BUILD_SANITIZERS") != Ok("1".to_string()) {
-        return;
-    }
-    if let Some(llvm_config) = env::var_os("LLVM_CONFIG") {
-        build_helper::restore_library_path();
-
-        let (native, target) = match sanitizer_lib_boilerplate("tsan") {
-            Ok(native) => native,
-            _ => return,
-        };
-
-        Config::new(&native.src_dir)
-            .define("COMPILER_RT_BUILD_SANITIZERS", "ON")
-            .define("COMPILER_RT_BUILD_BUILTINS", "OFF")
-            .define("COMPILER_RT_BUILD_XRAY", "OFF")
-            .define("LLVM_CONFIG_PATH", llvm_config)
-            .out_dir(&native.out_dir)
-            .build_target(&target)
-            .build();
-        native.fixup_sanitizer_lib_name("tsan");
-    }
-    println!("cargo:rerun-if-env-changed=LLVM_CONFIG");
-}
diff --git a/src/librustc_tsan/lib.rs b/src/librustc_tsan/lib.rs
deleted file mode 100644
index bdbc154f4e8..00000000000
--- a/src/librustc_tsan/lib.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-#![sanitizer_runtime]
-#![feature(nll)]
-#![feature(sanitizer_runtime)]
-#![feature(staged_api)]
-#![no_std]
-#![unstable(
-    feature = "sanitizer_runtime_lib",
-    reason = "internal implementation detail of sanitizers",
-    issue = "none"
-)]
diff --git a/src/libstd/Cargo.toml b/src/libstd/Cargo.toml
index a22e162bbff..c9ff93eac02 100644
--- a/src/libstd/Cargo.toml
+++ b/src/libstd/Cargo.toml
@@ -34,16 +34,6 @@ features = [ "rustc-dep-of-std" ] # enable build support for integrating into li
 [dev-dependencies]
 rand = "0.7"
 
-[target.x86_64-apple-darwin.dependencies]
-rustc_asan = { path = "../librustc_asan" }
-rustc_tsan = { path = "../librustc_tsan" }
-
-[target.x86_64-unknown-linux-gnu.dependencies]
-rustc_asan = { path = "../librustc_asan" }
-rustc_lsan = { path = "../librustc_lsan" }
-rustc_msan = { path = "../librustc_msan" }
-rustc_tsan = { path = "../librustc_tsan" }
-
 [target.'cfg(any(all(target_arch = "wasm32", not(target_os = "emscripten")), all(target_vendor = "fortanix", target_env = "sgx")))'.dependencies]
 dlmalloc = { version = "0.1", features = ['rustc-dep-of-std'] }
 
diff --git a/src/test/run-make-fulldeps/sanitizer-address/Makefile b/src/test/run-make-fulldeps/sanitizer-address/Makefile
index 3a377c32993..7f5e9049b2f 100644
--- a/src/test/run-make-fulldeps/sanitizer-address/Makefile
+++ b/src/test/run-make-fulldeps/sanitizer-address/Makefile
@@ -23,7 +23,7 @@ endif
 endif
 
 all:
-	$(RUSTC) -g -Z sanitizer=address -Z print-link-args $(EXTRA_RUSTFLAG) overflow.rs | $(CGREP) librustc_asan
+	$(RUSTC) -g -Z sanitizer=address -Z print-link-args $(EXTRA_RUSTFLAG) overflow.rs | $(CGREP) rustc_rt.asan
 	# Verify that stack buffer overflow is detected:
 	$(TMPDIR)/overflow 2>&1 | $(CGREP) stack-buffer-overflow
 	# Verify that variable name is included in address sanitizer report:
diff --git a/src/test/run-make-fulldeps/sanitizer-invalid-cratetype/Makefile b/src/test/run-make-fulldeps/sanitizer-invalid-cratetype/Makefile
deleted file mode 100644
index 9581ac565ea..00000000000
--- a/src/test/run-make-fulldeps/sanitizer-invalid-cratetype/Makefile
+++ /dev/null
@@ -1,16 +0,0 @@
-# needs-sanitizer-support
-
--include ../tools.mk
-
-# NOTE the address sanitizer only supports x86_64 linux and macOS
-
-ifeq ($(TARGET),x86_64-apple-darwin)
-EXTRA_RUSTFLAG=-C rpath
-else
-ifeq ($(TARGET),x86_64-unknown-linux-gnu)
-EXTRA_RUSTFLAG=
-endif
-endif
-
-all:
-	$(RUSTC) -Z sanitizer=address --crate-type proc-macro --target $(TARGET) hello.rs 2>&1 | $(CGREP) '-Z sanitizer'
diff --git a/src/test/run-make-fulldeps/sanitizer-invalid-cratetype/hello.rs b/src/test/run-make-fulldeps/sanitizer-invalid-cratetype/hello.rs
deleted file mode 100644
index e7a11a969c0..00000000000
--- a/src/test/run-make-fulldeps/sanitizer-invalid-cratetype/hello.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-fn main() {
-    println!("Hello, world!");
-}
diff --git a/src/test/run-make-fulldeps/sanitizer-invalid-target/Makefile b/src/test/run-make-fulldeps/sanitizer-invalid-target/Makefile
index df8afee15ce..2a23f0fe3d4 100644
--- a/src/test/run-make-fulldeps/sanitizer-invalid-target/Makefile
+++ b/src/test/run-make-fulldeps/sanitizer-invalid-target/Makefile
@@ -2,4 +2,4 @@
 
 all:
 	$(RUSTC) -Z sanitizer=leak --target i686-unknown-linux-gnu hello.rs 2>&1 | \
-		$(CGREP) 'LeakSanitizer only works with the `x86_64-unknown-linux-gnu` target'
+		$(CGREP) 'LeakSanitizer only works with the `x86_64-unknown-linux-gnu` or `x86_64-apple-darwin` target'
diff --git a/src/test/run-make-fulldeps/sanitizer-leak/Makefile b/src/test/run-make-fulldeps/sanitizer-leak/Makefile
index 101e8272ab9..d8598b8ac93 100644
--- a/src/test/run-make-fulldeps/sanitizer-leak/Makefile
+++ b/src/test/run-make-fulldeps/sanitizer-leak/Makefile
@@ -7,5 +7,5 @@
 # FIXME(#46126) ThinLTO for libstd broke this test
 
 all:
-	$(RUSTC) -C opt-level=1 -g -Z sanitizer=leak -Z print-link-args leak.rs | $(CGREP) librustc_lsan
+	$(RUSTC) -C opt-level=1 -g -Z sanitizer=leak -Z print-link-args leak.rs | $(CGREP) rustc_rt.lsan
 	$(TMPDIR)/leak 2>&1 | $(CGREP) 'detected memory leaks'
diff --git a/src/test/run-make-fulldeps/sanitizer-memory/Makefile b/src/test/run-make-fulldeps/sanitizer-memory/Makefile
index f5787903a2b..8bc9df1b4ba 100644
--- a/src/test/run-make-fulldeps/sanitizer-memory/Makefile
+++ b/src/test/run-make-fulldeps/sanitizer-memory/Makefile
@@ -5,7 +5,7 @@
 # only-x86_64
 
 all:
-	$(RUSTC) -g -Z sanitizer=memory -Z print-link-args uninit.rs | $(CGREP) librustc_msan
+	$(RUSTC) -g -Z sanitizer=memory -Z print-link-args uninit.rs | $(CGREP) rustc_rt.msan
 	$(TMPDIR)/uninit 2>&1 | $(CGREP) use-of-uninitialized-value
-	$(RUSTC) -g -Z sanitizer=memory -Z print-link-args maybeuninit.rs | $(CGREP) librustc_msan
+	$(RUSTC) -g -Z sanitizer=memory -Z print-link-args maybeuninit.rs | $(CGREP) rustc_rt.msan
 	$(TMPDIR)/maybeuninit 2>&1 | $(CGREP) use-of-uninitialized-value
diff --git a/src/test/ui/feature-gates/feature-gate-sanitizer-runtime.rs b/src/test/ui/feature-gates/feature-gate-sanitizer-runtime.rs
deleted file mode 100644
index 3b972c117a6..00000000000
--- a/src/test/ui/feature-gates/feature-gate-sanitizer-runtime.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-#![sanitizer_runtime] //~ ERROR the `#[sanitizer_runtime]` attribute is
-
-fn main() {}
diff --git a/src/test/ui/feature-gates/feature-gate-sanitizer-runtime.stderr b/src/test/ui/feature-gates/feature-gate-sanitizer-runtime.stderr
deleted file mode 100644
index b13ec215f8c..00000000000
--- a/src/test/ui/feature-gates/feature-gate-sanitizer-runtime.stderr
+++ /dev/null
@@ -1,11 +0,0 @@
-error[E0658]: the `#[sanitizer_runtime]` attribute is used to identify crates that contain the runtime of a sanitizer and will never be stable
-  --> $DIR/feature-gate-sanitizer-runtime.rs:1:1
-   |
-LL | #![sanitizer_runtime]
-   | ^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: add `#![feature(sanitizer_runtime)]` to the crate attributes to enable
-
-error: aborting due to previous error
-
-For more information about this error, try `rustc --explain E0658`.