about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-03-23 18:38:18 +0000
committerbors <bors@rust-lang.org>2024-03-23 18:38:18 +0000
commite50ab294714ae706f85948efb17fac3c599ba2d3 (patch)
treeaad17e2b448b7f431eb2d9467cd5bb7419ddbaa0
parent020bbe46bdf613c0b8211984eaa8e5fe97d6c92d (diff)
parent59b29455c17a6bf2fd69c70c194849c86ba35885 (diff)
Auto merge of #122952 - RalfJung:miri, r=RalfJung
Miri subtree update

r? `@ghost`
-rw-r--r--src/tools/miri/.github/workflows/ci.yml2
-rw-r--r--src/tools/miri/bench-cargo-miri/range-iteration/Cargo.lock (renamed from src/tools/miri/bench-cargo-miri/invalidate/Cargo.lock)2
-rw-r--r--src/tools/miri/bench-cargo-miri/range-iteration/Cargo.toml (renamed from src/tools/miri/bench-cargo-miri/invalidate/Cargo.toml)2
-rw-r--r--src/tools/miri/bench-cargo-miri/range-iteration/src/main.rs (renamed from src/tools/miri/bench-cargo-miri/invalidate/src/main.rs)3
-rw-r--r--src/tools/miri/rust-version2
-rw-r--r--src/tools/miri/src/bin/miri.rs20
-rw-r--r--src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs17
-rw-r--r--src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs13
-rw-r--r--src/tools/miri/src/concurrency/data_race.rs141
-rw-r--r--src/tools/miri/src/concurrency/vector_clock.rs62
-rw-r--r--src/tools/miri/src/diagnostics.rs8
-rw-r--r--src/tools/miri/src/lib.rs1
-rw-r--r--src/tools/miri/src/machine.rs21
-rw-r--r--src/tools/miri/src/shims/intrinsics/simd.rs44
-rw-r--r--src/tools/miri/src/shims/x86/mod.rs13
-rw-r--r--src/tools/miri/src/shims/x86/sse2.rs6
-rw-r--r--src/tools/miri/test-cargo-miri/src/lib.rs18
-rw-r--r--src/tools/miri/test-cargo-miri/test.default.stdout.ref6
-rw-r--r--src/tools/miri/test-cargo-miri/test.filter.stdout.ref2
-rw-r--r--src/tools/miri/tests/fail/both_borrows/retag_data_race_write.rs2
-rw-r--r--src/tools/miri/tests/fail/both_borrows/retag_data_race_write.stack.stderr7
-rw-r--r--src/tools/miri/tests/fail/both_borrows/retag_data_race_write.tree.stderr7
-rw-r--r--src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.rs2
-rw-r--r--src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.stderr7
-rw-r--r--src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.rs2
-rw-r--r--src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.stderr7
-rw-r--r--src/tools/miri/tests/pass/intptrcast.rs26
-rw-r--r--src/tools/miri/tests/pass/intrinsics-x86-pause-without-sse2.rs25
-rw-r--r--src/tools/miri/tests/pass/intrinsics-x86-sse2.rs5
-rw-r--r--src/tools/miri/tests/pass/main_fn.rs5
-rw-r--r--src/tools/miri/tests/pass/portable-simd.rs18
31 files changed, 376 insertions, 120 deletions
diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml
index c70005c2c58..1097fc6db72 100644
--- a/src/tools/miri/.github/workflows/ci.yml
+++ b/src/tools/miri/.github/workflows/ci.yml
@@ -206,7 +206,7 @@ jobs:
         run: |
           PR=$(gh pr create -B master --title 'Automatic Rustup' --body '')
           ~/.local/bin/zulip-send --user $ZULIP_BOT_EMAIL --api-key $ZULIP_API_TOKEN --site https://rust-lang.zulipchat.com \
-            --stream miri --subject "Cron Job Failure (miri, $(date -u +%Y-%m))" \
+            --stream miri --subject "Miri Build Failure ($(date -u +%Y-%m))" \
             --message "A PR doing a rustc-pull [has been automatically created]($PR) for your convenience."
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/src/tools/miri/bench-cargo-miri/invalidate/Cargo.lock b/src/tools/miri/bench-cargo-miri/range-iteration/Cargo.lock
index 7bf23225ea5..2a5f8f32254 100644
--- a/src/tools/miri/bench-cargo-miri/invalidate/Cargo.lock
+++ b/src/tools/miri/bench-cargo-miri/range-iteration/Cargo.lock
@@ -3,5 +3,5 @@
 version = 3
 
 [[package]]
-name = "invalidate"
+name = "range-iteration"
 version = "0.1.0"
diff --git a/src/tools/miri/bench-cargo-miri/invalidate/Cargo.toml b/src/tools/miri/bench-cargo-miri/range-iteration/Cargo.toml
index 14cf0882f0b..3f0146f6259 100644
--- a/src/tools/miri/bench-cargo-miri/invalidate/Cargo.toml
+++ b/src/tools/miri/bench-cargo-miri/range-iteration/Cargo.toml
@@ -1,5 +1,5 @@
 [package]
-name = "invalidate"
+name = "range-iteration"
 version = "0.1.0"
 edition = "2021"
 
diff --git a/src/tools/miri/bench-cargo-miri/invalidate/src/main.rs b/src/tools/miri/bench-cargo-miri/range-iteration/src/main.rs
index fa8deb851c3..1ef35ee6b3c 100644
--- a/src/tools/miri/bench-cargo-miri/invalidate/src/main.rs
+++ b/src/tools/miri/bench-cargo-miri/range-iteration/src/main.rs
@@ -1,4 +1,5 @@
+//! This generates a lot of work for the AllocId part of the GC.
 fn main() {
     // The end of the range is just chosen to make the benchmark run for a few seconds.
-    for _ in 0..200_000 {}
+    for _ in 0..50_000 {}
 }
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index e32968d8178..9b89f016a77 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-ee03c286cfdca26fa5b2a4ee40957625d2c826ff
+c3b05c6e5b5b59613350b8c2875b0add67ed74df
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 6955e649b4d..c12382527e7 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -179,6 +179,26 @@ impl rustc_driver::Callbacks for MiriBeRustCompilerCalls {
             });
         }
     }
