diff --git a/.github/ISSUE_TEMPLATE/diagnostics.md b/.github/ISSUE_TEMPLATE/diagnostics.md
new file mode 100644
index 00000000000..044979f3bae
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/diagnostics.md
@@ -0,0 +1,46 @@
+---
+name: Diagnostic issue
+about: Create a bug report or feature request for a change to `rustc`'s error output
+labels: A-diagnostics, T-compiler
+---
+<!--
+Thank you for filing a bug report! 🐛 Please provide a short summary of the bug,
+along with any information you feel relevant to replicating the bug.
+
+If you cannot produce a minimal reproduction case (something that would work in
+isolation), please provide the steps or even link to a repository that causes
+the problematic output to occur. 
+-->
+
+Given the following code: <!-- Please provide a link to play.rust-lang.org -->
+
+```rust
+<code>
+```
+
+The current output is:
+
+```
+<rustc output>
+```
+
+<!-- The following is not always necessary. -->
+Ideally the output should look like:
+
+```
+<proposed output>
+```
+
+<!--
+If the problem is not self-explanatory, please provide a rationale for the
+change.
+-->
+
+<!--
+If dramatically different output is caused by small changes, consider also
+adding them here.
+
+If you're using the stable version of the compiler, you should also check if the
+bug also exists in the beta or nightly versions. The output might also be
+different depending on the Edition.
+-->
diff --git a/RELEASES.md b/RELEASES.md
index f5b71f295c6..25f9c802982 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -43,6 +43,23 @@ The following previously stable methods are now `const`.
 
 - [`IpAddr::is_ipv4`]
 - [`IpAddr::is_ipv6`]
+- [`IpAddr::is_unspecified`]
+- [`IpAddr::is_loopback`]
+- [`IpAddr::is_multicast`]
+- [`Ipv4Addr::octets`]
+- [`Ipv4Addr::is_loopback`]
+- [`Ipv4Addr::is_private`]
+- [`Ipv4Addr::is_link_local`]
+- [`Ipv4Addr::is_multicast`]
+- [`Ipv4Addr::is_broadcast`]
+- [`Ipv4Addr::is_documentation`]
+- [`Ipv4Addr::to_ipv6_compatible`]
+- [`Ipv4Addr::to_ipv6_mapped`]
+- [`Ipv6Addr::segments`]
+- [`Ipv6Addr::is_unspecified`]
+- [`Ipv6Addr::is_loopback`]
+- [`Ipv6Addr::is_multicast`]
+- [`Ipv6Addr::to_ipv4`]
 - [`Layout::size`]
 - [`Layout::align`]
 - [`Layout::from_size_align`]