+
+    fn after_analysis<'tcx>(
+        &mut self,
+        _: &rustc_interface::interface::Compiler,
+        queries: &'tcx rustc_interface::Queries<'tcx>,
+    ) -> Compilation {
+        queries.global_ctxt().unwrap().enter(|tcx| {
+            if self.target_crate {
+                // cargo-miri has patched the compiler flags to make these into check-only builds,
+                // but we are still emulating regular rustc builds, which would perform post-mono
+                // const-eval during collection. So let's also do that here, even if we might be
+                // running with `--emit=metadata`. In particular this is needed to make
+                // `compile_fail` doc tests trigger post-mono errors.
+                // In general `collect_and_partition_mono_items` is not safe to call in check-only
+                // builds, but we are setting `-Zalways-encode-mir` which avoids those issues.
+                let _ = tcx.collect_and_partition_mono_items(());
+            }
+        });
+        Compilation::Continue
+    }
 }
 
 fn show_error(msg: &impl std::fmt::Display) -> ! {
diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
index b26bbd16902..96ff298402d 100644
--- a/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
+++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
@@ -18,6 +18,7 @@ use crate::borrow_tracker::{
     stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder},
     GlobalStateInner, ProtectorKind,
 };
+use crate::concurrency::data_race::{NaReadType, NaWriteType};
 use crate::*;
 
 use diagnostics::{RetagCause, RetagInfo};
@@ -751,7 +752,13 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
                     assert_eq!(access, AccessKind::Write);
                     // Make sure the data race model also knows about this.
                     if let Some(data_race) = alloc_extra.data_race.as_mut() {
-                        data_race.write(alloc_id, range, machine)?;
+                        data_race.write(
+                            alloc_id,
+                            range,
+                            NaWriteType::Retag,
+                            Some(place.layout.ty),
+                            machine,
+                        )?;
                     }
                 }
             }
@@ -794,7 +801,13 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
                         assert_eq!(access, AccessKind::Read);
                         // Make sure the data race model also knows about this.
                         if let Some(data_race) = alloc_extra.data_race.as_ref() {
-                            data_race.read(alloc_id, range, &this.machine)?;
+                            data_race.read(
+                                alloc_id,
+                                range,
+                                NaReadType::Retag,
+                                Some(place.layout.ty),
+                                &this.machine,
+                            )?;
                         }
                     }
                     Ok(())
diff --git a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
index 56ca1240f60..a3d49756e4c 100644
--- a/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
+++ b/src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
@@ -9,8 +9,11 @@ use rustc_middle::{
 use rustc_span::def_id::DefId;
 use rustc_target::abi::{Abi, Size};
 
-use crate::borrow_tracker::{GlobalState, GlobalStateInner, ProtectorKind};
 use crate::*;
+use crate::{
+    borrow_tracker::{GlobalState, GlobalStateInner, ProtectorKind},
+    concurrency::data_race::NaReadType,
+};
 
 pub mod diagnostics;
 mod perms;
@@ -312,7 +315,13 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
         // Also inform the data race model (but only if any bytes are actually affected).
         if range.size.bytes() > 0 {
             if let Some(data_race) = alloc_extra.data_race.as_ref() {
-                data_race.read(alloc_id, range, &this.machine)?;
+                data_race.read(
+                    alloc_id,
+                    range,
+                    NaReadType::Retag,
+                    Some(place.layout.ty),
+                    &this.machine,
+                )?;
             }
         }
 
diff --git a/src/tools/miri/src/concurrency/data_race.rs b/src/tools/miri/src/concurrency/data_race.rs
index 127d97bd5af..d51160b2831 100644
--- a/src/tools/miri/src/concurrency/data_race.rs
+++ b/src/tools/miri/src/concurrency/data_race.rs
@@ -49,7 +49,7 @@ use std::{
 use rustc_ast::Mutability;
 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_index::{Idx, IndexVec};
-use rustc_middle::mir;
+use rustc_middle::{mir, ty::Ty};
 use rustc_span::Span;
 use rustc_target::abi::{Align, HasDataLayout, Size};
 
@@ -200,18 +200,38 @@ enum AtomicAccessType {
     Rmw,
 }
 
-/// Type of write operation: allocating memory
-/// non-atomic writes and deallocating memory
-/// are all treated as writes for the purpose
-/// of the data-race detector.
+/// Type of a non-atomic read operation.
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
-enum NaWriteType {
+pub enum NaReadType {
+    /// Standard unsynchronized write.
+    Read,
+
+    // An implicit read generated by a retag.
+    Retag,
+}
+
+impl NaReadType {
+    fn description(self) -> &'static str {
+        match self {
+            NaReadType::Read => "non-atomic read",
+            NaReadType::Retag => "retag read",
+        }
+    }
+}
+
+/// Type of a non-atomic write operation: allocating memory, non-atomic writes, and
+/// deallocating memory are all treated as writes for the purpose of the data-race detector.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum NaWriteType {
     /// Allocate memory.
     Allocate,
 
     /// Standard unsynchronized write.
     Write,
 
+    // An implicit write generated by a retag.
+    Retag,
+
     /// Deallocate memory.
     /// Note that when memory is deallocated first, later non-atomic accesses
     /// will be reported as use-after-free, not as data races.
@@ -224,6 +244,7 @@ impl NaWriteType {
         match self {
             NaWriteType::Allocate => "creating a new allocation",
             NaWriteType::Write => "non-atomic write",
+            NaWriteType::Retag => "retag write",
             NaWriteType::Deallocate => "deallocation",
         }
     }
@@ -231,7 +252,7 @@ impl NaWriteType {
 
 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
 enum AccessType {
-    NaRead,
+    NaRead(NaReadType),
     NaWrite(NaWriteType),
     AtomicLoad,
     AtomicStore,
@@ -239,29 +260,48 @@ enum AccessType {
 }
 
 impl AccessType {
-    fn description(self) -> &'static str {
-        match self {
-            AccessType::NaRead => "non-atomic read",
+    fn description(self, ty: Option<Ty<'_>>, size: Option<Size>) -> String {
+        let mut msg = String::new();
+
+        if let Some(size) = size {
+            msg.push_str(&format!("{}-byte {}", size.bytes(), msg))
+        }
+
+        msg.push_str(match self {
+            AccessType::NaRead(w) => w.description(),
             AccessType::NaWrite(w) => w.description(),
             AccessType::AtomicLoad => "atomic load",
             AccessType::AtomicStore => "atomic store",
             AccessType::AtomicRmw => "atomic read-modify-write",
+        });
+
+        if let Some(ty) = ty {
+            msg.push_str(&format!(" of type `{}`", ty));
         }
+
+        msg
     }
 
     fn is_atomic(self) -> bool {
         match self {
             AccessType::AtomicLoad | AccessType::AtomicStore | AccessType::AtomicRmw => true,
-            AccessType::NaRead | AccessType::NaWrite(_) => false,
+            AccessType::NaRead(_) | AccessType::NaWrite(_) => false,
         }
     }
 
     fn is_read(self) -> bool {
         match self {
-            AccessType::AtomicLoad | AccessType::NaRead => true,
+            AccessType::AtomicLoad | AccessType::NaRead(_) => true,
             AccessType::NaWrite(_) | AccessType::AtomicStore | AccessType::AtomicRmw => false,
         }
     }
+
+    fn is_retag(self) -> bool {
+        matches!(
+            self,
+            AccessType::NaRead(NaReadType::Retag) | AccessType::NaWrite(NaWriteType::Retag)
+        )
+    }
 }
 
 /// Memory Cell vector clock metadata
@@ -502,12 +542,14 @@ impl MemoryCellClocks {
         &mut self,
         thread_clocks: &mut ThreadClockSet,
         index: VectorIdx,
+        read_type: NaReadType,
         current_span: Span,
     ) -> Result<(), DataRace> {
         trace!("Unsynchronized read with vectors: {:#?} :: {:#?}", self, thread_clocks);
         if !current_span.is_dummy() {
             thread_clocks.clock[index].span = current_span;
         }
+        thread_clocks.clock[index].set_read_type(read_type);
         if self.write_was_before(&thread_clocks.clock) {
             let race_free = if let Some(atomic) = self.atomic() {
                 // We must be ordered-after all atomic accesses, reads and writes.
@@ -875,7 +917,8 @@ impl VClockAlloc {
     /// This finds the two racing threads and the type
     /// of data-race that occurred. This will also
     /// return info about the memory location the data-race
-    /// occurred in.
+    /// occurred in. The `ty` parameter is used for diagnostics, letting
+    /// the user know which type was involved in the access.
     #[cold]
     #[inline(never)]
     fn report_data_race<'tcx>(
@@ -885,6 +928,7 @@ impl VClockAlloc {
         access: AccessType,
         access_size: Size,
         ptr_dbg: Pointer<AllocId>,
+        ty: Option<Ty<'_>>,
     ) -> InterpResult<'tcx> {
         let (current_index, current_clocks) = global.current_thread_state(thread_mgr);
         let mut other_size = None; // if `Some`, this was a size-mismatch race
@@ -908,7 +952,7 @@ impl VClockAlloc {
                 write_clock = mem_clocks.write();
                 (AccessType::NaWrite(mem_clocks.write_type), mem_clocks.write.0, &write_clock)
             } else if let Some(idx) = Self::find_gt_index(&mem_clocks.read, &current_clocks.clock) {
-                (AccessType::NaRead, idx, &mem_clocks.read)
+                (AccessType::NaRead(mem_clocks.read[idx].read_type()), idx, &mem_clocks.read)
             // Finally, mixed-size races.
             } else if access.is_atomic() && let Some(atomic) = mem_clocks.atomic() && atomic.size != access_size {
                 // This is only a race if we are not synchronized with all atomic accesses, so find
@@ -950,37 +994,33 @@ impl VClockAlloc {
         Err(err_machine_stop!(TerminationInfo::DataRace {
             involves_non_atomic,
             extra,
+            retag_explain: access.is_retag() || other_access.is_retag(),
             ptr: ptr_dbg,
             op1: RacingOp {
-                action: if let Some(other_size) = other_size {
-                    format!("{}-byte {}", other_size.bytes(), other_access.description())
-                } else {
-                    other_access.description().to_owned()
-                },
+                action: other_access.description(None, other_size),
                 thread_info: other_thread_info,
                 span: other_clock.as_slice()[other_thread.index()].span_data(),
             },
             op2: RacingOp {
-                action: if other_size.is_some() {
-                    format!("{}-byte {}", access_size.bytes(), access.description())
-                } else {
-                    access.description().to_owned()
-                },
+                action: access.description(ty, other_size.map(|_| access_size)),
                 thread_info: current_thread_info,
                 span: current_clocks.clock.as_slice()[current_index.index()].span_data(),
             },
         }))?
     }
 
-    /// Detect data-races for an unsynchronized read operation, will not perform
+    /// Detect data-races for an unsynchronized read operation. It will not perform
     /// data-race detection if `race_detecting()` is false, either due to no threads
     /// being created or if it is temporarily disabled during a racy read or write
     /// operation for which data-race detection is handled separately, for example
-    /// atomic read operations.
+    /// atomic read operations. The `ty` parameter is used for diagnostics, letting
+    /// the user know which type was read.
     pub fn read<'tcx>(
         &self,
         alloc_id: AllocId,
         access_range: AllocRange,
+        read_type: NaReadType,
+        ty: Option<Ty<'_>>,
         machine: &MiriMachine<'_, '_>,
     ) -> InterpResult<'tcx> {
         let current_span = machine.current_span();
@@ -992,7 +1032,7 @@ impl VClockAlloc {
                 alloc_ranges.iter_mut(access_range.start, access_range.size)
             {
                 if let Err(DataRace) =
-                    mem_clocks.read_race_detect(&mut thread_clocks, index, current_span)
+                    mem_clocks.read_race_detect(&mut thread_clocks, index, read_type, current_span)
                 {
                     drop(thread_clocks);
                     // Report data-race.
@@ -1000,9 +1040,10 @@ impl VClockAlloc {
                         global,
                         &machine.threads,
                         mem_clocks,
-                        AccessType::NaRead,
+                        AccessType::NaRead(read_type),
                         access_range.size,
                         Pointer::new(alloc_id, Size::from_bytes(mem_clocks_range.start)),
+                        ty,
                     );
                 }
             }
@@ -1012,12 +1053,17 @@ impl VClockAlloc {
         }
     }
 
-    // Shared code for detecting data-races on unique access to a section of memory
-    fn unique_access<'tcx>(
+    /// Detect data-races for an unsynchronized write operation. It will not perform
+    /// data-race detection if `race_detecting()` is false, either due to no threads
+    /// being created or if it is temporarily disabled during a racy read or write
+    /// operation. The `ty` parameter is used for diagnostics, letting
+    /// the user know which type was written.
+    pub fn write<'tcx>(
         &mut self,
         alloc_id: AllocId,
         access_range: AllocRange,
         write_type: NaWriteType,
+        ty: Option<Ty<'_>>,
         machine: &mut MiriMachine<'_, '_>,
     ) -> InterpResult<'tcx> {
         let current_span = machine.current_span();
@@ -1042,6 +1088,7 @@ impl VClockAlloc {
                         AccessType::NaWrite(write_type),
                         access_range.size,
                         Pointer::new(alloc_id, Size::from_bytes(mem_clocks_range.start)),
+                        ty,
                     );
                 }
             }
@@ -1050,37 +1097,6 @@ impl VClockAlloc {
             Ok(())
         }
     }
-
-    /// Detect data-races for an unsynchronized write operation, will not perform
-    /// data-race threads if `race_detecting()` is false, either due to no threads
-    /// being created or if it is temporarily disabled during a racy read or write
-    /// operation
-    pub fn write<'tcx>(
-        &mut self,
-        alloc_id: AllocId,
-        range: AllocRange,
-        machine: &mut MiriMachine<'_, '_>,
-    ) -> InterpResult<'tcx> {
-        self.unique_access(alloc_id, range, NaWriteType::Write, machine)
-    }
-
-    /// Detect data-races for an unsynchronized deallocate operation, will not perform
-    /// data-race threads if `race_detecting()` is false, either due to no threads
-    /// being created or if it is temporarily disabled during a racy read or write
-    /// operation
-    pub fn deallocate<'tcx>(
-        &mut self,
-        alloc_id: AllocId,
-        size: Size,
-        machine: &mut MiriMachine<'_, '_>,
-    ) -> InterpResult<'tcx> {
-        self.unique_access(
-            alloc_id,
-            alloc_range(Size::ZERO, size),
-            NaWriteType::Deallocate,
-            machine,
-        )
-    }
 }
 
 impl<'mir, 'tcx: 'mir> EvalContextPrivExt<'mir, 'tcx> for MiriInterpCx<'mir, 'tcx> {}