@@ -104,6 +121,23 @@ Compatibility Notes
 [cargo/8725]: https://github.com/rust-lang/cargo/pull/8725
 [`IpAddr::is_ipv4`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_ipv4
 [`IpAddr::is_ipv6`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_ipv6
+[`IpAddr::is_unspecified`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_unspecified
+[`IpAddr::is_loopback`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_loopback
+[`IpAddr::is_multicast`]: https://doc.rust-lang.org/stable/std/net/enum.IpAddr.html#method.is_multicast
+[`Ipv4Addr::octets`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.octets
+[`Ipv4Addr::is_loopback`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_loopback
+[`Ipv4Addr::is_private`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_private
+[`Ipv4Addr::is_link_local`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_link_local
+[`Ipv4Addr::is_multicast`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_multicast
+[`Ipv4Addr::is_broadcast`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_broadcast
+[`Ipv4Addr::is_documentation`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.is_documentation
+[`Ipv4Addr::to_ipv6_compatible`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.to_ipv6_compatible
+[`Ipv4Addr::to_ipv6_mapped`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv4Addr.html#method.to_ipv6_mapped
+[`Ipv6Addr::segments`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.segments
+[`Ipv6Addr::is_unspecified`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.is_unspecified
+[`Ipv6Addr::is_loopback`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.is_loopback
+[`Ipv6Addr::is_multicast`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.is_multicast
+[`Ipv6Addr::to_ipv4`]: https://doc.rust-lang.org/stable/std/net/struct.Ipv6Addr.html#method.to_ipv4
 [`Layout::align`]: https://doc.rust-lang.org/stable/std/alloc/struct.Layout.html#method.align
 [`Layout::from_size_align`]: https://doc.rust-lang.org/stable/std/alloc/struct.Layout.html#method.from_size_align
 [`Layout::size`]: https://doc.rust-lang.org/stable/std/alloc/struct.Layout.html#method.size
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 972b9bbfe1c..b93a579df15 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -2193,6 +2193,7 @@ fn add_apple_sdk(cmd: &mut dyn Linker, sess: &Session, flavor: LinkerFlavor) {
         ("x86_64", "tvos") => "appletvsimulator",
         ("arm", "ios") => "iphoneos",
         ("aarch64", "ios") if llvm_target.contains("macabi") => "macosx",
+        ("aarch64", "ios") if llvm_target.contains("sim") => "iphonesimulator",
         ("aarch64", "ios") => "iphoneos",
         ("x86", "ios") => "iphonesimulator",
         ("x86_64", "ios") if llvm_target.contains("macabi") => "macosx",
diff --git a/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs b/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs
new file mode 100644
index 00000000000..e594ceec1b7
--- /dev/null
+++ b/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs
@@ -0,0 +1,39 @@
+use super::apple_sdk_base::{opts, Arch};
+use crate::spec::{Target, TargetOptions};
+
+pub fn target() -> Target {
+    let base = opts("ios", Arch::Arm64_sim);
+
+    // Clang automatically chooses a more specific target based on
+    // IPHONEOS_DEPLOYMENT_TARGET.
+    // This is required for the simulator target to pick the right
+    // MACH-O commands, so we do too.
+    let arch = "arm64";
+    let llvm_target = super::apple_base::ios_sim_llvm_target(arch);
+
+    Target {
+        llvm_target: llvm_target,
+        pointer_width: 64,
+        data_layout: "e-m:o-i64:64-i128:128-n32:64-S128".to_string(),
+        arch: "aarch64".to_string(),
+        options: TargetOptions {
+            features: "+neon,+fp-armv8,+apple-a7".to_string(),
+            eliminate_frame_pointer: false,
+            max_atomic_width: Some(128),
+            unsupported_abis: super::arm_base::unsupported_abis(),
+            forces_embed_bitcode: true,
+            // Taken from a clang build on Xcode 11.4.1.
+            // These arguments are not actually invoked - they just have
+            // to look right to pass App Store validation.
+            bitcode_llvm_cmdline: "-triple\0\
+                arm64-apple-ios14.0-simulator\0\
+                -emit-obj\0\
+                -disable-llvm-passes\0\
+                -target-abi\0\
+                darwinpcs\0\
+                -Os\0"
+                .to_string(),
+            ..base
+        },
+    }
+}
diff --git a/compiler/rustc_target/src/spec/apple_base.rs b/compiler/rustc_target/src/spec/apple_base.rs
index 3b458962b3d..23f1357af16 100644
--- a/compiler/rustc_target/src/spec/apple_base.rs
+++ b/compiler/rustc_target/src/spec/apple_base.rs
@@ -54,14 +54,16 @@ pub fn opts(os: &str) -> TargetOptions {
     }
 }
 
-fn macos_deployment_target() -> (u32, u32) {
-    let deployment_target = env::var("MACOSX_DEPLOYMENT_TARGET").ok();
-    let version = deployment_target
+fn deployment_target(var_name: &str) -> Option<(u32, u32)> {
+    let deployment_target = env::var(var_name).ok();
+    deployment_target
         .as_ref()
         .and_then(|s| s.split_once('.'))
-        .and_then(|(a, b)| a.parse::<u32>().and_then(|a| b.parse::<u32>().map(|b| (a, b))).ok());
+        .and_then(|(a, b)| a.parse::<u32>().and_then(|a| b.parse::<u32>().map(|b| (a, b))).ok())
+}
 
-    version.unwrap_or((10, 7))
+fn macos_deployment_target() -> (u32, u32) {
+    deployment_target("MACOSX_DEPLOYMENT_TARGET").unwrap_or((10, 7))
 }
 
 pub fn macos_llvm_target(arch: &str) -> String {
@@ -84,3 +86,12 @@ pub fn macos_link_env_remove() -> Vec<String> {
     env_remove.push("IPHONEOS_DEPLOYMENT_TARGET".to_string());
     env_remove
 }
+
+fn ios_deployment_target() -> (u32, u32) {
+    deployment_target("IPHONEOS_DEPLOYMENT_TARGET").unwrap_or((7, 0))
+}
+
+pub fn ios_sim_llvm_target(arch: &str) -> String {
+    let (major, minor) = ios_deployment_target();
+    format!("{}-apple-ios{}.{}.0-simulator", arch, major, minor)
+}
diff --git a/compiler/rustc_target/src/spec/apple_sdk_base.rs b/compiler/rustc_target/src/spec/apple_sdk_base.rs
index d894f759937..538c4ca8697 100644
--- a/compiler/rustc_target/src/spec/apple_sdk_base.rs
+++ b/compiler/rustc_target/src/spec/apple_sdk_base.rs
@@ -11,6 +11,7 @@ pub enum Arch {
     X86_64,
     X86_64_macabi,
     Arm64_macabi,
+    Arm64_sim,
 }
 
 fn target_cpu(arch: Arch) -> String {
@@ -22,13 +23,16 @@ fn target_cpu(arch: Arch) -> String {
         X86_64 => "core2",
         X86_64_macabi => "core2",
         Arm64_macabi => "apple-a12",
+        Arm64_sim => "apple-a12",
     }
     .to_string()
 }
 
 fn link_env_remove(arch: Arch) -> Vec<String> {
     match arch {
-        Armv7 | Armv7s | Arm64 | I386 | X86_64 => vec!["MACOSX_DEPLOYMENT_TARGET".to_string()],
+        Armv7 | Armv7s | Arm64 | I386 | X86_64 | Arm64_sim => {
+            vec!["MACOSX_DEPLOYMENT_TARGET".to_string()]
+        }
         X86_64_macabi | Arm64_macabi => vec!["IPHONEOS_DEPLOYMENT_TARGET".to_string()],
     }
 }
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 5b14795f545..ae803dbdb31 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -727,6 +727,7 @@ supported_targets! {
     ("armv7s-apple-ios", armv7s_apple_ios),
     ("x86_64-apple-ios-macabi", x86_64_apple_ios_macabi),
     ("aarch64-apple-ios-macabi", aarch64_apple_ios_macabi),
+    ("aarch64-apple-ios-sim", aarch64_apple_ios_sim),
     ("aarch64-apple-tvos", aarch64_apple_tvos),
     ("x86_64-apple-tvos", x86_64_apple_tvos),
 
diff --git a/library/alloc/src/collections/binary_heap.rs b/library/alloc/src/collections/binary_heap.rs
index 8a36b2af765..33bd98d467c 100644
--- a/library/alloc/src/collections/binary_heap.rs
+++ b/library/alloc/src/collections/binary_heap.rs
@@ -275,7 +275,8 @@ impl<T: Ord + fmt::Debug> fmt::Debug for PeekMut<'_, T> {
 impl<T: Ord> Drop for PeekMut<'_, T> {
     fn drop(&mut self) {
         if self.sift {
-            self.heap.sift_down(0);
+            // SAFETY: PeekMut is only instantiated for non-empty heaps.
+            unsafe { self.heap.sift_down(0) };
         }
     }
 }
@@ -431,7 +432,8 @@ impl<T: Ord> BinaryHeap<T> {
         self.data.pop().map(|mut item| {
             if !self.is_empty() {
                 swap(&mut item, &mut self.data[0]);
-                self.sift_down_to_bottom(0);
+                // SAFETY: !self.is_empty() means that self.len() > 0
+                unsafe { self.sift_down_to_bottom(0) };
             }
             item
         })
@@ -473,7 +475,9 @@ impl<T: Ord> BinaryHeap<T> {
     pub fn push(&mut self, item: T) {
         let old_len = self.len();
         self.data.push(item);
-        self.sift_up(0, old_len);
+        // SAFETY: Since we pushed a new item it means that
+        //  old_len = self.len() - 1 < self.len()
+        unsafe { self.sift_up(0, old_len) };
     }
 
     /// Consumes the `BinaryHeap` and returns a vector in sorted
@@ -506,7 +510,10 @@ impl<T: Ord> BinaryHeap<T> {
                 let ptr = self.data.as_mut_ptr();
                 ptr::swap(ptr, ptr.add(end));
             }
-            self.sift_down_range(0, end);
+            // SAFETY: `end` goes from `self.len() - 1` to 1 (both included) so:
+            //  0 < 1 <= end <= self.len() - 1 < self.len()
+            //  Which means 0 < end and end < self.len().
+            unsafe { self.sift_down_range(0, end) };
         }
         self.into_vec()
     }
@@ -519,47 +526,84 @@ impl<T: Ord> BinaryHeap<T> {
     // the hole is filled back at the end of its scope, even on panic.
     // Using a hole reduces the constant factor compared to using swaps,
     // which involves twice as many moves.
-    fn sift_up(&mut self, start: usize, pos: usize) -> usize {
-        unsafe {
-            // Take out the value at `pos` and create a hole.
-            let mut hole = Hole::new(&mut self.data, pos);
 
-            while hole.pos() > start {
-                let parent = (hole.pos() - 1) / 2;
-                if hole.element() <= hole.get(parent) {
-                    break;
-                }
-                hole.move_to(parent);
+    /// # Safety
+    ///
+    /// The caller must guarantee that `pos < self.len()`.
+    unsafe fn sift_up(&mut self, start: usize, pos: usize) -> usize {
+        // Take out the value at `pos` and create a hole.
+        // SAFETY: The caller guarantees that pos < self.len()
+        let mut hole = unsafe { Hole::new(&mut self.data, pos) };
+
+        while hole.pos() > start {
+            let parent = (hole.pos() - 1) / 2;
+
+            // SAFETY: hole.pos() > start >= 0, which means hole.pos() > 0
+            //  and so hole.pos() - 1 can't underflow.
+            //  This guarantees that parent < hole.pos() so
+            //  it's a valid index and also != hole.pos().
+            if hole.element() <= unsafe { hole.get(parent) } {
+                break;
             }
-            hole.pos()
+
+            // SAFETY: Same as above
+            unsafe { hole.move_to(parent) };
         }
+
+        hole.pos()
     }
 
     /// Take an element at `pos` and move it down the heap,
     /// while its children are larger.
-    fn sift_down_range(&mut self, pos: usize, end: usize) {
-        unsafe {
-            let mut hole = Hole::new(&mut self.data, pos);
-            let mut child = 2 * pos + 1;
-            while child < end - 1 {
-                // compare with the greater of the two children
-                child += (hole.get(child) <= hole.get(child + 1)) as usize;
-                // if we are already in order, stop.
-                if hole.element() >= hole.get(child) {
-                    return;
-                }
-                hole.move_to(child);
-                child = 2 * hole.pos() + 1;
-            }
-            if child == end - 1 && hole.element() < hole.get(child) {
-                hole.move_to(child);
+    ///
+    /// # Safety
+    ///
+    /// The caller must guarantee that `pos < end <= self.len()`.
+    unsafe fn sift_down_range(&mut self, pos: usize, end: usize) {
+        // SAFETY: The caller guarantees that pos < end <= self.len().
+        let mut hole = unsafe { Hole::new(&mut self.data, pos) };
+        let mut child = 2 * hole.pos() + 1;
+
+        // Loop invariant: child == 2 * hole.pos() + 1.
+        while child < end - 1 {
+            // compare with the greater of the two children
+            // SAFETY: child < end - 1 < self.len() and
+            //  child + 1 < end <= self.len(), so they're valid indexes.
+            //  child == 2 * hole.pos() + 1 != hole.pos() and
+            //  child + 1 == 2 * hole.pos() + 2 != hole.pos().
+            // FIXME: 2 * hole.pos() + 1 or 2 * hole.pos() + 2 could overflow
+            //  if T is a ZST
+            child += unsafe { hole.get(child) <= hole.get(child + 1) } as usize;
+
+            // if we are already in order, stop.
+            // SAFETY: child is now either the old child or the old child+1
+            //  We already proven that both are < self.len() and != hole.pos()
+            if hole.element() >= unsafe { hole.get(child) } {
+                return;
             }
+
+            // SAFETY: same as above.
+            unsafe { hole.move_to(child) };
+            child = 2 * hole.pos() + 1;
+        }
+
+        // SAFETY: && short circuit, which means that in the
+        //  second condition it's already true that child == end - 1 < self.len().
+        if child == end - 1 && hole.element() < unsafe { hole.get(child) } {
+            // SAFETY: child is already proven to be a valid index and
+            //  child == 2 * hole.pos() + 1 != hole.pos().
+            unsafe { hole.move_to(child) };
         }
     }
 
-    fn sift_down(&mut self, pos: usize) {
+    /// # Safety
+    ///
+    /// The caller must guarantee that `pos < self.len()`.
+    unsafe fn sift_down(&mut self, pos: usize) {
         let len = self.len();
-        self.sift_down_range(pos, len);
+        // SAFETY: pos < len is guaranteed by the caller and
+        //  obviously len = self.len() <= self.len().
+        unsafe { self.sift_down_range(pos, len) };
     }
 
     /// Take an element at `pos` and move it all the way down the heap,
@@ -567,30 +611,54 @@ impl<T: Ord> BinaryHeap<T> {
     ///
     /// Note: This is faster when the element is known to be large / should
     /// be closer to the bottom.
-    fn sift_down_to_bottom(&mut self, mut pos: usize) {
+    ///
+    /// # Safety
+    ///
+    /// The caller must guarantee that `pos < self.len()`.
+    unsafe fn sift_down_to_bottom(&mut self, mut pos: usize) {
         let end = self.len();
         let start = pos;
-        unsafe {
-            let mut hole = Hole::new(&mut self.data, pos);
-            let mut child = 2 * pos + 1;
-            while child < end - 1 {
-                child += (hole.get(child) <= hole.get(child + 1)) as usize;
-                hole.move_to(child);
-                child = 2 * hole.pos() + 1;
-            }
-            if child == end - 1 {
-                hole.move_to(child);
-            }
-            pos = hole.pos;
+
+        // SAFETY: The caller guarantees that pos < self.len().
+        let mut hole = unsafe { Hole::new(&mut self.data, pos) };
+        let mut child = 2 * hole.pos() + 1;
+
+        // Loop invariant: child == 2 * hole.pos() + 1.
+        while child < end - 1 {
+            // SAFETY: child < end - 1 < self.len() and
+            //  child + 1 < end <= self.len(), so they're valid indexes.
+            //  child == 2 * hole.pos() + 1 != hole.pos() and
+            //  child + 1 == 2 * hole.pos() + 2 != hole.pos().
+            // FIXME: 2 * hole.pos() + 1 or 2 * hole.pos() + 2 could overflow
+            //  if T is a ZST
+            child += unsafe { hole.get(child) <= hole.get(child + 1) } as usize;
+
+            // SAFETY: Same as above
+            unsafe { hole.move_to(child) };
+            child = 2 * hole.pos() + 1;
         }
-        self.sift_up(start, pos);
+
+        if child == end - 1 {
+            // SAFETY: child == end - 1 < self.len(), so it's a valid index
+            //  and child == 2 * hole.pos() + 1 != hole.pos().
+            unsafe { hole.move_to(child) };
+        }
+        pos = hole.pos();
+        drop(hole);
+
+        // SAFETY: pos is the position in the hole and was already proven
+        //  to be a valid index.
+        unsafe { self.sift_up(start, pos) };
     }
 
     fn rebuild(&mut self) {
         let mut n = self.len() / 2;
         while n > 0 {
             n -= 1;
-            self.sift_down(n);
+            // SAFETY: n starts from self.len() / 2 and goes down to 0.
+            //  The only case when !(n < self.len()) is if
+            //  self.len() == 0, but it's ruled out by the loop condition.
+            unsafe { self.sift_down(n) };
         }
     }
 
diff --git a/library/alloc/src/collections/btree/map/tests.rs b/library/alloc/src/collections/btree/map/tests.rs
index 1993c6e047d..56d6ae57e04 100644
--- a/library/alloc/src/collections/btree/map/tests.rs
+++ b/library/alloc/src/collections/btree/map/tests.rs
@@ -1,4 +1,6 @@
-use super::super::{node, DeterministicRng};
+use super::super::testing::crash_test::{CrashTestDummy, Panic};
+use super::super::testing::ord_chaos::{Cyclic3, Governed, Governor};
+use super::super::testing::rng::DeterministicRng;
 use super::Entry::{Occupied, Vacant};
 use super::*;
 use crate::boxed::Box;
@@ -15,9 +17,6 @@ use std::ops::RangeBounds;
 use std::panic::{catch_unwind, AssertUnwindSafe};
 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
 
-mod ord_chaos;
-use ord_chaos::{Cyclic3, Governed, Governor};
-
 // Capacity of a tree with a single level,
 // i.e., a tree who's root is a leaf node at height 0.
 const NODE_CAPACITY: usize = node::CAPACITY;
@@ -1136,91 +1135,62 @@ mod test_drain_filter {
 
     #[test]
     fn drop_panic_leak() {
-        static PREDS: AtomicUsize = AtomicUsize::new(0);
-        static DROPS: AtomicUsize = AtomicUsize::new(0);
+        let a = CrashTestDummy::new(0);
+        let b = CrashTestDummy::new(1);
+        let c = CrashTestDummy::new(2);
+        let mut map = BTreeMap::new();
+        map.insert(a.spawn(Panic::Never), ());
+        map.insert(b.spawn(Panic::InDrop), ());
+        map.insert(c.spawn(Panic::Never), ());
 
-        struct D;
-        impl Drop for D {
-            fn drop(&mut self) {
-                if DROPS.fetch_add(1, SeqCst) == 1 {
-                    panic!("panic in `drop`");
-                }
-            }
-        }
+        catch_unwind(move || drop(map.drain_filter(|dummy, _| dummy.query(true)))).unwrap_err();
 
-        // Keys are multiples of 4, so that each key is counted by a hexadecimal digit.
-        let mut map = (0..3).map(|i| (i * 4, D)).collect::<BTreeMap<_, _>>();
-
-        catch_unwind(move || {
-            drop(map.drain_filter(|i, _| {
-                PREDS.fetch_add(1usize << i, SeqCst);
-                true
-            }))
-        })
-        .unwrap_err();
-
-        assert_eq!(PREDS.load(SeqCst), 0x011);
-        assert_eq!(DROPS.load(SeqCst), 3);
+        assert_eq!(a.queried(), 1);
+        assert_eq!(b.queried(), 1);
+        assert_eq!(c.queried(), 0);
+        assert_eq!(a.dropped(), 1);
+        assert_eq!(b.dropped(), 1);
+        assert_eq!(c.dropped(), 1);
     }
 
     #[test]
     fn pred_panic_leak() {
-        static PREDS: AtomicUsize = AtomicUsize::new(0);
-        static DROPS: AtomicUsize = AtomicUsize::new(0);
+        let a = CrashTestDummy::new(0);
+        let b = CrashTestDummy::new(1);
+        let c = CrashTestDummy::new(2);
+        let mut map = BTreeMap::new();
+        map.insert(a.spawn(Panic::Never), ());
+        map.insert(b.spawn(Panic::InQuery), ());
+        map.insert(c.spawn(Panic::InQuery), ());
 
-        struct D;
-        impl Drop for D {
-            fn drop(&mut self) {
-                DROPS.fetch_add(1, SeqCst);
-            }
-        }
+        catch_unwind(AssertUnwindSafe(|| drop(map.drain_filter(|dummy, _| dummy.query(true)))))
+            .unwrap_err();
 
-        // Keys are multiples of 4, so that each key is counted by a hexadecimal digit.
-        let mut map = (0..3).map(|i| (i * 4, D)).collect::<BTreeMap<_, _>>();
-
-        catch_unwind(AssertUnwindSafe(|| {
-            drop(map.drain_filter(|i, _| {
-                PREDS.fetch_add(1usize << i, SeqCst);
-                match i {
-                    0 => true,
-                    _ => panic!(),
-                }
-            }))
-        }))
-        .unwrap_err();
-
-        assert_eq!(PREDS.load(SeqCst), 0x011);
-        assert_eq!(DROPS.load(SeqCst), 1);
+        assert_eq!(a.queried(), 1);
+        assert_eq!(b.queried(), 1);
+        assert_eq!(c.queried(), 0);
+        assert_eq!(a.dropped(), 1);
+        assert_eq!(b.dropped(), 0);
+        assert_eq!(c.dropped(), 0);
         assert_eq!(map.len(), 2);
-        assert_eq!(map.first_entry().unwrap().key(), &4);
-        assert_eq!(map.last_entry().unwrap().key(), &8);
+        assert_eq!(map.first_entry().unwrap().key().id(), 1);
+        assert_eq!(map.last_entry().unwrap().key().id(), 2);
         map.check();
     }
 
     // Same as above, but attempt to use the iterator again after the panic in the predicate
     #[test]
     fn pred_panic_reuse() {
-        static PREDS: AtomicUsize = AtomicUsize::new(0);
-        static DROPS: AtomicUsize = AtomicUsize::new(0);
-
-        struct D;
-        impl Drop for D {
-            fn drop(&mut self) {
-                DROPS.fetch_add(1, SeqCst);
-            }
-        }
-
-        // Keys are multiples of 4, so that each key is counted by a hexadecimal digit.
-        let mut map = (0..3).map(|i| (i * 4, D)).collect::<BTreeMap<_, _>>();
+        let a = CrashTestDummy::new(0);
+        let b = CrashTestDummy::new(1);
+        let c = CrashTestDummy::new(2);
+        let mut map = BTreeMap::new();
+        map.insert(a.spawn(Panic::Never), ());
+        map.insert(b.spawn(Panic::InQuery), ());
+        map.insert(c.spawn(Panic::InQuery), ());
 
         {
-            let mut it = map.drain_filter(|i, _| {
-                PREDS.fetch_add(1usize << i, SeqCst);
-                match i {
-                    0 => true,
-                    _ => panic!(),
-                }
-            });
+            let mut it = map.drain_filter(|dummy, _| dummy.query(true));
             catch_unwind(AssertUnwindSafe(|| while it.next().is_some() {})).unwrap_err();
             // Iterator behaviour after a panic is explicitly unspecified,
             // so this is just the current implementation:
@@ -1228,11 +1198,15 @@ mod test_drain_filter {
             assert!(matches!(result, Ok(None)));
         }
 
-        assert_eq!(PREDS.load(SeqCst), 0x011);
-        assert_eq!(DROPS.load(SeqCst), 1);
+        assert_eq!(a.queried(), 1);
+        assert_eq!(b.queried(), 1);
+        assert_eq!(c.queried(), 0);
+        assert_eq!(a.dropped(), 1);
+        assert_eq!(b.dropped(), 0);
+        assert_eq!(c.dropped(), 0);
         assert_eq!(map.len(), 2);
-        assert_eq!(map.first_entry().unwrap().key(), &4);
-        assert_eq!(map.last_entry().unwrap().key(), &8);
+        assert_eq!(map.first_entry().unwrap().key().id(), 1);
+        assert_eq!(map.last_entry().unwrap().key().id(), 2);
         map.check();
     }
 }
@@ -1439,6 +1413,43 @@ fn test_bad_zst() {
     m.check();
 }
 
+#[test]
+fn test_clear() {
+    let mut map = BTreeMap::new();
+    for &len in &[MIN_INSERTS_HEIGHT_1, MIN_INSERTS_HEIGHT_2, 0, NODE_CAPACITY] {
+        for i in 0..len {
+            map.insert(i, ());
+        }
+        assert_eq!(map.len(), len);
+        map.clear();
+        map.check();
+        assert!(map.is_empty());
+    }
+}
+
+#[test]
+fn test_clear_drop_panic_leak() {
+    let a = CrashTestDummy::new(0);
+    let b = CrashTestDummy::new(1);
+    let c = CrashTestDummy::new(2);
+
+    let mut map = BTreeMap::new();
+    map.insert(a.spawn(Panic::Never), ());
+    map.insert(b.spawn(Panic::InDrop), ());
+    map.insert(c.spawn(Panic::Never), ());
+
+    catch_unwind(AssertUnwindSafe(|| map.clear())).unwrap_err();
+    assert_eq!(a.dropped(), 1);
+    assert_eq!(b.dropped(), 1);
+    assert_eq!(c.dropped(), 1);
+    assert_eq!(map.len(), 0);
+
+    drop(map);
+    assert_eq!(a.dropped(), 1);
+    assert_eq!(b.dropped(), 1);
+    assert_eq!(c.dropped(), 1);
+}
+
 #[test]
 fn test_clone() {
     let mut map = BTreeMap::new();
@@ -1484,6 +1495,35 @@ fn test_clone() {
     map.check();
 }
 
+#[test]
+fn test_clone_panic_leak() {
+    let a = CrashTestDummy::new(0);
+    let b = CrashTestDummy::new(1);
+    let c = CrashTestDummy::new(2);
+
+    let mut map = BTreeMap::new();
+    map.insert(a.spawn(Panic::Never), ());
+    map.insert(b.spawn(Panic::InClone), ());
+    map.insert(c.spawn(Panic::Never), ());
+
+    catch_unwind(|| map.clone()).unwrap_err();
+    assert_eq!(a.cloned(), 1);
+    assert_eq!(b.cloned(), 1);
+    assert_eq!(c.cloned(), 0);
+    assert_eq!(a.dropped(), 1);
+    assert_eq!(b.dropped(), 0);
+    assert_eq!(c.dropped(), 0);
+    assert_eq!(map.len(), 3);
+
+    drop(map);
+    assert_eq!(a.cloned(), 1);
+    assert_eq!(b.cloned(), 1);
+    assert_eq!(c.cloned(), 0);
+    assert_eq!(a.dropped(), 2);
+    assert_eq!(b.dropped(), 1);
+    assert_eq!(c.dropped(), 1);
+}
+
 #[test]
 fn test_clone_from() {
     let mut map1 = BTreeMap::new();
@@ -1901,29 +1941,21 @@ create_append_test!(test_append_1700, 1700);
 
 #[test]
 fn test_append_drop_leak() {
-    static DROPS: AtomicUsize = AtomicUsize::new(0);
-
-    struct D;
-
-    impl Drop for D {
-        fn drop(&mut self) {
-            if DROPS.fetch_add(1, SeqCst) == 0 {
-                panic!("panic in `drop`");
-            }
-        }
-    }
-
+    let a = CrashTestDummy::new(0);
+    let b = CrashTestDummy::new(1);
+    let c = CrashTestDummy::new(2);
     let mut left = BTreeMap::new();
     let mut right = BTreeMap::new();
-    left.insert(0, D);
-    left.insert(1, D); // first to be dropped during append
-    left.insert(2, D);
-    right.insert(1, D);
-    right.insert(2, D);
+    left.insert(a.spawn(Panic::Never), ());
+    left.insert(b.spawn(Panic::InDrop), ()); // first duplicate key, dropped during append
+    left.insert(c.spawn(Panic::Never), ());
+    right.insert(b.spawn(Panic::Never), ());
+    right.insert(c.spawn(Panic::Never), ());
 
     catch_unwind(move || left.append(&mut right)).unwrap_err();
-
-    assert_eq!(DROPS.load(SeqCst), 4); // Rust issue #47949 ate one little piggy
+    assert_eq!(a.dropped(), 1);
+    assert_eq!(b.dropped(), 1); // should be 2 were it not for Rust issue #47949
+    assert_eq!(c.dropped(), 2);
 }
 
 #[test]
@@ -2050,51 +2082,42 @@ fn test_split_off_large_random_sorted() {
 
 #[test]
 fn test_into_iter_drop_leak_height_0() {
-    static DROPS: AtomicUsize = AtomicUsize::new(0);
-
-    struct D;
-
-    impl Drop for D {
-        fn drop(&mut self) {
-            if DROPS.fetch_add(1, SeqCst) == 3 {
-                panic!("panic in `drop`");
-            }
-        }
-    }
-
+    let a = CrashTestDummy::new(0);
+    let b = CrashTestDummy::new(1);
+    let c = CrashTestDummy::new(2);
+    let d = CrashTestDummy::new(3);
+    let e = CrashTestDummy::new(4);
     let mut map = BTreeMap::new();
-    map.insert("a", D);
-    map.insert("b", D);
-    map.insert("c", D);
-    map.insert("d", D);
-    map.insert("e", D);
+    map.insert("a", a.spawn(Panic::Never));
+    map.insert("b", b.spawn(Panic::Never));
+    map.insert("c", c.spawn(Panic::Never));
+    map.insert("d", d.spawn(Panic::InDrop));
+    map.insert("e", e.spawn(Panic::Never));
 
     catch_unwind(move || drop(map.into_iter())).unwrap_err();
 
-    assert_eq!(DROPS.load(SeqCst), 5);
+    assert_eq!(a.dropped(), 1);
+    assert_eq!(b.dropped(), 1);
+    assert_eq!(c.dropped(), 1);
+    assert_eq!(d.dropped(), 1);
+    assert_eq!(e.dropped(), 1);
 }
 
 #[test]
 fn test_into_iter_drop_leak_height_1() {
     let size = MIN_INSERTS_HEIGHT_1;
-    static DROPS: AtomicUsize = AtomicUsize::new(0);
-    static PANIC_POINT: AtomicUsize = AtomicUsize::new(0);
-
-    struct D;
-    impl Drop for D {
-        fn drop(&mut self) {
-            if DROPS.fetch_add(1, SeqCst) == PANIC_POINT.load(SeqCst) {
-                panic!("panic in `drop`");
-            }
-        }
-    }
-
     for panic_point in vec![0, 1, size - 2, size - 1] {
-        DROPS.store(0, SeqCst);
-        PANIC_POINT.store(panic_point, SeqCst);
-        let map: BTreeMap<_, _> = (0..size).map(|i| (i, D)).collect();
+        let dummies: Vec<_> = (0..size).map(|i| CrashTestDummy::new(i)).collect();
+        let map: BTreeMap<_, _> = (0..size)
+            .map(|i| {
+                let panic = if i == panic_point { Panic::InDrop } else { Panic::Never };
+                (dummies[i].spawn(Panic::Never), dummies[i].spawn(panic))
+            })
+            .collect();
         catch_unwind(move || drop(map.into_iter())).unwrap_err();
-        assert_eq!(DROPS.load(SeqCst), size);
+        for i in 0..size {
+            assert_eq!(dummies[i].dropped(), 2);
+        }
     }
 }
 
diff --git a/library/alloc/src/collections/btree/mod.rs b/library/alloc/src/collections/btree/mod.rs
index cf91c17b511..421f842dab0 100644
--- a/library/alloc/src/collections/btree/mod.rs
+++ b/library/alloc/src/collections/btree/mod.rs
@@ -20,32 +20,4 @@ trait Recover<Q: ?Sized> {
 }
 
 #[cfg(test)]
-/// XorShiftRng
-struct DeterministicRng {
-    count: usize,
-    x: u32,
-    y: u32,
-    z: u32,
-    w: u32,
-}
-
-#[cfg(test)]
-impl DeterministicRng {
-    fn new() -> Self {
-        DeterministicRng { count: 0, x: 0x193a6754, y: 0xa8a7d469, z: 0x97830e05, w: 0x113ba7bb }
-    }
-
-    /// Guarantees that each returned number is unique.
-    fn next(&mut self) -> u32 {
-        self.count += 1;
-        assert!(self.count <= 70029);
-        let x = self.x;
-        let t = x ^ (x << 11);
-        self.x = self.y;
-        self.y = self.z;
-        self.z = self.w;
-        let w_ = self.w;
-        self.w = w_ ^ (w_ >> 19) ^ (t ^ (t >> 8));
-        self.w
-    }
-}
+mod testing;
diff --git a/library/alloc/src/collections/btree/set/tests.rs b/library/alloc/src/collections/btree/set/tests.rs
index 3762af7236a..4cb6e3d6619 100644
--- a/library/alloc/src/collections/btree/set/tests.rs
+++ b/library/alloc/src/collections/btree/set/tests.rs
@@ -1,10 +1,10 @@
-use super::super::DeterministicRng;
+use super::super::testing::crash_test::{CrashTestDummy, Panic};
+use super::super::testing::rng::DeterministicRng;
 use super::*;
 use crate::vec::Vec;
 use std::cmp::Ordering;
 use std::iter::FromIterator;
 use std::panic::{catch_unwind, AssertUnwindSafe};
-use std::sync::atomic::{AtomicU32, Ordering::SeqCst};
 
 #[test]
 fn test_clone_eq() {
@@ -349,70 +349,45 @@ fn test_drain_filter() {
 
 #[test]
 fn test_drain_filter_drop_panic_leak() {
-    static PREDS: AtomicU32 = AtomicU32::new(0);
-    static DROPS: AtomicU32 = AtomicU32::new(0);
-
-    #[derive(PartialEq, Eq, PartialOrd, Ord)]
-    struct D(i32);
-    impl Drop for D {
-        fn drop(&mut self) {
-            if DROPS.fetch_add(1, SeqCst) == 1 {
-                panic!("panic in `drop`");
-            }
-        }
-    }
-
+    let a = CrashTestDummy::new(0);
+    let b = CrashTestDummy::new(1);
+    let c = CrashTestDummy::new(2);
     let mut set = BTreeSet::new();
-    set.insert(D(0));
-    set.insert(D(4));
-    set.insert(D(8));
+    set.insert(a.spawn(Panic::Never));
+    set.insert(b.spawn(Panic::InDrop));
+    set.insert(c.spawn(Panic::Never));
 
-    catch_unwind(move || {
-        drop(set.drain_filter(|d| {
-            PREDS.fetch_add(1u32 << d.0, SeqCst);
-            true
-        }))
-    })
-    .ok();
+    catch_unwind(move || drop(set.drain_filter(|dummy| dummy.query(true)))).ok();
 
-    assert_eq!(PREDS.load(SeqCst), 0x011);
-    assert_eq!(DROPS.load(SeqCst), 3);
+    assert_eq!(a.queried(), 1);
+    assert_eq!(b.queried(), 1);
+    assert_eq!(c.queried(), 0);
+    assert_eq!(a.dropped(), 1);
+    assert_eq!(b.dropped(), 1);
+    assert_eq!(c.dropped(), 1);
 }
 
 #[test]
 fn test_drain_filter_pred_panic_leak() {
-    static PREDS: AtomicU32 = AtomicU32::new(0);
-    static DROPS: AtomicU32 = AtomicU32::new(0);
-
-    #[derive(PartialEq, Eq, PartialOrd, Ord)]
-    struct D(i32);
-    impl Drop for D {
-        fn drop(&mut self) {
-            DROPS.fetch_add(1, SeqCst);
-        }
-    }
-
+    let a = CrashTestDummy::new(0);
+    let b = CrashTestDummy::new(1);
+    let c = CrashTestDummy::new(2);
     let mut set = BTreeSet::new();
-    set.insert(D(0));
-    set.insert(D(4));
-    set.insert(D(8));
+    set.insert(a.spawn(Panic::Never));
+    set.insert(b.spawn(Panic::InQuery));
+    set.insert(c.spawn(Panic::InQuery));
 
-    catch_unwind(AssertUnwindSafe(|| {
-        drop(set.drain_filter(|d| {
-            PREDS.fetch_add(1u32 << d.0, SeqCst);
-            match d.0 {
-                0 => true,
-                _ => panic!(),
-            }
-        }))
-    }))
-    .ok();
+    catch_unwind(AssertUnwindSafe(|| drop(set.drain_filter(|dummy| dummy.query(true))))).ok();
 
-    assert_eq!(PREDS.load(SeqCst), 0x011);
-    assert_eq!(DROPS.load(SeqCst), 1);
+    assert_eq!(a.queried(), 1);
+    assert_eq!(b.queried(), 1);
+    assert_eq!(c.queried(), 0);
+    assert_eq!(a.dropped(), 1);
+    assert_eq!(b.dropped(), 0);
+    assert_eq!(c.dropped(), 0);
     assert_eq!(set.len(), 2);
-    assert_eq!(set.first().unwrap().0, 4);
-    assert_eq!(set.last().unwrap().0, 8);
+    assert_eq!(set.first().unwrap().id(), 1);
+    assert_eq!(set.last().unwrap().id(), 2);
 }
 
 #[test]
diff --git a/library/alloc/src/collections/btree/testing/crash_test.rs b/library/alloc/src/collections/btree/testing/crash_test.rs
new file mode 100644
index 00000000000..b2527b95f5b
--- /dev/null
+++ b/library/alloc/src/collections/btree/testing/crash_test.rs
@@ -0,0 +1,119 @@
+use crate::fmt::Debug;
+use std::cmp::Ordering;
+use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+
+/// A blueprint for crash test dummy instances that monitor particular events.
+/// Some instances may be configured to panic at some point.
+/// Events are `clone`, `drop` or some anonymous `query`.
+///
+/// Crash test dummies are identified and ordered by an id, so they can be used
+/// as keys in a BTreeMap. The implementation intentionally uses does not rely
+/// on anything defined in the crate, apart from the `Debug` trait.
+#[derive(Debug)]
+pub struct CrashTestDummy {
+    id: usize,
+    cloned: AtomicUsize,
+    dropped: AtomicUsize,
+    queried: AtomicUsize,
+}
+
+impl CrashTestDummy {
+    /// Creates a crash test dummy design. The `id` determines order and equality of instances.
+    pub fn new(id: usize) -> CrashTestDummy {
+        CrashTestDummy {
+            id,
+            cloned: AtomicUsize::new(0),
+            dropped: AtomicUsize::new(0),
+            queried: AtomicUsize::new(0),
+        }
+    }
+
+    /// Creates an instance of a crash test dummy that records what events it experiences
+    /// and optionally panics.
+    pub fn spawn(&self, panic: Panic) -> Instance<'_> {
+        Instance { origin: self, panic }
+    }
+
+    /// Returns how many times instances of the dummy have been cloned.
+    pub fn cloned(&self) -> usize {
+        self.cloned.load(SeqCst)
+    }
+
+    /// Returns how many times instances of the dummy have been dropped.
+    pub fn dropped(&self) -> usize {
+        self.dropped.load(SeqCst)
+    }
+
+    /// Returns how many times instances of the dummy have had their `query` member invoked.
+    pub fn queried(&self) -> usize {
+        self.queried.load(SeqCst)
+    }
+}
+
+#[derive(Debug)]
+pub struct Instance<'a> {
+    origin: &'a CrashTestDummy,
+    panic: Panic,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Panic {
+    Never,
+    InClone,
+    InDrop,
+    InQuery,
+}
+
+impl Instance<'_> {
+    pub fn id(&self) -> usize {
+        self.origin.id
+    }
+
+    /// Some anonymous query, the result of which is already given.
+    pub fn query<R>(&self, result: R) -> R {
+        self.origin.queried.fetch_add(1, SeqCst);
+        if self.panic == Panic::InQuery {
+            panic!("panic in `query`");
+        }
+        result
+    }
+}
+
+impl Clone for Instance<'_> {
+    fn clone(&self) -> Self {
+        self.origin.cloned.fetch_add(1, SeqCst);
+        if self.panic == Panic::InClone {
+            panic!("panic in `clone`");
+        }
+        Self { origin: self.origin, panic: Panic::Never }
+    }
+}
+
+impl Drop for Instance<'_> {
+    fn drop(&mut self) {
+        self.origin.dropped.fetch_add(1, SeqCst);
+        if self.panic == Panic::InDrop {
+            panic!("panic in `drop`");
+        }
+    }
+}
+
+impl PartialOrd for Instance<'_> {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        self.id().partial_cmp(&other.id())
+    }
+}
+
+impl Ord for Instance<'_> {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.id().cmp(&other.id())
+    }
+}
+
+impl PartialEq for Instance<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        self.id().eq(&other.id())
+    }
+}
+
+impl Eq for Instance<'_> {}
diff --git a/library/alloc/src/collections/btree/testing/mod.rs b/library/alloc/src/collections/btree/testing/mod.rs
new file mode 100644
index 00000000000..7a094f8a595
--- /dev/null
+++ b/library/alloc/src/collections/btree/testing/mod.rs
@@ -0,0 +1,3 @@
+pub mod crash_test;
+pub mod ord_chaos;
+pub mod rng;
diff --git a/library/alloc/src/collections/btree/map/tests/ord_chaos.rs b/library/alloc/src/collections/btree/testing/ord_chaos.rs
similarity index 100%
rename from library/alloc/src/collections/btree/map/tests/ord_chaos.rs
rename to library/alloc/src/collections/btree/testing/ord_chaos.rs
diff --git a/library/alloc/src/collections/btree/testing/rng.rs b/library/alloc/src/collections/btree/testing/rng.rs
new file mode 100644
index 00000000000..ecf543bee03
--- /dev/null
+++ b/library/alloc/src/collections/btree/testing/rng.rs
@@ -0,0 +1,28 @@
+/// XorShiftRng
+pub struct DeterministicRng {
+    count: usize,
+    x: u32,
+    y: u32,
+    z: u32,
+    w: u32,
+}
+
+impl DeterministicRng {
+    pub fn new() -> Self {
+        DeterministicRng { count: 0, x: 0x193a6754, y: 0xa8a7d469, z: 0x97830e05, w: 0x113ba7bb }
+    }
+
+    /// Guarantees that each returned number is unique.
+    pub fn next(&mut self) -> u32 {
+        self.count += 1;
+        assert!(self.count <= 70029);
+        let x = self.x;
+        let t = x ^ (x << 11);
+        self.x = self.y;
+        self.y = self.z;
+        self.z = self.w;
+        let w_ = self.w;
+        self.w = w_ ^ (w_ >> 19) ^ (t ^ (t >> 8));
+        self.w
+    }
+}
diff --git a/library/test/src/event.rs b/library/test/src/event.rs
index 297bb72aecb..2103a0d10f4 100644
--- a/library/test/src/event.rs
+++ b/library/test/src/event.rs
@@ -24,8 +24,6 @@ impl CompletedTest {
     }
 }
 
-unsafe impl Send for CompletedTest {}
-
 #[derive(Debug, Clone)]
 pub enum TestEvent {
     TeFiltered(Vec<TestDesc>),
diff --git a/library/test/src/formatters/pretty.rs b/library/test/src/formatters/pretty.rs
index 6fa36929841..5e41d6d9692 100644
--- a/library/test/src/formatters/pretty.rs
+++ b/library/test/src/formatters/pretty.rs
@@ -222,10 +222,6 @@ impl<T: Write> OutputFormatter for PrettyFormatter<T> {
     }
 
     fn write_timeout(&mut self, desc: &TestDesc) -> io::Result<()> {
-        if self.is_multithreaded {
-            self.write_test_name(desc)?;
-        }
-
         self.write_plain(&format!(
             "test {} has been running for over {} seconds\n",
             desc.name,
diff --git a/library/test/src/test_result.rs b/library/test/src/test_result.rs
index 598fb670bb4..c5c56ca9c7f 100644
--- a/library/test/src/test_result.rs
+++ b/library/test/src/test_result.rs
@@ -24,8 +24,6 @@ pub enum TestResult {
     TrTimedFail,
 }
 
-unsafe impl Send for TestResult {}
-
 /// Creates a `TestResult` depending on the raw result of test execution
 /// and associated data.
 pub fn calc_result<'a>(
diff --git a/src/bootstrap/clean.rs b/src/bootstrap/clean.rs
index 9b9df36e7dc..3216c1af267 100644
--- a/src/bootstrap/clean.rs
+++ b/src/bootstrap/clean.rs
@@ -51,14 +51,40 @@ fn rm_rf(path: &Path) {
         }
         Ok(metadata) => {
             if metadata.file_type().is_file() || metadata.file_type().is_symlink() {
-                do_op(path, "remove file", |p| fs::remove_file(p));
+                do_op(path, "remove file", |p| {
+                    fs::remove_file(p).or_else(|e| {
+                        // Work around the fact that we cannot
+                        // delete an executable while it runs on Windows.
+                        #[cfg(windows)]
+                        if e.kind() == std::io::ErrorKind::PermissionDenied
+                            && p.file_name().and_then(std::ffi::OsStr::to_str)
+                                == Some("bootstrap.exe")
+                        {
+                            eprintln!("warning: failed to delete '{}'.", p.display());
+                            return Ok(());
+                        }
+                        Err(e)
+                    })
+                });
                 return;
             }
 
             for file in t!(fs::read_dir(path)) {
                 rm_rf(&t!(file).path());
             }
-            do_op(path, "remove dir", |p| fs::remove_dir(p));
+            do_op(path, "remove dir", |p| {
+                fs::remove_dir(p).or_else(|e| {
+                    // Check for dir not empty on Windows
+                    #[cfg(windows)]
+                    if matches!(e.kind(), std::io::ErrorKind::Other)
+                        && e.raw_os_error() == Some(145)
+                    {
+                        return Ok(());
+                    }
+
+                    Err(e)
+                })
+            });
         }
     };
 }
@@ -73,12 +99,18 @@ where
         // As a result, we have some special logic to remove readonly files on windows.
         // This is also the reason that we can't use things like fs::remove_dir_all().
         Err(ref e) if cfg!(windows) && e.kind() == ErrorKind::PermissionDenied => {
-            let mut p = t!(path.symlink_metadata()).permissions();
+            let m = t!(path.symlink_metadata());
+            let mut p = m.permissions();
             p.set_readonly(false);
             t!(fs::set_permissions(path, p));
             f(path).unwrap_or_else(|e| {
+                // Delete symlinked directories on Windows
+                #[cfg(windows)]
+                if m.file_type().is_symlink() && path.is_dir() && fs::remove_dir(path).is_ok() {
+                    return;
+                }
                 panic!("failed to {} {}: {}", desc, path.display(), e);
-            })
+            });
         }
         Err(e) => {
             panic!("failed to {} {}: {}", desc, path.display(), e);
diff --git a/src/bootstrap/format.rs b/src/bootstrap/format.rs
index 0ae9f9712d5..d21e3408144 100644
--- a/src/bootstrap/format.rs
+++ b/src/bootstrap/format.rs
@@ -3,10 +3,12 @@
 use crate::Build;
 use build_helper::{output, t};
 use ignore::WalkBuilder;
-use std::path::Path;
+use std::collections::VecDeque;
+use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
+use std::sync::mpsc::SyncSender;
 
-fn rustfmt(src: &Path, rustfmt: &Path, path: &Path, check: bool) {
+fn rustfmt(src: &Path, rustfmt: &Path, paths: &[PathBuf], check: bool) -> impl FnMut() {
     let mut cmd = Command::new(&rustfmt);
     // avoid the submodule config paths from coming into play,
     // we only allow a single global config for the workspace for now
@@ -17,17 +19,21 @@ fn rustfmt(src: &Path, rustfmt: &Path, path: &Path, check: bool) {
     if check {
         cmd.arg("--check");
     }
-    cmd.arg(&path);
+    cmd.args(paths);
     let cmd_debug = format!("{:?}", cmd);
-    let status = cmd.status().expect("executing rustfmt");
-    if !status.success() {
-        eprintln!(
-            "Running `{}` failed.\nIf you're running `tidy`, \
-            try again with `--bless`. Or, if you just want to format \
-            code, run `./x.py fmt` instead.",
-            cmd_debug,
-        );
-        std::process::exit(1);
+    let mut cmd = cmd.spawn().expect("running rustfmt");
+    // poor man's async: return a closure that'll wait for rustfmt's completion
+    move || {
+        let status = cmd.wait().unwrap();
+        if !status.success() {
+            eprintln!(
+                "Running `{}` failed.\nIf you're running `tidy`, \
+                        try again with `--bless`. Or, if you just want to format \
+                        code, run `./x.py fmt` instead.",
+                cmd_debug,
+            );
+            std::process::exit(1);
+        }
     }
 }
 
@@ -101,19 +107,58 @@ pub fn format(build: &Build, check: bool) {
     }
     let ignore_fmt = ignore_fmt.build().unwrap();
 
-    let rustfmt_path = build.config.initial_rustfmt.as_ref().unwrap_or_else(|| {
-        eprintln!("./x.py fmt is not supported on this channel");
-        std::process::exit(1);
+    let rustfmt_path = build
+        .config
+        .initial_rustfmt
+        .as_ref()
+        .unwrap_or_else(|| {
+            eprintln!("./x.py fmt is not supported on this channel");
+            std::process::exit(1);
+        })
+        .to_path_buf();
+    let src = build.src.clone();
+    let (tx, rx): (SyncSender<PathBuf>, _) = std::sync::mpsc::sync_channel(128);
+    let walker =
+        WalkBuilder::new(src.clone()).types(matcher).overrides(ignore_fmt).build_parallel();
+
+    // there is a lot of blocking involved in spawning a child process and reading files to format.
+    // spawn more processes than available concurrency to keep the CPU busy
+    let max_processes = build.jobs() as usize * 2;
+
+    // spawn child processes on a separate thread so we can batch entries we have received from ignore
+    let thread = std::thread::spawn(move || {
+        let mut children = VecDeque::new();
+        while let Ok(path) = rx.recv() {
+            // try getting a few more paths from the channel to amortize the overhead of spawning processes
+            let paths: Vec<_> = rx.try_iter().take(7).chain(std::iter::once(path)).collect();
+
+            let child = rustfmt(&src, &rustfmt_path, paths.as_slice(), check);
+            children.push_back(child);
+
+            if children.len() >= max_processes {
+                // await oldest child
+                children.pop_front().unwrap()();
+            }
+        }
+
+        // await remaining children
+        for mut child in children {
+            child();
+        }
     });
-    let src = &build.src;
-    let walker = WalkBuilder::new(src).types(matcher).overrides(ignore_fmt).build_parallel();
+
     walker.run(|| {
+        let tx = tx.clone();
         Box::new(move |entry| {
             let entry = t!(entry);
             if entry.file_type().map_or(false, |t| t.is_file()) {
-                rustfmt(src, &rustfmt_path, &entry.path(), check);
+                t!(tx.send(entry.into_path()));
             }
             ignore::WalkState::Continue
         })
     });
+
+    drop(tx);
+
+    thread.join().unwrap();
 }
diff --git a/src/ci/docker/host-x86_64/mingw-check/Dockerfile b/src/ci/docker/host-x86_64/mingw-check/Dockerfile
index b2aa5844e47..cb50e0c8f74 100644
--- a/src/ci/docker/host-x86_64/mingw-check/Dockerfile
+++ b/src/ci/docker/host-x86_64/mingw-check/Dockerfile
@@ -17,6 +17,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
   pkg-config \
   mingw-w64
 
+RUN curl -sL https://nodejs.org/dist/v14.4.0/node-v14.4.0-linux-x64.tar.xz | tar -xJ
+ENV PATH="/node-v14.4.0-linux-x64/bin:${PATH}"
+# Install es-check
+RUN npm install es-check -g
+
 COPY scripts/sccache.sh /scripts/
 RUN sh /scripts/sccache.sh
 
@@ -29,4 +34,6 @@ ENV SCRIPT python3 ../x.py --stage 2 test src/tools/expand-yaml-anchors && \
            python3 ../x.py test --stage 0 src/tools/compiletest && \
            python3 ../x.py test --stage 2 src/tools/tidy && \
            python3 ../x.py doc --stage 0 library/std && \
-           /scripts/validate-toolstate.sh
+           /scripts/validate-toolstate.sh && \
+           # Runs checks to ensure that there are no ES5 issues in our JS code.
+           es-check es5 ../src/librustdoc/html/static/*.js
diff --git a/src/doc/rustc/src/platform-support.md b/src/doc/rustc/src/platform-support.md
index 8198dbaa527..d21cf334442 100644
--- a/src/doc/rustc/src/platform-support.md
+++ b/src/doc/rustc/src/platform-support.md
@@ -153,6 +153,7 @@ not available.
 target | std | host | notes
 -------|-----|------|-------
 `aarch64-apple-ios-macabi` | ? |  | Apple Catalyst on ARM64
+`aarch64-apple-ios-sim` | ? |  | Apple iOS Simulator on ARM64
 `aarch64-apple-tvos` | * |  | ARM64 tvOS
 `aarch64-unknown-freebsd` | ✓ | ✓ | ARM64 FreeBSD
 `aarch64-unknown-hermit` | ? |  |
diff --git a/src/test/ui/try-block/try-block-bad-type.rs b/src/test/ui/try-block/try-block-bad-type.rs
index 496ba145810..ef6e690e1bd 100644
--- a/src/test/ui/try-block/try-block-bad-type.rs
+++ b/src/test/ui/try-block/try-block-bad-type.rs
@@ -3,7 +3,7 @@
 #![feature(try_blocks)]
 
 pub fn main() {
-    let res: Result<u32, i32> = try {
+    let res: Result<u32, std::array::TryFromSliceError> = try {
         Err("")?; //~ ERROR `?` couldn't convert the error
         5
     };
diff --git a/src/test/ui/try-block/try-block-bad-type.stderr b/src/test/ui/try-block/try-block-bad-type.stderr
index 2d1313d7d0e..75a42c0d6b7 100644
--- a/src/test/ui/try-block/try-block-bad-type.stderr
+++ b/src/test/ui/try-block/try-block-bad-type.stderr
@@ -1,16 +1,12 @@
-error[E0277]: `?` couldn't convert the error to `i32`
+error[E0277]: `?` couldn't convert the error to `TryFromSliceError`
   --> $DIR/try-block-bad-type.rs:7:16
    |
 LL |         Err("")?;
-   |                ^ the trait `From<&str>` is not implemented for `i32`
+   |                ^ the trait `From<&str>` is not implemented for `TryFromSliceError`
    |
    = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
    = help: the following implementations were found:
-             <i32 as From<NonZeroI32>>
-             <i32 as From<bool>>
-             <i32 as From<i16>>
-             <i32 as From<i8>>
-           and 2 others
+             <TryFromSliceError as From<Infallible>>
    = note: required by `from`
 
 error[E0271]: type mismatch resolving `<Result<i32, i32> as Try>::Ok == &str`