@@ -1279,7 +1295,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
                 let alloc_meta = this.get_alloc_extra(alloc_id)?.data_race.as_ref().unwrap();
                 trace!(
                     "Atomic op({}) with ordering {:?} on {:?} (size={})",
-                    access.description(),
+                    access.description(None, None),
                     &atomic,
                     place.ptr(),
                     size.bytes()
@@ -1307,6 +1323,7 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
                                         alloc_id,
                                         Size::from_bytes(mem_clocks_range.start),
                                     ),
+                                    None,
                                 )
                                 .map(|_| true);
                             }
diff --git a/src/tools/miri/src/concurrency/vector_clock.rs b/src/tools/miri/src/concurrency/vector_clock.rs
index fa93c9e00b1..fe719943dcb 100644
--- a/src/tools/miri/src/concurrency/vector_clock.rs
+++ b/src/tools/miri/src/concurrency/vector_clock.rs
@@ -4,9 +4,11 @@ use smallvec::SmallVec;
 use std::{
     cmp::Ordering,
     fmt::Debug,
-    ops::{Index, IndexMut},
+    ops::{Index, IndexMut, Shr},
 };
 
+use super::data_race::NaReadType;
+
 /// A vector clock index, this is associated with a thread id
 /// but in some cases one vector index may be shared with
 /// multiple thread ids if it's safe to do so.
@@ -50,13 +52,51 @@ const SMALL_VECTOR: usize = 4;
 /// so that diagnostics can report what code was responsible for an operation.
 #[derive(Clone, Copy, Debug)]
 pub struct VTimestamp {
-    time: u32,
+    /// The lowest bit indicates read type, the rest is the time.
+    /// `1` indicates a retag read, `0` a regular read.
+    time_and_read_type: u32,
     pub span: Span,
 }
 
 impl VTimestamp {
-    pub const ZERO: VTimestamp = VTimestamp { time: 0, span: DUMMY_SP };
+    pub const ZERO: VTimestamp = VTimestamp::new(0, NaReadType::Read, DUMMY_SP);
+
+    #[inline]
+    const fn encode_time_and_read_type(time: u32, read_type: NaReadType) -> u32 {
+        let read_type_bit = match read_type {
+            NaReadType::Read => 0,
+            NaReadType::Retag => 1,
+        };
+        // Put the `read_type` in the lowest bit and `time` in the rest
+        read_type_bit | time.checked_mul(2).expect("Vector clock overflow")
+    }
+
+    #[inline]
+    const fn new(time: u32, read_type: NaReadType, span: Span) -> Self {
+        Self { time_and_read_type: Self::encode_time_and_read_type(time, read_type), span }
+    }
+
+    #[inline]
+    fn time(&self) -> u32 {
+        self.time_and_read_type.shr(1)
+    }
 
+    #[inline]
+    fn set_time(&mut self, time: u32) {
+        self.time_and_read_type = Self::encode_time_and_read_type(time, self.read_type());
+    }
+
+    #[inline]
+    pub fn read_type(&self) -> NaReadType {
+        if self.time_and_read_type & 1 == 0 { NaReadType::Read } else { NaReadType::Retag }
+    }
+
+    #[inline]
+    pub fn set_read_type(&mut self, read_type: NaReadType) {
+        self.time_and_read_type = Self::encode_time_and_read_type(self.time(), read_type);
+    }
+
+    #[inline]
     pub fn span_data(&self) -> SpanData {
         self.span.data()
     }
@@ -64,7 +104,7 @@ impl VTimestamp {
 
 impl PartialEq for VTimestamp {
     fn eq(&self, other: &Self) -> bool {
-        self.time == other.time
+        self.time() == other.time()
     }
 }
 
@@ -78,7 +118,7 @@ impl PartialOrd for VTimestamp {
 
 impl Ord for VTimestamp {
     fn cmp(&self, other: &Self) -> Ordering {
-        self.time.cmp(&other.time)
+        self.time().cmp(&other.time())
     }
 }
 
@@ -130,7 +170,7 @@ impl VClock {
         let idx = idx.index();
         let mut_slice = self.get_mut_with_min_len(idx + 1);
         let idx_ref = &mut mut_slice[idx];
-        idx_ref.time = idx_ref.time.checked_add(1).expect("Vector clock overflow");
+        idx_ref.set_time(idx_ref.time().checked_add(1).expect("Vector clock overflow"));
         if !current_span.is_dummy() {
             idx_ref.span = current_span;
         }
@@ -379,8 +419,8 @@ impl IndexMut<VectorIdx> for VClock {
 ///  test suite
 #[cfg(test)]
 mod tests {
-
     use super::{VClock, VTimestamp, VectorIdx};
+    use crate::concurrency::data_race::NaReadType;
     use rustc_span::DUMMY_SP;
     use std::cmp::Ordering;
 
@@ -448,7 +488,13 @@ mod tests {
         while let Some(0) = slice.last() {
             slice = &slice[..slice.len() - 1]
         }
-        VClock(slice.iter().copied().map(|time| VTimestamp { time, span: DUMMY_SP }).collect())
+        VClock(
+            slice
+                .iter()
+                .copied()
+                .map(|time| VTimestamp::new(time, NaReadType::Read, DUMMY_SP))
+                .collect(),
+        )
     }
 
     fn assert_order(l: &[u32], r: &[u32], o: Option<Ordering>) {
diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs
index 03428b081c5..99d37065bac 100644
--- a/src/tools/miri/src/diagnostics.rs
+++ b/src/tools/miri/src/diagnostics.rs
@@ -46,6 +46,7 @@ pub enum TerminationInfo {
         op1: RacingOp,
         op2: RacingOp,
         extra: Option<&'static str>,
+        retag_explain: bool,
     },
 }
 
@@ -263,12 +264,17 @@ pub fn report_error<'tcx, 'mir>(
                 vec![(Some(*span), format!("the `{link_name}` symbol is defined here"))],
             Int2PtrWithStrictProvenance =>
                 vec![(None, format!("use Strict Provenance APIs (https://doc.rust-lang.org/nightly/std/ptr/index.html#strict-provenance, https://crates.io/crates/sptr) instead"))],
-            DataRace { op1, extra, .. } => {
+            DataRace { op1, extra, retag_explain, .. } => {
                 let mut helps = vec![(Some(op1.span), format!("and (1) occurred earlier here"))];
                 if let Some(extra) = extra {
                     helps.push((None, format!("{extra}")));
                     helps.push((None, format!("see https://doc.rust-lang.org/nightly/std/sync/atomic/index.html#memory-model-for-atomic-accesses for more information about the Rust memory model")));
                 }
+                if *retag_explain {
+                    helps.push((None, "retags occur on all (re)borrows and as well as when references are copied or moved".to_owned()));
+                    helps.push((None, "retags permit optimizations that insert speculative reads or writes".to_owned()));
+                    helps.push((None, "therefore from the perspective of data races, a retag has the same implications as a read or write".to_owned()));
+                }
                 helps.push((None, format!("this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior")));
                 helps.push((None, format!("see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information")));
                 helps
diff --git a/src/tools/miri/src/lib.rs b/src/tools/miri/src/lib.rs
index 416d0cda8f1..7821aa9efd4 100644
--- a/src/tools/miri/src/lib.rs
+++ b/src/tools/miri/src/lib.rs
@@ -1,5 +1,6 @@
 #![feature(rustc_private)]
 #![feature(cell_update)]
+#![feature(const_option)]
 #![feature(float_gamma)]
 #![feature(generic_nonzero)]
 #![feature(map_try_insert)]
diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs
index 29315c4933c..2137de6a29b 100644
--- a/src/tools/miri/src/machine.rs
+++ b/src/tools/miri/src/machine.rs
@@ -35,6 +35,9 @@ use crate::{
     *,
 };
 
+use self::concurrency::data_race::NaReadType;
+use self::concurrency::data_race::NaWriteType;
+
 /// First real-time signal.
 /// `signal(7)` says this must be between 32 and 64 and specifies 34 or 35
 /// as typical values.
@@ -372,10 +375,8 @@ pub struct PrimitiveLayouts<'tcx> {
 impl<'mir, 'tcx: 'mir> PrimitiveLayouts<'tcx> {
     fn new(layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>) -> Result<Self, &'tcx LayoutError<'tcx>> {
         let tcx = layout_cx.tcx;
-        let mut_raw_ptr =
-            Ty::new_mut_ptr(tcx, tcx.types.unit);
-        let const_raw_ptr =
-            Ty::new_imm_ptr(tcx, tcx.types.unit);
+        let mut_raw_ptr = Ty::new_mut_ptr(tcx, tcx.types.unit);
+        let const_raw_ptr = Ty::new_imm_ptr(tcx, tcx.types.unit);
         Ok(Self {
             unit: layout_cx.layout_of(Ty::new_unit(tcx))?,
             i8: layout_cx.layout_of(tcx.types.i8)?,
@@ -1240,7 +1241,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
                 .emit_diagnostic(NonHaltingDiagnostic::AccessedAlloc(alloc_id, AccessKind::Read));
         }
         if let Some(data_race) = &alloc_extra.data_race {
-            data_race.read(alloc_id, range, machine)?;
+            data_race.read(alloc_id, range, NaReadType::Read, None, machine)?;
         }
         if let Some(borrow_tracker) = &alloc_extra.borrow_tracker {
             borrow_tracker.before_memory_read(alloc_id, prov_extra, range, machine)?;
@@ -1264,7 +1265,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
                 .emit_diagnostic(NonHaltingDiagnostic::AccessedAlloc(alloc_id, AccessKind::Write));
         }
         if let Some(data_race) = &mut alloc_extra.data_race {
-            data_race.write(alloc_id, range, machine)?;
+            data_race.write(alloc_id, range, NaWriteType::Write, None, machine)?;
         }
         if let Some(borrow_tracker) = &mut alloc_extra.borrow_tracker {
             borrow_tracker.before_memory_write(alloc_id, prov_extra, range, machine)?;
@@ -1288,7 +1289,13 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
             machine.emit_diagnostic(NonHaltingDiagnostic::FreedAlloc(alloc_id));
         }
         if let Some(data_race) = &mut alloc_extra.data_race {
-            data_race.deallocate(alloc_id, size, machine)?;
+            data_race.write(
+                alloc_id,
+                alloc_range(Size::ZERO, size),
+                NaWriteType::Deallocate,
+                None,
+                machine,
+            )?;
         }
         if let Some(borrow_tracker) = &mut alloc_extra.borrow_tracker {
             borrow_tracker.before_memory_deallocation(alloc_id, prove_extra, size, machine)?;
diff --git a/src/tools/miri/src/shims/intrinsics/simd.rs b/src/tools/miri/src/shims/intrinsics/simd.rs
index c97a052f517..af98b38af8c 100644
--- a/src/tools/miri/src/shims/intrinsics/simd.rs
+++ b/src/tools/miri/src/shims/intrinsics/simd.rs
@@ -33,6 +33,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             | "round"
             | "trunc"
             | "fsqrt"
+            | "fsin"
+            | "fcos"
+            | "fexp"
+            | "fexp2"
+            | "flog"
+            | "flog2"
+            | "flog10"
             | "ctlz"
             | "cttz"
             | "bswap"
@@ -45,17 +52,16 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 assert_eq!(dest_len, op_len);
 
                 #[derive(Copy, Clone)]
-                enum Op {
+                enum Op<'a> {
                     MirOp(mir::UnOp),
                     Abs,
-                    Sqrt,
                     Round(rustc_apfloat::Round),
                     Numeric(Symbol),
+                    HostOp(&'a str),
                 }
                 let which = match intrinsic_name {
                     "neg" => Op::MirOp(mir::UnOp::Neg),
                     "fabs" => Op::Abs,
-                    "fsqrt" => Op::Sqrt,
                     "ceil" => Op::Round(rustc_apfloat::Round::TowardPositive),
                     "floor" => Op::Round(rustc_apfloat::Round::TowardNegative),
                     "round" => Op::Round(rustc_apfloat::Round::NearestTiesToAway),
@@ -64,7 +70,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                     "cttz" => Op::Numeric(sym::cttz),
                     "bswap" => Op::Numeric(sym::bswap),
                     "bitreverse" => Op::Numeric(sym::bitreverse),
-                    _ => unreachable!(),
+                    _ => Op::HostOp(intrinsic_name),
                 };
 
                 for i in 0..dest_len {
@@ -89,7 +95,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                                 FloatTy::F128 => unimplemented!("f16_f128"),
                             }
                         }
-                        Op::Sqrt => {
+                        Op::HostOp(host_op) => {
                             let ty::Float(float_ty) = op.layout.ty.kind() else {
                                 span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
                             };
@@ -98,13 +104,37 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                                 FloatTy::F16 => unimplemented!("f16_f128"),
                                 FloatTy::F32 => {
                                     let f = op.to_scalar().to_f32()?;
-                                    let res = f.to_host().sqrt().to_soft();
+                                    let f_host = f.to_host();
+                                    let res = match host_op {
+                                        "fsqrt" => f_host.sqrt(),
+                                        "fsin" => f_host.sin(),
+                                        "fcos" => f_host.cos(),
+                                        "fexp" => f_host.exp(),
+                                        "fexp2" => f_host.exp2(),
+                                        "flog" => f_host.ln(),
+                                        "flog2" => f_host.log2(),
+                                        "flog10" => f_host.log10(),
+                                        _ => bug!(),
+                                    };
+                                    let res = res.to_soft();
                                     let res = this.adjust_nan(res, &[f]);
                                     Scalar::from(res)
                                 }
                                 FloatTy::F64 => {
                                     let f = op.to_scalar().to_f64()?;
-                                    let res = f.to_host().sqrt().to_soft();
+                                    let f_host = f.to_host();
+                                    let res = match host_op {
+                                        "fsqrt" => f_host.sqrt(),
+                                        "fsin" => f_host.sin(),
+                                        "fcos" => f_host.cos(),
+                                        "fexp" => f_host.exp(),
+                                        "fexp2" => f_host.exp2(),
+                                        "flog" => f_host.ln(),
+                                        "flog2" => f_host.log2(),
+                                        "flog10" => f_host.log10(),
+                                        _ => bug!(),
+                                    };
+                                    let res = res.to_soft();
                                     let res = this.adjust_nan(res, &[f]);
                                     Scalar::from(res)
                                 }
diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs
index 7cd397625dc..7b7921219e6 100644
--- a/src/tools/miri/src/shims/x86/mod.rs
+++ b/src/tools/miri/src/shims/x86/mod.rs
@@ -88,6 +88,19 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
                 this.write_immediate(*sub, &this.project_field(dest, 1)?)?;
             }
 
+            // Used to implement the `_mm_pause` function.
+            // The intrinsic is used to hint the processor that the code is in a spin-loop.
+            // It is compiled down to a `pause` instruction. When SSE2 is not available,
+            // the instruction behaves like a no-op, so it is always safe to call the
+            // intrinsic.
+            "sse2.pause" => {
+                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                // Only exhibit the spin-loop hint behavior when SSE2 is enabled.
+                if this.tcx.sess.unstable_target_features.contains(&Symbol::intern("sse2")) {
+                    this.yield_active_thread();
+                }
+            }
+
             name if name.starts_with("sse.") => {
                 return sse::EvalContextExt::emulate_x86_sse_intrinsic(
                     this, link_name, abi, args, dest,
diff --git a/src/tools/miri/src/shims/x86/sse2.rs b/src/tools/miri/src/shims/x86/sse2.rs
index 18ff5d809e3..eb2cc9d37c8 100644
--- a/src/tools/miri/src/shims/x86/sse2.rs
+++ b/src/tools/miri/src/shims/x86/sse2.rs
@@ -580,12 +580,6 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
                     this.copy_op(&this.project_index(&left, i)?, &this.project_index(&dest, i)?)?;
                 }
             }
-            // Used to implement the `_mm_pause` function.
-            // The intrinsic is used to hint the processor that the code is in a spin-loop.
-            "pause" => {
-                let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
-                this.yield_active_thread();
-            }
             _ => return Ok(EmulateForeignItemResult::NotSupported),
         }
         Ok(EmulateForeignItemResult::NeedsJumping)
diff --git a/src/tools/miri/test-cargo-miri/src/lib.rs b/src/tools/miri/test-cargo-miri/src/lib.rs
index 66c8aa2eac5..e6b8c4ef65b 100644
--- a/src/tools/miri/test-cargo-miri/src/lib.rs
+++ b/src/tools/miri/test-cargo-miri/src/lib.rs
@@ -1,13 +1,31 @@
 /// Doc-test test
+///
 /// ```rust
 /// assert!(cargo_miri_test::make_true());
 /// ```
+///
+/// `no_run` test:
+///
 /// ```rust,no_run
 /// assert!(!cargo_miri_test::make_true());
 /// ```
+///
+/// `compile_fail` test:
+///
 /// ```rust,compile_fail
 /// assert!(cargo_miri_test::make_true() == 5);
 /// ```
+///
+/// Post-monomorphization error in `compile_fail` test:
+///
+/// ```rust,compile_fail
+/// struct Fail<T>(T);
+/// impl<T> Fail<T> {
+///     const C: () = panic!();
+/// }
+///
+/// let _val = Fail::<i32>::C;
+/// ```
 #[no_mangle]
 pub fn make_true() -> bool {
     issue_1567::use_the_dependency();
diff --git a/src/tools/miri/test-cargo-miri/test.default.stdout.ref b/src/tools/miri/test-cargo-miri/test.default.stdout.ref
index 9a17f3d61b6..922d2120bed 100644
--- a/src/tools/miri/test-cargo-miri/test.default.stdout.ref
+++ b/src/tools/miri/test-cargo-miri/test.default.stdout.ref
@@ -10,7 +10,7 @@ running 6 tests
 test result: ok. 5 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out
 
 
-running 4 tests
-....
-test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
+running 5 tests
+.....
+test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
 
diff --git a/src/tools/miri/test-cargo-miri/test.filter.stdout.ref b/src/tools/miri/test-cargo-miri/test.filter.stdout.ref
index c618956656a..5c819dd5323 100644
--- a/src/tools/miri/test-cargo-miri/test.filter.stdout.ref
+++ b/src/tools/miri/test-cargo-miri/test.filter.stdout.ref
@@ -13,5 +13,5 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out
 
 running 0 tests
 
-test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in $TIME
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 5 filtered out; finished in $TIME
 
diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.rs b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.rs
index eb1fe56df07..3edaf10f3dc 100644
--- a/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.rs
+++ b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.rs
@@ -17,7 +17,7 @@ fn thread_1(p: SendPtr) {
 fn thread_2(p: SendPtr) {
     let p = p.0;
     unsafe {
-        *p = 5; //~ ERROR: /Data race detected between \(1\) non-atomic (read|write) on thread `unnamed-[0-9]+` and \(2\) non-atomic write on thread `unnamed-[0-9]+`/
+        *p = 5; //~ ERROR: /Data race detected between \(1\) retag (read|write) on thread `unnamed-[0-9]+` and \(2\) non-atomic write on thread `unnamed-[0-9]+`/
     }
 }
 
diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.stack.stderr b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.stack.stderr
index c5b65e6f747..6f4b52fb887 100644
--- a/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.stack.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.stack.stderr
@@ -1,14 +1,17 @@
-error: Undefined Behavior: Data race detected between (1) non-atomic write on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
+error: Undefined Behavior: Data race detected between (1) retag write on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
   --> $DIR/retag_data_race_write.rs:LL:CC
    |
 LL |         *p = 5;
-   |         ^^^^^^ Data race detected between (1) non-atomic write on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
+   |         ^^^^^^ Data race detected between (1) retag write on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
    |
 help: and (1) occurred earlier here
   --> $DIR/retag_data_race_write.rs:LL:CC
    |
 LL |         let _r = &mut *p;
    |                  ^^^^^^^
+   = help: retags occur on all (re)borrows and as well as when references are copied or moved
+   = help: retags permit optimizations that insert speculative reads or writes
+   = help: therefore from the perspective of data races, a retag has the same implications as a read or write
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE (of the first span) on thread `unnamed-ID`:
diff --git a/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.tree.stderr b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.tree.stderr
index 62f139f6f08..fa0012f9b26 100644
--- a/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.tree.stderr
+++ b/src/tools/miri/tests/fail/both_borrows/retag_data_race_write.tree.stderr
@@ -1,14 +1,17 @@
-error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
+error: Undefined Behavior: Data race detected between (1) retag read on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
   --> $DIR/retag_data_race_write.rs:LL:CC
    |
 LL |         *p = 5;
-   |         ^^^^^^ Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
+   |         ^^^^^^ Data race detected between (1) retag read on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
    |
 help: and (1) occurred earlier here
   --> $DIR/retag_data_race_write.rs:LL:CC
    |
 LL |         let _r = &mut *p;
    |                  ^^^^^^^
+   = help: retags occur on all (re)borrows and as well as when references are copied or moved
+   = help: retags permit optimizations that insert speculative reads or writes
+   = help: therefore from the perspective of data races, a retag has the same implications as a read or write
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE (of the first span) on thread `unnamed-ID`:
diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.rs b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.rs
index 5db89c89b77..3de517055ec 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.rs
+++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.rs
@@ -13,7 +13,7 @@ fn main() {
         let ptr = ptr;
         // We do a protected mutable retag (but no write!) in this thread.
         fn retag(_x: &mut i32) {}
-        retag(unsafe { &mut *ptr.0 }); //~ERROR: Data race detected between (1) non-atomic read on thread `main` and (2) non-atomic write on thread `unnamed-1`
+        retag(unsafe { &mut *ptr.0 }); //~ERROR: Data race detected between (1) non-atomic read on thread `main` and (2) retag write of type `i32` on thread `unnamed-1`
     });
 
     // We do a read in the main thread.
diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.stderr b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.stderr
index 2ce757013d5..47ae4b5d46d 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.stderr
+++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_protected_read.stderr
@@ -1,14 +1,17 @@
-error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `main` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
+error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `main` and (2) retag write of type `i32` on thread `unnamed-ID` at ALLOC. (2) just happened here
   --> $DIR/retag_data_race_protected_read.rs:LL:CC
    |
 LL |         retag(unsafe { &mut *ptr.0 });
-   |                        ^^^^^^^^^^^ Data race detected between (1) non-atomic read on thread `main` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
+   |                        ^^^^^^^^^^^ Data race detected between (1) non-atomic read on thread `main` and (2) retag write of type `i32` on thread `unnamed-ID` at ALLOC. (2) just happened here
    |
 help: and (1) occurred earlier here
   --> $DIR/retag_data_race_protected_read.rs:LL:CC
    |
 LL |     unsafe { ptr.0.read() };
    |              ^^^^^^^^^^^^
+   = help: retags occur on all (re)borrows and as well as when references are copied or moved
+   = help: retags permit optimizations that insert speculative reads or writes
+   = help: therefore from the perspective of data races, a retag has the same implications as a read or write
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE (of the first span) on thread `unnamed-ID`:
diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.rs b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.rs
index 01a2e9ac474..25c92ddf6ca 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.rs
+++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.rs
@@ -15,7 +15,7 @@ fn thread_1(p: SendPtr) {
 fn thread_2(p: SendPtr) {
     let p = p.0;
     unsafe {
-        *p = 5; //~ ERROR: Data race detected between (1) non-atomic read on thread `unnamed-1` and (2) non-atomic write on thread `unnamed-2`
+        *p = 5; //~ ERROR: Data race detected between (1) retag read on thread `unnamed-1` and (2) non-atomic write on thread `unnamed-2`
     }
 }
 
diff --git a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.stderr b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.stderr
index d3c8d14e2a1..9fe9fbeda44 100644
--- a/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.stderr
+++ b/src/tools/miri/tests/fail/stacked_borrows/retag_data_race_read.stderr
@@ -1,14 +1,17 @@
-error: Undefined Behavior: Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
+error: Undefined Behavior: Data race detected between (1) retag read on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
   --> $DIR/retag_data_race_read.rs:LL:CC
    |
 LL |         *p = 5;
-   |         ^^^^^^ Data race detected between (1) non-atomic read on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
+   |         ^^^^^^ Data race detected between (1) retag read on thread `unnamed-ID` and (2) non-atomic write on thread `unnamed-ID` at ALLOC. (2) just happened here
    |
 help: and (1) occurred earlier here
   --> $DIR/retag_data_race_read.rs:LL:CC
    |
 LL |         let _r = &*p;
    |                  ^^^
+   = help: retags occur on all (re)borrows and as well as when references are copied or moved
+   = help: retags permit optimizations that insert speculative reads or writes
+   = help: therefore from the perspective of data races, a retag has the same implications as a read or write
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE (of the first span) on thread `unnamed-ID`:
diff --git a/src/tools/miri/tests/pass/intptrcast.rs b/src/tools/miri/tests/pass/intptrcast.rs
index 370b09f512c..4e9fa12c181 100644
--- a/src/tools/miri/tests/pass/intptrcast.rs
+++ b/src/tools/miri/tests/pass/intptrcast.rs
@@ -149,6 +149,31 @@ fn functions() {
     }
 }
 
+/// Example that should be UB but due to wildcard pointers being too permissive
+/// we don't notice.
+fn should_be_ub() {
+    let alloc1 = 1u8;
+    let alloc2 = 2u8;
+    // Expose both allocations
+    let addr1: usize = &alloc1 as *const u8 as usize;
+    let addr2: usize = &alloc2 as *const u8 as usize;
+
+    // Cast addr1 back to a pointer. In Miri, this gives it Wildcard provenance.
+    let wildcard = addr1 as *const u8;
+    unsafe {
+        // Read through the wildcard
+        assert_eq!(*wildcard, 1);
+        // Offset the pointer to another allocation.
+        // Note that we are doing this arithmetic that does not require we stay within bounds of the allocation.
+        let wildcard = wildcard.wrapping_offset(addr2 as isize - addr1 as isize);
+        // This should report UB:
+        assert_eq!(*wildcard, 2);
+        // ... but it doesn't. A pointer's provenance specifies a single allocation that it is allowed to read from.
+        // And wrapping_offset only modifies the address, not the provenance.
+        // So which allocation is wildcard allowed to access? It cannot be both.
+    }
+}
+
 fn main() {
     cast();
     cast_dangling();
@@ -162,4 +187,5 @@ fn main() {
     ptr_eq_integer();
     zst_deref_of_dangling();
     functions();
+    should_be_ub();
 }
diff --git a/src/tools/miri/tests/pass/intrinsics-x86-pause-without-sse2.rs b/src/tools/miri/tests/pass/intrinsics-x86-pause-without-sse2.rs
new file mode 100644
index 00000000000..c8b92fd5458
--- /dev/null
+++ b/src/tools/miri/tests/pass/intrinsics-x86-pause-without-sse2.rs
@@ -0,0 +1,25 @@
+// Ignore everything except x86 and x86_64
+// Any new targets that are added to CI should be ignored here.
+// (We cannot use `cfg`-based tricks here since the `target-feature` flags below only work on x86.)
+//@ignore-target-aarch64
+//@ignore-target-arm
+//@ignore-target-avr
+//@ignore-target-s390x
+//@ignore-target-thumbv7em
+//@ignore-target-wasm32
+//@compile-flags: -C target-feature=-sse2
+
+#[cfg(target_arch = "x86")]
+use std::arch::x86::*;
+#[cfg(target_arch = "x86_64")]
+use std::arch::x86_64::*;
+
+fn main() {
+    assert!(!is_x86_feature_detected!("sse2"));
+
+    unsafe {
+        // This is a SSE2 intrinsic, but it behaves as a no-op when SSE2
+        // is not available, so it is always safe to call.
+        _mm_pause();
+    }
+}
diff --git a/src/tools/miri/tests/pass/intrinsics-x86-sse2.rs b/src/tools/miri/tests/pass/intrinsics-x86-sse2.rs
index e636d6c8aaf..e0088b9eb24 100644
--- a/src/tools/miri/tests/pass/intrinsics-x86-sse2.rs
+++ b/src/tools/miri/tests/pass/intrinsics-x86-sse2.rs
@@ -54,6 +54,11 @@ mod tests {
             }
         }
 
+        fn test_mm_pause() {
+            unsafe { _mm_pause() }
+        }
+        test_mm_pause();
+
         #[target_feature(enable = "sse2")]
         unsafe fn test_mm_avg_epu8() {
             let (a, b) = (_mm_set1_epi8(3), _mm_set1_epi8(9));
diff --git a/src/tools/miri/tests/pass/main_fn.rs b/src/tools/miri/tests/pass/main_fn.rs
deleted file mode 100644
index 4cdd034f30e..00000000000
--- a/src/tools/miri/tests/pass/main_fn.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-mod foo {
-    pub(crate) fn bar() {}
-}
-
-use foo::bar as main;
diff --git a/src/tools/miri/tests/pass/portable-simd.rs b/src/tools/miri/tests/pass/portable-simd.rs
index 399913a757b..cdb441b450b 100644
--- a/src/tools/miri/tests/pass/portable-simd.rs
+++ b/src/tools/miri/tests/pass/portable-simd.rs
@@ -526,6 +526,23 @@ fn simd_intrinsics() {
     }
 }
 
+fn simd_float_intrinsics() {
+    use intrinsics::*;
+
+    // These are just smoke tests to ensure the intrinsics can be called.
+    unsafe {
+        let a = f32x4::splat(10.0);
+        simd_fsqrt(a);
+        simd_fsin(a);
+        simd_fcos(a);
+        simd_fexp(a);
+        simd_fexp2(a);
+        simd_flog(a);
+        simd_flog2(a);
+        simd_flog10(a);
+    }
+}
+
 fn simd_masked_loadstore() {
     // The buffer is deliberarely too short, so reading the last element would be UB.
     let buf = [3i32; 3];
@@ -559,5 +576,6 @@ fn main() {
     simd_gather_scatter();
     simd_round();
     simd_intrinsics();
+    simd_float_intrinsics();
     simd_masked_loadstore();
 }