about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--RELEASES.md115
-rw-r--r--compiler/rustc_parse/src/lexer/tokentrees.rs79
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs2
-rwxr-xr-xsrc/tools/miri/ci.sh2
-rw-r--r--src/tools/miri/src/concurrency/thread.rs32
-rw-r--r--src/tools/miri/src/diagnostics.rs3
-rw-r--r--src/tools/miri/src/eval.rs7
-rw-r--r--src/tools/miri/src/shims/unix/freebsd/foreign_items.rs16
-rw-r--r--src/tools/miri/src/shims/unix/sync.rs70
-rw-r--r--src/tools/miri/src/shims/x86/sse41.rs80
-rw-r--r--src/tools/miri/tests/fail/data_race/mixed_size_read.stderr1
-rw-r--r--src/tools/miri/tests/fail/data_race/mixed_size_write.stderr1
-rw-r--r--src/tools/miri/tests/fail/data_race/read_read_race1.stderr1
-rw-r--r--src/tools/miri/tests/fail/data_race/read_read_race2.stderr1
-rw-r--r--src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs (renamed from src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.rs)0
-rw-r--r--src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.stderr (renamed from src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.stderr)2
-rw-r--r--src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs (renamed from src/tools/miri/tests/fail/panic/thread_local_drop_panic.rs)0
-rw-r--r--src/tools/miri/tests/fail/panic/tls_macro_drop_panic.stderr (renamed from src/tools/miri/tests/fail/panic/thread_local_drop_panic.stderr)2
-rw-r--r--src/tools/miri/tests/fail/tls/tls_static_dealloc.rs (renamed from src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs)0
-rw-r--r--src/tools/miri/tests/fail/tls/tls_static_dealloc.stderr (renamed from src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr)4
-rw-r--r--src/tools/miri/tests/fail/tls_macro_leak.rs21
-rw-r--r--src/tools/miri/tests/fail/tls_macro_leak.stderr32
-rw-r--r--src/tools/miri/tests/fail/tls_static_leak.rs22
-rw-r--r--src/tools/miri/tests/fail/tls_static_leak.stderr23
-rw-r--r--src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr1
-rw-r--r--src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr1
-rw-r--r--src/tools/miri/tests/pass-dep/shims/pthreads.rs32
-rw-r--r--src/tools/miri/tests/pass/intrinsics-x86-sse41.rs388
-rw-r--r--src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs23
-rw-r--r--src/tools/miri/tests/pass/tls/tls_macro_drop.rs (renamed from src/tools/miri/tests/pass/concurrency/tls_lib_drop.rs)0
-rw-r--r--src/tools/miri/tests/pass/tls/tls_macro_drop.stack.stdout (renamed from src/tools/miri/tests/pass/concurrency/tls_lib_drop.stack.stdout)0
-rw-r--r--src/tools/miri/tests/pass/tls/tls_macro_drop.tree.stdout (renamed from src/tools/miri/tests/pass/concurrency/tls_lib_drop.tree.stdout)0
-rw-r--r--src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.rs (renamed from src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.rs)3
-rw-r--r--src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.stderr (renamed from src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.stderr)0
-rw-r--r--src/tools/miri/tests/pass/tls/tls_static.rs (renamed from src/tools/miri/tests/pass/concurrency/thread_locals.rs)0
-rw-r--r--tests/ui/parser/brace-in-let-chain.rs58
-rw-r--r--tests/ui/parser/brace-in-let-chain.stderr65
37 files changed, 947 insertions, 140 deletions
diff --git a/RELEASES.md b/RELEASES.md
index 1cc110e6607..3b764fd773b 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,3 +1,118 @@
+Version 1.74.0 (2023-11-16)
+==========================
+
+<a id="1.74.0-Language"></a>
+
+Language
+--------
+
+- [Codify that `std::mem::Discriminant<T>` does not depend on any lifetimes in T](https://github.com/rust-lang/rust/pull/104299/)
+- [Replace `private_in_public` lint with `private_interfaces` and `private_bounds` per RFC 2145](https://github.com/rust-lang/rust/pull/113126/)
+  Read more in [RFC 2145](https://rust-lang.github.io/rfcs/2145-type-privacy.html).
+- [Allow explicit `#[repr(Rust)]`](https://github.com/rust-lang/rust/pull/114201/)
+- [closure field capturing: don't depend on alignment of packed fields](https://github.com/rust-lang/rust/pull/115315/)
+- [Enable MIR-based drop-tracking for `async` blocks](https://github.com/rust-lang/rust/pull/107421/)
+
+<a id="1.74.0-Compiler"></a>
+
+Compiler
+--------
+
+- [stabilize combining +bundle and +whole-archive link modifiers](https://github.com/rust-lang/rust/pull/113301/)
+- [Stabilize `PATH` option for `--print KIND=PATH`](https://github.com/rust-lang/rust/pull/114183/)
+- [Enable ASAN/LSAN/TSAN for `*-apple-ios-macabi`](https://github.com/rust-lang/rust/pull/115644/)
+- [Promote loongarch64-unknown-none* to Tier 2](https://github.com/rust-lang/rust/pull/115368/)
+- [Add `i686-pc-windows-gnullvm` as a tier 3 target](https://github.com/rust-lang/rust/pull/115687/)
+
+<a id="1.74.0-Libraries"></a>
+
+Libraries
+---------
+
+- [Implement `From<OwnedFd/Handle>` for ChildStdin/out/err](https://github.com/rust-lang/rust/pull/98704/)
+- [Implement `From<{&,&mut} [T; N]>` for `Vec<T>` where `T: Clone`](https://github.com/rust-lang/rust/pull/111278/)
+- [impl Step for IP addresses](https://github.com/rust-lang/rust/pull/113748/)
+- [Implement `From<[T; N]>` for `Rc<[T]>` and `Arc<[T]>`](https://github.com/rust-lang/rust/pull/114041/)
+- [`impl TryFrom<char> for u16`](https://github.com/rust-lang/rust/pull/114065/)
+- [Stabilize `io_error_other` feature](https://github.com/rust-lang/rust/pull/115453/)
+- [Stabilize the `Saturating` type](https://github.com/rust-lang/rust/pull/115477/)
+- [Stabilize const_transmute_copy](https://github.com/rust-lang/rust/pull/115520/)
+
+<a id="1.74.0-Stabilized-APIs"></a>
+
+Stabilized APIs
+---------------
+
+- [`core::num::Saturating`](https://doc.rust-lang.org/stable/std/num/struct.Saturating.html)
+- [`impl From<io::Stdout> for std::process::Stdio`](https://doc.rust-lang.org/stable/std/process/struct.Stdio.html#impl-From%3CStdout%3E-for-Stdio)
+- [`impl From<io::Stderr> for std::process::Stdio`](https://doc.rust-lang.org/stable/std/process/struct.Stdio.html#impl-From%3CStderr%3E-for-Stdio)
+- [`impl From<OwnedHandle> for std::process::Child{Stdin, Stdout, Stderr}`](https://doc.rust-lang.org/stable/std/process/struct.Stdio.html#impl-From%3CStderr%3E-for-Stdio)
+- [`impl From<OwnedFd> for std::process::Child{Stdin, Stdout, Stderr}`](https://doc.rust-lang.org/stable/std/process/struct.Stdio.html#impl-From%3CStderr%3E-for-Stdio)
+- [`std::ffi::OsString::from_encoded_bytes_unchecked`](https://doc.rust-lang.org/stable/std/ffi/struct.OsString.html#method.from_encoded_bytes_unchecked)
+- [`std::ffi::OsString::into_encoded_bytes`](https://doc.rust-lang.org/stable/std/ffi/struct.OsString.html#method.into_encoded_bytes)
+- [`std::ffi::OsStr::from_encoded_bytes_unchecked`](https://doc.rust-lang.org/stable/std/ffi/struct.OsStr.html#method.from_encoded_bytes_unchecked)
+- [`std::ffi::OsStr::as_encoded_bytes`](https://doc.rust-lang.org/stable/std/ffi/struct.OsStr.html#method.as_encoded_bytes)
+- [`std::io::Error::other`](https://doc.rust-lang.org/stable/std/io/struct.Error.html#method.other)
+- [`impl TryFrom<char> for u16`](https://doc.rust-lang.org/stable/std/primitive.u16.html#impl-TryFrom%3Cchar%3E-for-u16)
+- [`impl<T: Clone, const N: usize> From<&[T; N]> for Vec<T>`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#impl-From%3C%26%5BT;+N%5D%3E-for-Vec%3CT,+Global%3E)
+- [`impl<T: Clone, const N: usize> From<&mut [T; N]> for Vec<T>`](https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#impl-From%3C%26mut+%5BT;+N%5D%3E-for-Vec%3CT,+Global%3E)
+- [`impl<T, const N: usize> From<[T; N]> for Arc<[T]>`](https://doc.rust-lang.org/stable/std/sync/struct.Arc.html#impl-From%3C%5BT;+N%5D%3E-for-Arc%3C%5BT%5D,+Global%3E)
+- [`impl<T, const N: usize> From<[T; N]> for Rc<[T]>`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#impl-From%3C%5BT;+N%5D%3E-for-Rc%3C%5BT%5D,+Global%3E)
+
+These APIs are now stable in const contexts:
+
+- [`core::mem::transmute_copy`](https://doc.rust-lang.org/beta/std/mem/fn.transmute_copy.html)
+- [`str::is_ascii`](https://doc.rust-lang.org/beta/std/primitive.str.html#method.is_ascii)
+- [`[u8]::is_ascii`](https://doc.rust-lang.org/beta/std/primitive.slice.html#method.is_ascii)
+
+<a id="1.74.0-Cargo"></a>
+
+Cargo
+-----
+
+- [fix: Set MSRV for internal packages](https://github.com/rust-lang/cargo/pull/12381/)
+- [config: merge lists in precedence order](https://github.com/rust-lang/cargo/pull/12515/)
+- [fix(update): Clarify meaning of --aggressive as --recursive](https://github.com/rust-lang/cargo/pull/12544/)
+- [fix(update): Make `-p` more convenient by being positional](https://github.com/rust-lang/cargo/pull/12545/)
+- [feat(help): Add styling to help output ](https://github.com/rust-lang/cargo/pull/12578/)
+- [feat(pkgid): Allow incomplete versions when unambigious](https://github.com/rust-lang/cargo/pull/12614/)
+- [feat: stabilize credential-process and registry-auth](https://github.com/rust-lang/cargo/pull/12649/)
+- [feat(cli): Add '-n' to dry-run](https://github.com/rust-lang/cargo/pull/12660/)
+- [Add support for `target.'cfg(..)'.linker`](https://github.com/rust-lang/cargo/pull/12535/)
+- [Stabilize `--keep-going`](https://github.com/rust-lang/cargo/pull/12568/)
+- [feat: Stabilize lints](https://github.com/rust-lang/cargo/pull/12648/)
+
+<a id="1.74.0-Rustdoc"></a>
+
+Rustdoc
+-------
+
+- [Add warning block support in rustdoc](https://github.com/rust-lang/rust/pull/106561/)
+- [Accept additional user-defined syntax classes in fenced code blocks](https://github.com/rust-lang/rust/pull/110800/)
+- [rustdoc-search: add support for type parameters](https://github.com/rust-lang/rust/pull/112725/)
+- [rustdoc: show inner enum and struct in type definition for concrete type](https://github.com/rust-lang/rust/pull/114855/)
+
+<a id="1.74.0-Compatibility-Notes"></a>
+
+Compatibility Notes
+-------------------
+
+- [Raise minimum supported Apple OS versions](https://github.com/rust-lang/rust/pull/104385/)
+- [make Cell::swap panic if the Cells partially overlap](https://github.com/rust-lang/rust/pull/114795/)
+- [Reject invalid crate names in `--extern`](https://github.com/rust-lang/rust/pull/116001/)
+- [Don't resolve generic impls that may be shadowed by dyn built-in impls](https://github.com/rust-lang/rust/pull/114941/)
+
+<a id="1.74.0-Internal-Changes"></a>
+
+Internal Changes
+----------------
+
+These changes do not affect any public interfaces of Rust, but they represent
+significant improvements to the performance or internals of rustc and related
+tools.
+
+None this cycle.
+
 Version 1.73.0 (2023-10-05)
 ==========================
 
diff --git a/compiler/rustc_parse/src/lexer/tokentrees.rs b/compiler/rustc_parse/src/lexer/tokentrees.rs
index 31d91fe80bd..db795ce9f72 100644
--- a/compiler/rustc_parse/src/lexer/tokentrees.rs
+++ b/compiler/rustc_parse/src/lexer/tokentrees.rs
@@ -5,7 +5,8 @@ use super::{StringReader, UnmatchedDelim};
 use rustc_ast::token::{self, Delimiter, Token};
 use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree};
 use rustc_ast_pretty::pprust::token_to_string;
-use rustc_errors::PErr;
+use rustc_errors::{Applicability, PErr};
+use rustc_span::symbol::kw;
 
 pub(super) struct TokenTreesReader<'a> {
     string_reader: StringReader<'a>,
@@ -116,24 +117,8 @@ impl<'a> TokenTreesReader<'a> {
         // We stop at any delimiter so we can try to recover if the user
         // uses an incorrect delimiter.
         let (tts, res) = self.parse_token_trees(/* is_delimited */ true);
-        if let Err(mut errs) = res {
-            // If there are unclosed delims, see if there are diff markers and if so, point them
-            // out instead of complaining about the unclosed delims.
-            let mut parser = crate::stream_to_parser(self.string_reader.sess, tts, None);
-            let mut diff_errs = vec![];
-            while parser.token != token::Eof {
-                if let Err(diff_err) = parser.err_diff_marker() {
-                    diff_errs.push(diff_err);
-                }
-                parser.bump();
-            }
-            if !diff_errs.is_empty() {
-                errs.iter_mut().for_each(|err| {
-                    err.delay_as_bug();
-                });
-                return Err(diff_errs);
-            }
-            return Err(errs);
+        if let Err(errs) = res {
+            return Err(self.unclosed_delim_err(tts, errs));
         }
 
         // Expand to cover the entire delimited token tree
@@ -220,6 +205,62 @@ impl<'a> TokenTreesReader<'a> {
         Ok(TokenTree::Delimited(delim_span, open_delim, tts))
     }
 
+    fn unclosed_delim_err(&mut self, tts: TokenStream, mut errs: Vec<PErr<'a>>) -> Vec<PErr<'a>> {
+        // If there are unclosed delims, see if there are diff markers and if so, point them
+        // out instead of complaining about the unclosed delims.
+        let mut parser = crate::stream_to_parser(self.string_reader.sess, tts, None);
+        let mut diff_errs = vec![];
+        // Suggest removing a `{` we think appears in an `if`/`while` condition
+        // We want to suggest removing a `{` only if we think we're in an `if`/`while` condition, but
+        // we have no way of tracking this in the lexer itself, so we piggyback on the parser
+        let mut in_cond = false;
+        while parser.token != token::Eof {
+            if let Err(diff_err) = parser.err_diff_marker() {
+                diff_errs.push(diff_err);
+            } else if parser.is_keyword_ahead(0, &[kw::If, kw::While]) {
+                in_cond = true;
+            } else if matches!(
+                parser.token.kind,
+                token::CloseDelim(Delimiter::Brace) | token::FatArrow
+            ) {
+                // end of the `if`/`while` body, or the end of a `match` guard
+                in_cond = false;
+            } else if in_cond && parser.token == token::OpenDelim(Delimiter::Brace) {
+                // Store the `&&` and `let` to use their spans later when creating the diagnostic
+                let maybe_andand = parser.look_ahead(1, |t| t.clone());
+                let maybe_let = parser.look_ahead(2, |t| t.clone());
+                if maybe_andand == token::OpenDelim(Delimiter::Brace) {
+                    // This might be the beginning of the `if`/`while` body (i.e., the end of the condition)
+                    in_cond = false;
+                } else if maybe_andand == token::AndAnd && maybe_let.is_keyword(kw::Let) {
+                    let mut err = parser.struct_span_err(
+                        parser.token.span,
+                        "found a `{` in the middle of a let-chain",
+                    );
+                    err.span_suggestion(
+                        parser.token.span,
+                        "consider removing this brace to parse the `let` as part of the same chain",
+                        "",
+                        Applicability::MachineApplicable,
+                    );
+                    err.span_label(
+                        maybe_andand.span.to(maybe_let.span),
+                        "you might have meant to continue the let-chain here",
+                    );
+                    errs.push(err);
+                }
+            }
+            parser.bump();
+        }
+        if !diff_errs.is_empty() {
+            errs.iter_mut().for_each(|err| {
+                err.delay_as_bug();
+            });
+            return diff_errs;
+        }
+        return errs;
+    }
+
     fn close_delim_err(&mut self, delim: Delimiter) -> PErr<'a> {
         // An unexpected closing delimiter (i.e., there is no
         // matching opening delimiter).
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 1a7ae406911..76f3f21a516 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -1115,7 +1115,7 @@ impl<'a> Parser<'a> {
     }
 
     /// Returns whether any of the given keywords are `dist` tokens ahead of the current one.
-    fn is_keyword_ahead(&self, dist: usize, kws: &[Symbol]) -> bool {
+    pub(crate) fn is_keyword_ahead(&self, dist: usize, kws: &[Symbol]) -> bool {
         self.look_ahead(dist, |t| kws.iter().any(|&kw| t.is_keyword(kw)))
     }
 
diff --git a/src/tools/miri/ci.sh b/src/tools/miri/ci.sh
index 5078e9eaab3..f0917556c64 100755
--- a/src/tools/miri/ci.sh
+++ b/src/tools/miri/ci.sh
@@ -108,7 +108,7 @@ case $HOST_TARGET in
     MIRI_TEST_TARGET=aarch64-unknown-linux-gnu run_tests
     MIRI_TEST_TARGET=aarch64-apple-darwin run_tests
     MIRI_TEST_TARGET=i686-pc-windows-gnu run_tests
-    MIRI_TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple atomic env/var
+    MIRI_TEST_TARGET=x86_64-unknown-freebsd run_tests_minimal hello integer vec panic/panic concurrency/simple pthreads atomic env/var
     MIRI_TEST_TARGET=aarch64-linux-android run_tests_minimal hello integer vec panic/panic
     MIRI_TEST_TARGET=wasm32-wasi run_tests_minimal no_std integer strings wasm
     MIRI_TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std integer strings wasm
diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs
index 9041683fbc4..6449ed29cf8 100644
--- a/src/tools/miri/src/concurrency/thread.rs
+++ b/src/tools/miri/src/concurrency/thread.rs
@@ -33,6 +33,15 @@ enum SchedulingAction {
     Sleep(Duration),
 }
 
+/// What to do with TLS allocations from terminated threads
+pub enum TlsAllocAction {
+    /// Deallocate backing memory of thread-local statics as usual
+    Deallocate,
+    /// Skip deallocating backing memory of thread-local statics and consider all memory reachable
+    /// from them as "allowed to leak" (like global `static`s).
+    Leak,
+}
+
 /// Trait for callbacks that can be executed when some event happens, such as after a timeout.
 pub trait MachineCallback<'mir, 'tcx>: VisitTags {
     fn call(&self, ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>) -> InterpResult<'tcx>;
@@ -1051,7 +1060,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                         // See if this thread can do something else.
                         match this.run_on_stack_empty()? {
                             Poll::Pending => {} // keep going
-                            Poll::Ready(()) => this.terminate_active_thread()?,
+                            Poll::Ready(()) =>
+                                this.terminate_active_thread(TlsAllocAction::Deallocate)?,
                         }
                     }
                 }
@@ -1066,21 +1076,29 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     }
 
     /// Handles thread termination of the active thread: wakes up threads joining on this one,
-    /// and deallocated thread-local statics.
+    /// and deals with the thread's thread-local statics according to `tls_alloc_action`.
     ///
     /// This is called by the eval loop when a thread's on_stack_empty returns `Ready`.
     #[inline]
-    fn terminate_active_thread(&mut self) -> InterpResult<'tcx> {
+    fn terminate_active_thread(&mut self, tls_alloc_action: TlsAllocAction) -> InterpResult<'tcx> {
         let this = self.eval_context_mut();
         let thread = this.active_thread_mut();
         assert!(thread.stack.is_empty(), "only threads with an empty stack can be terminated");
         thread.state = ThreadState::Terminated;
 
         let current_span = this.machine.current_span();
-        for ptr in
-            this.machine.threads.thread_terminated(this.machine.data_race.as_mut(), current_span)
-        {
-            this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?;
+        let thread_local_allocations =
+            this.machine.threads.thread_terminated(this.machine.data_race.as_mut(), current_span);
+        for ptr in thread_local_allocations {
+            match tls_alloc_action {
+                TlsAllocAction::Deallocate =>
+                    this.deallocate_ptr(ptr.into(), None, MiriMemoryKind::Tls.into())?,
+                TlsAllocAction::Leak =>
+                    if let Some(alloc) = ptr.provenance.get_alloc_id() {
+                        trace!("Thread-local static leaked and stored as static root: {:?}", alloc);
+                        this.machine.static_roots.push(alloc);
+                    },
+            }
         }
         Ok(())
     }
diff --git a/src/tools/miri/src/diagnostics.rs b/src/tools/miri/src/diagnostics.rs
index fe445c6dcd9..c2ef7710011 100644
--- a/src/tools/miri/src/diagnostics.rs
+++ b/src/tools/miri/src/diagnostics.rs
@@ -270,7 +270,8 @@ pub fn report_error<'tcx, 'mir>(
             DataRace { op1, extra, .. } => {
                 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!("{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")));
                 }
                 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")));
diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs
index 5b785c0143e..1345b22a34a 100644
--- a/src/tools/miri/src/eval.rs
+++ b/src/tools/miri/src/eval.rs
@@ -11,6 +11,7 @@ use log::info;
 use rustc_middle::ty::Ty;
 
 use crate::borrow_tracker::RetagFields;
+use crate::concurrency::thread::TlsAllocAction;
 use crate::diagnostics::report_leaks;
 use rustc_data_structures::fx::FxHashSet;
 use rustc_hir::def::Namespace;
@@ -244,9 +245,9 @@ impl MainThreadState {
                 // Figure out exit code.
                 let ret_place = this.machine.main_fn_ret_place.clone().unwrap();
                 let exit_code = this.read_target_isize(&ret_place)?;
-                // Need to call this ourselves since we are not going to return to the scheduler
-                // loop, and we want the main thread TLS to not show up as memory leaks.
-                this.terminate_active_thread()?;
+                // Deal with our thread-local memory. We do *not* want to actually free it, instead we consider TLS
+                // to be like a global `static`, so that all memory reached by it is considered to "not leak".
+                this.terminate_active_thread(TlsAllocAction::Leak)?;
                 // Stop interpreter loop.
                 throw_machine_stop!(TerminationInfo::Exit { code: exit_code, leak_check: true });
             }
diff --git a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
index 869434e8876..96e322c4cf5 100644
--- a/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
@@ -29,13 +29,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
             "pthread_set_name_np" => {
                 let [thread, name] =
                     this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
-                let max_len = usize::MAX; // freebsd does not seem to have a limit.
-                let res = this.pthread_setname_np(
+                let max_len = usize::MAX; // FreeBSD does not seem to have a limit.
+                // FreeBSD's pthread_set_name_np does not return anything.
+                this.pthread_setname_np(
                     this.read_scalar(thread)?,
                     this.read_scalar(name)?,
                     max_len,
                 )?;
-                this.write_scalar(res, dest)?;
+            }
+            "pthread_get_name_np" => {
+                let [thread, name, len] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+                // FreeBSD's pthread_get_name_np does not return anything.
+                this.pthread_getname_np(
+                    this.read_scalar(thread)?,
+                    this.read_scalar(name)?,
+                    this.read_scalar(len)?,
+                )?;
             }
 
             // errno
diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs
index 6666ffbd1d5..1a91219e016 100644
--- a/src/tools/miri/src/shims/unix/sync.rs
+++ b/src/tools/miri/src/shims/unix/sync.rs
@@ -277,6 +277,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_mutexattr_init` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         let default_kind = this.eval_libc_i32("PTHREAD_MUTEX_DEFAULT");
         mutexattr_set_kind(this, attr_op, default_kind)?;
 
@@ -359,6 +366,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_mutex_init` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         let attr = this.read_pointer(attr_op)?;
         let kind = if this.ptr_is_null(attr)? {
             this.eval_libc_i32("PTHREAD_MUTEX_DEFAULT")
@@ -513,6 +527,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_rwlock_rdlock` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         let id = rwlock_get_id(this, rwlock_op)?;
         let active_thread = this.get_active_thread();
 
@@ -531,6 +552,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_rwlock_tryrdlock` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         let id = rwlock_get_id(this, rwlock_op)?;
         let active_thread = this.get_active_thread();
 
@@ -548,6 +576,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_rwlock_wrlock` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         let id = rwlock_get_id(this, rwlock_op)?;
         let active_thread = this.get_active_thread();
 
@@ -578,6 +613,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_rwlock_trywrlock` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         let id = rwlock_get_id(this, rwlock_op)?;
         let active_thread = this.get_active_thread();
 
@@ -595,6 +637,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_rwlock_unlock` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         let id = rwlock_get_id(this, rwlock_op)?;
         let active_thread = this.get_active_thread();
 
@@ -614,6 +663,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_rwlock_destroy` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         let id = rwlock_get_id(this, rwlock_op)?;
 
         if this.rwlock_is_locked(id) {
@@ -638,6 +694,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_condattr_init` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         // The default value of the clock attribute shall refer to the system
         // clock.
         // https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_condattr_setclock.html
@@ -704,6 +767,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
     ) -> InterpResult<'tcx, i32> {
         let this = self.eval_context_mut();
 
+        if !matches!(&*this.tcx.sess.target.os, "linux" | "macos") {
+            throw_unsup_format!(
+                "`pthread_cond_init` is not supported on {}",
+                this.tcx.sess.target.os
+            );
+        }
+
         let attr = this.read_pointer(attr_op)?;
         let clock_id = if this.ptr_is_null(attr)? {
             this.eval_libc_i32("CLOCK_REALTIME")
diff --git a/src/tools/miri/src/shims/x86/sse41.rs b/src/tools/miri/src/shims/x86/sse41.rs
index cfa06ded6e6..523f3bfc26f 100644
--- a/src/tools/miri/src/shims/x86/sse41.rs
+++ b/src/tools/miri/src/shims/x86/sse41.rs
@@ -148,6 +148,14 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
 
                 round_first::<rustc_apfloat::ieee::Single>(this, left, right, rounding, dest)?;
             }
+            // Used to implement the _mm_floor_ps, _mm_ceil_ps and _mm_round_ps
+            // functions. Rounds the elements of `op` according to `rounding`.
+            "round.ps" => {
+                let [op, rounding] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+
+                round_all::<rustc_apfloat::ieee::Single>(this, op, rounding, dest)?;
+            }
             // Used to implement the _mm_floor_sd, _mm_ceil_sd and _mm_round_sd
             // functions. Rounds the first element of `right` according to `rounding`
             // and copies the remaining elements from `left`.
@@ -157,6 +165,14 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:
 
                 round_first::<rustc_apfloat::ieee::Double>(this, left, right, rounding, dest)?;
             }
+            // Used to implement the _mm_floor_pd, _mm_ceil_pd and _mm_round_pd
+            // functions. Rounds the elements of `op` according to `rounding`.
+            "round.pd" => {
+                let [op, rounding] =
+                    this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
+
+                round_all::<rustc_apfloat::ieee::Double>(this, op, rounding, dest)?;
+            }
             // Used to implement the _mm_minpos_epu16 function.
             // Find the minimum unsinged 16-bit integer in `op` and
             // returns its value and position.
@@ -283,22 +299,7 @@ fn round_first<'tcx, F: rustc_apfloat::Float>(
     assert_eq!(dest_len, left_len);
     assert_eq!(dest_len, right_len);
 
-    // The fourth bit of `rounding` only affects the SSE status
-    // register, which cannot be accessed from Miri (or from Rust,
-    // for that matter), so we can ignore it.
-    let rounding = match this.read_scalar(rounding)?.to_i32()? & !0b1000 {
-        // When the third bit is 0, the rounding mode is determined by the
-        // first two bits.
-        0b000 => rustc_apfloat::Round::NearestTiesToEven,
-        0b001 => rustc_apfloat::Round::TowardNegative,
-        0b010 => rustc_apfloat::Round::TowardPositive,
-        0b011 => rustc_apfloat::Round::TowardZero,
-        // When the third bit is 1, the rounding mode is determined by the
-        // SSE status register. Since we do not support modifying it from
-        // Miri (or Rust), we assume it to be at its default mode (round-to-nearest).
-        0b100..=0b111 => rustc_apfloat::Round::NearestTiesToEven,
-        rounding => throw_unsup_format!("unsupported rounding mode 0x{rounding:02x}"),
-    };
+    let rounding = rounding_from_imm(this.read_scalar(rounding)?.to_i32()?)?;
 
     let op0: F = this.read_scalar(&this.project_index(&right, 0)?)?.to_float()?;
     let res = op0.round_to_integral(rounding).value;
@@ -317,3 +318,50 @@ fn round_first<'tcx, F: rustc_apfloat::Float>(
 
     Ok(())
 }
+
+// Rounds all elements of `op` according to `rounding`.
+fn round_all<'tcx, F: rustc_apfloat::Float>(
+    this: &mut crate::MiriInterpCx<'_, 'tcx>,
+    op: &OpTy<'tcx, Provenance>,
+    rounding: &OpTy<'tcx, Provenance>,
+    dest: &PlaceTy<'tcx, Provenance>,
+) -> InterpResult<'tcx, ()> {
+    let (op, op_len) = this.operand_to_simd(op)?;
+    let (dest, dest_len) = this.place_to_simd(dest)?;
+
+    assert_eq!(dest_len, op_len);
+
+    let rounding = rounding_from_imm(this.read_scalar(rounding)?.to_i32()?)?;
+
+    for i in 0..dest_len {
+        let op: F = this.read_scalar(&this.project_index(&op, i)?)?.to_float()?;
+        let res = op.round_to_integral(rounding).value;
+        this.write_scalar(
+            Scalar::from_uint(res.to_bits(), Size::from_bits(F::BITS)),
+            &this.project_index(&dest, i)?,
+        )?;
+    }
+
+    Ok(())
+}
+
+/// Gets equivalent `rustc_apfloat::Round` from rounding mode immediate of
+/// `round.{ss,sd,ps,pd}` intrinsics.
+fn rounding_from_imm<'tcx>(rounding: i32) -> InterpResult<'tcx, rustc_apfloat::Round> {
+    // The fourth bit of `rounding` only affects the SSE status
+    // register, which cannot be accessed from Miri (or from Rust,
+    // for that matter), so we can ignore it.
+    match rounding & !0b1000 {
+        // When the third bit is 0, the rounding mode is determined by the
+        // first two bits.
+        0b000 => Ok(rustc_apfloat::Round::NearestTiesToEven),
+        0b001 => Ok(rustc_apfloat::Round::TowardNegative),
+        0b010 => Ok(rustc_apfloat::Round::TowardPositive),
+        0b011 => Ok(rustc_apfloat::Round::TowardZero),
+        // When the third bit is 1, the rounding mode is determined by the
+        // SSE status register. Since we do not support modifying it from
+        // Miri (or Rust), we assume it to be at its default mode (round-to-nearest).
+        0b100..=0b111 => Ok(rustc_apfloat::Round::NearestTiesToEven),
+        rounding => throw_unsup_format!("unsupported rounding mode 0x{rounding:02x}"),
+    }
+}
diff --git a/src/tools/miri/tests/fail/data_race/mixed_size_read.stderr b/src/tools/miri/tests/fail/data_race/mixed_size_read.stderr
index cb7dc89359a..8988655208a 100644
--- a/src/tools/miri/tests/fail/data_race/mixed_size_read.stderr
+++ b/src/tools/miri/tests/fail/data_race/mixed_size_read.stderr
@@ -10,6 +10,7 @@ help: and (1) occurred earlier here
 LL |             a16.load(Ordering::SeqCst);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^
    = help: overlapping unsynchronized atomic accesses must use the same access size
+   = help: 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
    = 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):
diff --git a/src/tools/miri/tests/fail/data_race/mixed_size_write.stderr b/src/tools/miri/tests/fail/data_race/mixed_size_write.stderr
index b3908e9c6bf..55c9011f1b4 100644
--- a/src/tools/miri/tests/fail/data_race/mixed_size_write.stderr
+++ b/src/tools/miri/tests/fail/data_race/mixed_size_write.stderr
@@ -10,6 +10,7 @@ help: and (1) occurred earlier here
 LL |             a16.store(1, Ordering::SeqCst);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    = help: overlapping unsynchronized atomic accesses must use the same access size
+   = help: 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
    = 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):
diff --git a/src/tools/miri/tests/fail/data_race/read_read_race1.stderr b/src/tools/miri/tests/fail/data_race/read_read_race1.stderr
index 0846a88f362..e1009472fee 100644
--- a/src/tools/miri/tests/fail/data_race/read_read_race1.stderr
+++ b/src/tools/miri/tests/fail/data_race/read_read_race1.stderr
@@ -10,6 +10,7 @@ help: and (1) occurred earlier here
 LL |             unsafe { ptr.read() };
    |                      ^^^^^^^^^^
    = help: overlapping atomic and non-atomic accesses must be synchronized, even if both are read-only
+   = help: 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
    = 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):
diff --git a/src/tools/miri/tests/fail/data_race/read_read_race2.stderr b/src/tools/miri/tests/fail/data_race/read_read_race2.stderr
index c6181cc45b2..22017ae633d 100644
--- a/src/tools/miri/tests/fail/data_race/read_read_race2.stderr
+++ b/src/tools/miri/tests/fail/data_race/read_read_race2.stderr
@@ -10,6 +10,7 @@ help: and (1) occurred earlier here
 LL |             a.load(Ordering::SeqCst);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^
    = help: overlapping atomic and non-atomic accesses must be synchronized, even if both are read-only
+   = help: 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
    = 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):
diff --git a/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.rs b/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs
index 93ad42ea1cc..93ad42ea1cc 100644
--- a/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.rs
+++ b/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.rs
diff --git a/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.stderr b/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.stderr
index 550d009607d..17e92fec6fd 100644
--- a/src/tools/miri/tests/fail/panic/thread_local_const_drop_panic.stderr
+++ b/src/tools/miri/tests/fail/panic/tls_macro_const_drop_panic.stderr
@@ -1,4 +1,4 @@
-thread $NAME panicked at $DIR/thread_local_const_drop_panic.rs:LL:CC:
+thread $NAME panicked at $DIR/tls_macro_const_drop_panic.rs:LL:CC:
 ow
 fatal runtime error: thread local panicked on drop
 error: abnormal termination: the program aborted execution
diff --git a/src/tools/miri/tests/fail/panic/thread_local_drop_panic.rs b/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs
index 107d70a3b3c..107d70a3b3c 100644
--- a/src/tools/miri/tests/fail/panic/thread_local_drop_panic.rs
+++ b/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.rs
diff --git a/src/tools/miri/tests/fail/panic/thread_local_drop_panic.stderr b/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.stderr
index 3d6c41faabc..b1a384bbb52 100644
--- a/src/tools/miri/tests/fail/panic/thread_local_drop_panic.stderr
+++ b/src/tools/miri/tests/fail/panic/tls_macro_drop_panic.stderr
@@ -1,4 +1,4 @@
-thread $NAME panicked at $DIR/thread_local_drop_panic.rs:LL:CC:
+thread $NAME panicked at $DIR/tls_macro_drop_panic.rs:LL:CC:
 ow
 fatal runtime error: thread local panicked on drop
 error: abnormal termination: the program aborted execution
diff --git a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs b/src/tools/miri/tests/fail/tls/tls_static_dealloc.rs
index d5e6d37226a..d5e6d37226a 100644
--- a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.rs
+++ b/src/tools/miri/tests/fail/tls/tls_static_dealloc.rs
diff --git a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr b/src/tools/miri/tests/fail/tls/tls_static_dealloc.stderr
index 7069e8cccfe..ae8a421ca40 100644
--- a/src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr
+++ b/src/tools/miri/tests/fail/tls/tls_static_dealloc.stderr
@@ -1,5 +1,5 @@
 error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
-  --> $DIR/thread_local_static_dealloc.rs:LL:CC
+  --> $DIR/tls_static_dealloc.rs:LL:CC
    |
 LL |         let _val = *dangling_ptr.0;
    |                    ^^^^^^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling
@@ -7,7 +7,7 @@ LL |         let _val = *dangling_ptr.0;
    = 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:
-   = note: inside `main` at $DIR/thread_local_static_dealloc.rs:LL:CC
+   = note: inside `main` at $DIR/tls_static_dealloc.rs:LL:CC
 
 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
 
diff --git a/src/tools/miri/tests/fail/tls_macro_leak.rs b/src/tools/miri/tests/fail/tls_macro_leak.rs
new file mode 100644
index 00000000000..996d7ed4a23
--- /dev/null
+++ b/src/tools/miri/tests/fail/tls_macro_leak.rs
@@ -0,0 +1,21 @@
+//@error-in-other-file: memory leaked
+//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
+
+use std::cell::Cell;
+
+pub fn main() {
+    thread_local! {
+        static TLS: Cell<Option<&'static i32>> = Cell::new(None);
+    }
+
+    std::thread::spawn(|| {
+        TLS.with(|cell| {
+            cell.set(Some(Box::leak(Box::new(123))));
+        });
+    })
+    .join()
+    .unwrap();
+
+    // Imagine the program running for a long time while the thread is gone
+    // and this memory still sits around, unused -- leaked.
+}
diff --git a/src/tools/miri/tests/fail/tls_macro_leak.stderr b/src/tools/miri/tests/fail/tls_macro_leak.stderr
new file mode 100644
index 00000000000..e9daa78543c
--- /dev/null
+++ b/src/tools/miri/tests/fail/tls_macro_leak.stderr
@@ -0,0 +1,32 @@
+error: memory leaked: ALLOC (Rust heap, size: 4, align: 4), allocated here:
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |         __rust_alloc(layout.size(), layout.align())
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `<std::alloc::Global as std::alloc::Allocator>::allocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `alloc::alloc::exchange_malloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `std::boxed::Box::<i32>::new` at RUSTLIB/alloc/src/boxed.rs:LL:CC
+note: inside closure
+  --> $DIR/tls_macro_leak.rs:LL:CC
+   |
+LL |             cell.set(Some(Box::leak(Box::new(123))));
+   |                                     ^^^^^^^^^^^^^
+   = note: inside `std::thread::LocalKey::<std::cell::Cell<std::option::Option<&i32>>>::try_with::<{closure@$DIR/tls_macro_leak.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/local.rs:LL:CC
+   = note: inside `std::thread::LocalKey::<std::cell::Cell<std::option::Option<&i32>>>::with::<{closure@$DIR/tls_macro_leak.rs:LL:CC}, ()>` at RUSTLIB/std/src/thread/local.rs:LL:CC
+note: inside closure
+  --> $DIR/tls_macro_leak.rs:LL:CC
+   |
+LL | /         TLS.with(|cell| {
+LL | |             cell.set(Some(Box::leak(Box::new(123))));
+LL | |         });
+   | |__________^
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/tls_static_leak.rs b/src/tools/miri/tests/fail/tls_static_leak.rs
new file mode 100644
index 00000000000..637d648fb3f
--- /dev/null
+++ b/src/tools/miri/tests/fail/tls_static_leak.rs
@@ -0,0 +1,22 @@
+//@error-in-other-file: memory leaked
+//@normalize-stderr-test: ".*│.*" -> "$$stripped$$"
+
+#![feature(thread_local)]
+
+use std::cell::Cell;
+
+/// Ensure that leaks through `thread_local` statics *not in the main thread*
+/// are detected.
+pub fn main() {
+    #[thread_local]
+    static TLS: Cell<Option<&'static i32>> = Cell::new(None);
+
+    std::thread::spawn(|| {
+        TLS.set(Some(Box::leak(Box::new(123))));
+    })
+    .join()
+    .unwrap();
+
+    // Imagine the program running for a long time while the thread is gone
+    // and this memory still sits around, unused -- leaked.
+}
diff --git a/src/tools/miri/tests/fail/tls_static_leak.stderr b/src/tools/miri/tests/fail/tls_static_leak.stderr
new file mode 100644
index 00000000000..bcfaf80229a
--- /dev/null
+++ b/src/tools/miri/tests/fail/tls_static_leak.stderr
@@ -0,0 +1,23 @@
+error: memory leaked: ALLOC (Rust heap, size: 4, align: 4), allocated here:
+  --> RUSTLIB/alloc/src/alloc.rs:LL:CC
+   |
+LL |         __rust_alloc(layout.size(), layout.align())
+   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: inside `std::alloc::alloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `std::alloc::Global::alloc_impl` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `<std::alloc::Global as std::alloc::Allocator>::allocate` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `alloc::alloc::exchange_malloc` at RUSTLIB/alloc/src/alloc.rs:LL:CC
+   = note: inside `std::boxed::Box::<i32>::new` at RUSTLIB/alloc/src/boxed.rs:LL:CC
+note: inside closure
+  --> $DIR/tls_static_leak.rs:LL:CC
+   |
+LL |         TLS.set(Some(Box::leak(Box::new(123))));
+   |                                ^^^^^^^^^^^^^
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+note: the evaluated program leaked memory, pass `-Zmiri-ignore-leaks` to disable this check
+
+error: aborting due to previous error
+
diff --git a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr
index 03b5a4e4c17..d35970205f2 100644
--- a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr
+++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size.stderr
@@ -10,6 +10,7 @@ help: and (1) occurred earlier here
 LL |         x.store(1, Relaxed);
    |         ^^^^^^^^^^^^^^^^^^^
    = help: overlapping unsynchronized atomic accesses must use the same access size
+   = help: 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
    = 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):
diff --git a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr
index 05eba41f4d5..e85d76951b6 100644
--- a/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr
+++ b/src/tools/miri/tests/fail/weak_memory/racing_mixed_size_read.stderr
@@ -10,6 +10,7 @@ help: and (1) occurred earlier here
 LL |         x.load(Relaxed);
    |         ^^^^^^^^^^^^^^^
    = help: overlapping unsynchronized atomic accesses must use the same access size
+   = help: 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
    = 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):
diff --git a/src/tools/miri/tests/pass-dep/shims/pthreads.rs b/src/tools/miri/tests/pass-dep/shims/pthreads.rs
index 80b8d67401a..3bb6f83ec2a 100644
--- a/src/tools/miri/tests/pass-dep/shims/pthreads.rs
+++ b/src/tools/miri/tests/pass-dep/shims/pthreads.rs
@@ -1,18 +1,26 @@
 //@ignore-target-windows: No libc on Windows
-use std::ffi::{CStr, CString};
+use std::ffi::CStr;
+#[cfg(not(target_os = "freebsd"))]
+use std::ffi::CString;
 use std::thread;
 
 fn main() {
+    test_named_thread_truncation();
+
+    #[cfg(not(target_os = "freebsd"))]
     test_mutex_libc_init_recursive();
+    #[cfg(not(target_os = "freebsd"))]
     test_mutex_libc_init_normal();
+    #[cfg(not(target_os = "freebsd"))]
     test_mutex_libc_init_errorcheck();
+    #[cfg(not(target_os = "freebsd"))]
     test_rwlock_libc_static_initializer();
-    test_named_thread_truncation();
 
     #[cfg(target_os = "linux")]
     test_mutex_libc_static_initializer_recursive();
 }
 
+#[cfg(not(target_os = "freebsd"))]
 fn test_mutex_libc_init_recursive() {
     unsafe {
         let mut attr: libc::pthread_mutexattr_t = std::mem::zeroed();
@@ -37,6 +45,7 @@ fn test_mutex_libc_init_recursive() {
     }
 }
 
+#[cfg(not(target_os = "freebsd"))]
 fn test_mutex_libc_init_normal() {
     unsafe {
         let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed();
@@ -59,6 +68,7 @@ fn test_mutex_libc_init_normal() {
     }
 }
 
+#[cfg(not(target_os = "freebsd"))]
 fn test_mutex_libc_init_errorcheck() {
     unsafe {
         let mut mutexattr: libc::pthread_mutexattr_t = std::mem::zeroed();
@@ -104,6 +114,7 @@ fn test_mutex_libc_static_initializer_recursive() {
 // Testing the behavior of std::sync::RwLock does not fully exercise the pthread rwlock shims, we
 // need to go a layer deeper and test the behavior of the libc functions, because
 // std::sys::unix::rwlock::RWLock itself keeps track of write_locked and num_readers.
+#[cfg(not(target_os = "freebsd"))]
 fn test_rwlock_libc_static_initializer() {
     let rw = std::cell::UnsafeCell::new(libc::PTHREAD_RWLOCK_INITIALIZER);
     unsafe {
@@ -137,6 +148,12 @@ fn test_named_thread_truncation() {
     fn set_thread_name(name: &CStr) -> i32 {
         #[cfg(target_os = "linux")]
         return unsafe { libc::pthread_setname_np(libc::pthread_self(), name.as_ptr().cast()) };
+        #[cfg(target_os = "freebsd")]
+        unsafe {
+            // pthread_set_name_np does not return anything
+            libc::pthread_set_name_np(libc::pthread_self(), name.as_ptr().cast());
+            return 0;
+        };
         #[cfg(target_os = "macos")]
         return unsafe { libc::pthread_setname_np(name.as_ptr().cast()) };
     }
@@ -147,16 +164,23 @@ fn test_named_thread_truncation() {
 
         // But the system is limited -- make sure we successfully set a truncation.
         let mut buf = vec![0u8; long_name.len() + 1];
+        #[cfg(not(target_os = "freebsd"))]
         unsafe {
             libc::pthread_getname_np(libc::pthread_self(), buf.as_mut_ptr().cast(), buf.len())
         };
+        #[cfg(target_os = "freebsd")]
+        unsafe {
+            libc::pthread_get_name_np(libc::pthread_self(), buf.as_mut_ptr().cast(), buf.len())
+        };
         let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
-        assert!(cstr.to_bytes().len() >= 15); // POSIX seems to promise at least 15 chars
+        assert!(cstr.to_bytes().len() >= 15, "name is too short: len={}", cstr.to_bytes().len()); // POSIX seems to promise at least 15 chars
         assert!(long_name.as_bytes().starts_with(cstr.to_bytes()));
 
         // Also test directly calling pthread_setname to check its return value.
         assert_eq!(set_thread_name(&cstr), 0);
-        // But with a too long name it should fail.
+        // But with a too long name it should fail (except on FreeBSD where the
+        // function has no return, hence cannot indicate failure).
+        #[cfg(not(target_os = "freebsd"))]
         assert_ne!(set_thread_name(&CString::new(long_name).unwrap()), 0);
     });
     result.unwrap().join().unwrap();
diff --git a/src/tools/miri/tests/pass/intrinsics-x86-sse41.rs b/src/tools/miri/tests/pass/intrinsics-x86-sse41.rs
index d5489ffaf4b..8c565a2d6e0 100644
--- a/src/tools/miri/tests/pass/intrinsics-x86-sse41.rs
+++ b/src/tools/miri/tests/pass/intrinsics-x86-sse41.rs
@@ -73,114 +73,342 @@ unsafe fn test_sse41() {
     test_mm_dp_ps();
 
     #[target_feature(enable = "sse4.1")]
-    unsafe fn test_mm_floor_sd() {
-        let a = _mm_setr_pd(2.5, 4.5);
-        let b = _mm_setr_pd(-1.5, -3.5);
-        let r = _mm_floor_sd(a, b);
-        let e = _mm_setr_pd(-2.0, 4.5);
-        assert_eq_m128d(r, e);
+    unsafe fn test_round_nearest_f32() {
+        #[target_feature(enable = "sse4.1")]
+        unsafe fn test(x: f32, res: f32) {
+            let a = _mm_setr_ps(3.5, 2.5, 1.5, 4.5);
+            let b = _mm_setr_ps(x, -1.5, -3.5, -2.5);
+            let e = _mm_setr_ps(res, 2.5, 1.5, 4.5);
+            let r = _mm_round_ss::<_MM_FROUND_TO_NEAREST_INT>(a, b);
+            assert_eq_m128(r, e);
+            // Assume round-to-nearest by default
+            let r = _mm_round_ss::<_MM_FROUND_CUR_DIRECTION>(a, b);
+            assert_eq_m128(r, e);
+
+            let a = _mm_set1_ps(x);
+            let e = _mm_set1_ps(res);
+            let r = _mm_round_ps::<_MM_FROUND_TO_NEAREST_INT>(a);
+            assert_eq_m128(r, e);
+            // Assume round-to-nearest by default
+            let r = _mm_round_ps::<_MM_FROUND_CUR_DIRECTION>(a);
+            assert_eq_m128(r, e);
+        }
+
+        // Test rounding direction
+        test(-2.5, -2.0);
+        test(-1.75, -2.0);
+        test(-1.5, -2.0);
+        test(-1.25, -1.0);
+        test(-1.0, -1.0);
+        test(0.0, 0.0);
+        test(1.0, 1.0);
+        test(1.25, 1.0);
+        test(1.5, 2.0);
+        test(1.75, 2.0);
+        test(2.5, 2.0);
+
+        // Test that each element is rounded
+        let a = _mm_setr_ps(1.5, 3.5, 5.5, 7.5);
+        let e = _mm_setr_ps(2.0, 4.0, 6.0, 8.0);
+        let r = _mm_round_ps::<_MM_FROUND_TO_NEAREST_INT>(a);
+        assert_eq_m128(r, e);
+        // Assume round-to-nearest by default
+        let r = _mm_round_ps::<_MM_FROUND_CUR_DIRECTION>(a);
+        assert_eq_m128(r, e);
     }
-    test_mm_floor_sd();
+    test_round_nearest_f32();
 
     #[target_feature(enable = "sse4.1")]
-    unsafe fn test_mm_floor_ss() {
-        let a = _mm_setr_ps(2.5, 4.5, 8.5, 16.5);
-        let b = _mm_setr_ps(-1.5, -3.5, -7.5, -15.5);
-        let r = _mm_floor_ss(a, b);
-        let e = _mm_setr_ps(-2.0, 4.5, 8.5, 16.5);
+    unsafe fn test_round_floor_f32() {
+        #[target_feature(enable = "sse4.1")]
+        unsafe fn test(x: f32, res: f32) {
+            let a = _mm_setr_ps(3.5, 2.5, 1.5, 4.5);
+            let b = _mm_setr_ps(x, -1.5, -3.5, -2.5);
+            let e = _mm_setr_ps(res, 2.5, 1.5, 4.5);
+            let r = _mm_floor_ss(a, b);
+            assert_eq_m128(r, e);
+            let r = _mm_round_ss::<_MM_FROUND_TO_NEG_INF>(a, b);
+            assert_eq_m128(r, e);
+
+            let a = _mm_set1_ps(x);
+            let e = _mm_set1_ps(res);
+            let r = _mm_floor_ps(a);
+            assert_eq_m128(r, e);
+            let r = _mm_round_ps::<_MM_FROUND_TO_NEG_INF>(a);
+            assert_eq_m128(r, e);
+        }
+
+        // Test rounding direction
+        test(-2.5, -3.0);
+        test(-1.75, -2.0);
+        test(-1.5, -2.0);
+        test(-1.25, -2.0);
+        test(-1.0, -1.0);
+        test(0.0, 0.0);
+        test(1.0, 1.0);
+        test(1.25, 1.0);
+        test(1.5, 1.0);
+        test(1.75, 1.0);
+        test(2.5, 2.0);
+
+        // Test that each element is rounded
+        let a = _mm_setr_ps(1.5, 3.5, 5.5, 7.5);
+        let e = _mm_setr_ps(1.0, 3.0, 5.0, 7.0);
+        let r = _mm_floor_ps(a);
+        assert_eq_m128(r, e);
+        let r = _mm_round_ps::<_MM_FROUND_TO_NEG_INF>(a);
         assert_eq_m128(r, e);
     }
-    test_mm_floor_ss();
+    test_round_floor_f32();
 
     #[target_feature(enable = "sse4.1")]
-    unsafe fn test_mm_ceil_sd() {
-        let a = _mm_setr_pd(1.5, 3.5);
-        let b = _mm_setr_pd(-2.5, -4.5);
-        let r = _mm_ceil_sd(a, b);
-        let e = _mm_setr_pd(-2.0, 3.5);
-        assert_eq_m128d(r, e);
+    unsafe fn test_round_ceil_f32() {
+        #[target_feature(enable = "sse4.1")]
+        unsafe fn test(x: f32, res: f32) {
+            let a = _mm_setr_ps(3.5, 2.5, 1.5, 4.5);
+            let b = _mm_setr_ps(x, -1.5, -3.5, -2.5);
+            let e = _mm_setr_ps(res, 2.5, 1.5, 4.5);
+            let r = _mm_ceil_ss(a, b);
+            assert_eq_m128(r, e);
+            let r = _mm_round_ss::<_MM_FROUND_TO_POS_INF>(a, b);
+            assert_eq_m128(r, e);
+
+            let a = _mm_set1_ps(x);
+            let e = _mm_set1_ps(res);
+            let r = _mm_ceil_ps(a);
+            assert_eq_m128(r, e);
+            let r = _mm_round_ps::<_MM_FROUND_TO_POS_INF>(a);
+            assert_eq_m128(r, e);
+        }
+
+        // Test rounding direction
+        test(-2.5, -2.0);
+        test(-1.75, -1.0);
+        test(-1.5, -1.0);
+        test(-1.25, -1.0);
+        test(-1.0, -1.0);
+        test(0.0, 0.0);
+        test(1.0, 1.0);
+        test(1.25, 2.0);
+        test(1.5, 2.0);
+        test(1.75, 2.0);
+        test(2.5, 3.0);
+
+        // Test that each element is rounded
+        let a = _mm_setr_ps(1.5, 3.5, 5.5, 7.5);
+        let e = _mm_setr_ps(2.0, 4.0, 6.0, 8.0);
+        let r = _mm_ceil_ps(a);
+        assert_eq_m128(r, e);
+        let r = _mm_round_ps::<_MM_FROUND_TO_POS_INF>(a);
+        assert_eq_m128(r, e);
     }
-    test_mm_ceil_sd();
+    test_round_ceil_f32();
 
     #[target_feature(enable = "sse4.1")]
-    unsafe fn test_mm_ceil_ss() {
-        let a = _mm_setr_ps(1.5, 3.5, 7.5, 15.5);
-        let b = _mm_setr_ps(-2.5, -4.5, -8.5, -16.5);
-        let r = _mm_ceil_ss(a, b);
-        let e = _mm_setr_ps(-2.0, 3.5, 7.5, 15.5);
+    unsafe fn test_round_trunc_f32() {
+        #[target_feature(enable = "sse4.1")]
+        unsafe fn test(x: f32, res: f32) {
+            let a = _mm_setr_ps(3.5, 2.5, 1.5, 4.5);
+            let b = _mm_setr_ps(x, -1.5, -3.5, -2.5);
+            let e = _mm_setr_ps(res, 2.5, 1.5, 4.5);
+            let r = _mm_round_ss::<_MM_FROUND_TO_ZERO>(a, b);
+            assert_eq_m128(r, e);
+
+            let a = _mm_set1_ps(x);
+            let e = _mm_set1_ps(res);
+            let r = _mm_round_ps::<_MM_FROUND_TO_ZERO>(a);
+            assert_eq_m128(r, e);
+        }
+
+        // Test rounding direction
+        test(-2.5, -2.0);
+        test(-1.75, -1.0);
+        test(-1.5, -1.0);
+        test(-1.25, -1.0);
+        test(-1.0, -1.0);
+        test(0.0, 0.0);
+        test(1.0, 1.0);
+        test(1.25, 1.0);
+        test(1.5, 1.0);
+        test(1.75, 1.0);
+        test(2.5, 2.0);
+
+        // Test that each element is rounded
+        let a = _mm_setr_ps(1.5, 3.5, 5.5, 7.5);
+        let e = _mm_setr_ps(1.0, 3.0, 5.0, 7.0);
+        let r = _mm_round_ps::<_MM_FROUND_TO_ZERO>(a);
         assert_eq_m128(r, e);
     }
-    test_mm_ceil_ss();
+    test_round_trunc_f32();
 
     #[target_feature(enable = "sse4.1")]
-    unsafe fn test_mm_round_sd() {
+    unsafe fn test_round_nearest_f64() {
+        #[target_feature(enable = "sse4.1")]
+        unsafe fn test(x: f64, res: f64) {
+            let a = _mm_setr_pd(3.5, 2.5);
+            let b = _mm_setr_pd(x, -1.5);
+            let e = _mm_setr_pd(res, 2.5);
+            let r = _mm_round_sd::<_MM_FROUND_TO_NEAREST_INT>(a, b);
+            assert_eq_m128d(r, e);
+            // Assume round-to-nearest by default
+            let r = _mm_round_sd::<_MM_FROUND_CUR_DIRECTION>(a, b);
+            assert_eq_m128d(r, e);
+
+            let a = _mm_set1_pd(x);
+            let e = _mm_set1_pd(res);
+            let r = _mm_round_pd::<_MM_FROUND_TO_NEAREST_INT>(a);
+            assert_eq_m128d(r, e);
+            // Assume round-to-nearest by default
+            let r = _mm_round_pd::<_MM_FROUND_CUR_DIRECTION>(a);
+            assert_eq_m128d(r, e);
+        }
+
+        // Test rounding direction
+        test(-2.5, -2.0);
+        test(-1.75, -2.0);
+        test(-1.5, -2.0);
+        test(-1.25, -1.0);
+        test(-1.0, -1.0);
+        test(0.0, 0.0);
+        test(1.0, 1.0);
+        test(1.25, 1.0);
+        test(1.5, 2.0);
+        test(1.75, 2.0);
+        test(2.5, 2.0);
+
+        // Test that each element is rounded
         let a = _mm_setr_pd(1.5, 3.5);
-        let b = _mm_setr_pd(-2.5, -4.5);
-        let r = _mm_round_sd::<_MM_FROUND_TO_NEAREST_INT>(a, b);
-        let e = _mm_setr_pd(-2.0, 3.5);
+        let e = _mm_setr_pd(2.0, 4.0);
+        let r = _mm_round_pd::<_MM_FROUND_TO_NEAREST_INT>(a);
         assert_eq_m128d(r, e);
-
-        let a = _mm_setr_pd(1.5, 3.5);
-        let b = _mm_setr_pd(-2.5, -4.5);
-        let r = _mm_round_sd::<_MM_FROUND_TO_NEG_INF>(a, b);
-        let e = _mm_setr_pd(-3.0, 3.5);
+        // Assume round-to-nearest by default
+        let r = _mm_round_pd::<_MM_FROUND_CUR_DIRECTION>(a);
         assert_eq_m128d(r, e);
+    }
+    test_round_nearest_f64();
 
+    #[target_feature(enable = "sse4.1")]
+    unsafe fn test_round_floor_f64() {
+        #[target_feature(enable = "sse4.1")]
+        unsafe fn test(x: f64, res: f64) {
+            let a = _mm_setr_pd(3.5, 2.5);
+            let b = _mm_setr_pd(x, -1.5);
+            let e = _mm_setr_pd(res, 2.5);
+            let r = _mm_floor_sd(a, b);
+            assert_eq_m128d(r, e);
+            let r = _mm_round_sd::<_MM_FROUND_TO_NEG_INF>(a, b);
+            assert_eq_m128d(r, e);
+
+            let a = _mm_set1_pd(x);
+            let e = _mm_set1_pd(res);
+            let r = _mm_floor_pd(a);
+            assert_eq_m128d(r, e);
+            let r = _mm_round_pd::<_MM_FROUND_TO_NEG_INF>(a);
+            assert_eq_m128d(r, e);
+        }
+
+        // Test rounding direction
+        test(-2.5, -3.0);
+        test(-1.75, -2.0);
+        test(-1.5, -2.0);
+        test(-1.25, -2.0);
+        test(-1.0, -1.0);
+        test(0.0, 0.0);
+        test(1.0, 1.0);
+        test(1.25, 1.0);
+        test(1.5, 1.0);
+        test(1.75, 1.0);
+        test(2.5, 2.0);
+
+        // Test that each element is rounded
         let a = _mm_setr_pd(1.5, 3.5);
-        let b = _mm_setr_pd(-2.5, -4.5);
-        let r = _mm_round_sd::<_MM_FROUND_TO_POS_INF>(a, b);
-        let e = _mm_setr_pd(-2.0, 3.5);
+        let e = _mm_setr_pd(1.0, 3.0);
+        let r = _mm_floor_pd(a);
         assert_eq_m128d(r, e);
-
-        let a = _mm_setr_pd(1.5, 3.5);
-        let b = _mm_setr_pd(-2.5, -4.5);
-        let r = _mm_round_sd::<_MM_FROUND_TO_ZERO>(a, b);
-        let e = _mm_setr_pd(-2.0, 3.5);
+        let r = _mm_round_pd::<_MM_FROUND_TO_NEG_INF>(a);
         assert_eq_m128d(r, e);
+    }
+    test_round_floor_f64();
 
-        // Assume round-to-nearest by default
+    #[target_feature(enable = "sse4.1")]
+    unsafe fn test_round_ceil_f64() {
+        #[target_feature(enable = "sse4.1")]
+        unsafe fn test(x: f64, res: f64) {
+            let a = _mm_setr_pd(3.5, 2.5);
+            let b = _mm_setr_pd(x, -1.5);
+            let e = _mm_setr_pd(res, 2.5);
+            let r = _mm_ceil_sd(a, b);
+            assert_eq_m128d(r, e);
+            let r = _mm_round_sd::<_MM_FROUND_TO_POS_INF>(a, b);
+            assert_eq_m128d(r, e);
+
+            let a = _mm_set1_pd(x);
+            let e = _mm_set1_pd(res);
+            let r = _mm_ceil_pd(a);
+            assert_eq_m128d(r, e);
+            let r = _mm_round_pd::<_MM_FROUND_TO_POS_INF>(a);
+            assert_eq_m128d(r, e);
+        }
+
+        // Test rounding direction
+        test(-2.5, -2.0);
+        test(-1.75, -1.0);
+        test(-1.5, -1.0);
+        test(-1.25, -1.0);
+        test(-1.0, -1.0);
+        test(0.0, 0.0);
+        test(1.0, 1.0);
+        test(1.25, 2.0);
+        test(1.5, 2.0);
+        test(1.75, 2.0);
+        test(2.5, 3.0);
+
+        // Test that each element is rounded
         let a = _mm_setr_pd(1.5, 3.5);
-        let b = _mm_setr_pd(-2.5, -4.5);
-        let r = _mm_round_sd::<_MM_FROUND_CUR_DIRECTION>(a, b);
-        let e = _mm_setr_pd(-2.0, 3.5);
+        let e = _mm_setr_pd(2.0, 4.0);
+        let r = _mm_ceil_pd(a);
+        assert_eq_m128d(r, e);
+        let r = _mm_round_pd::<_MM_FROUND_TO_POS_INF>(a);
         assert_eq_m128d(r, e);
     }
-    test_mm_round_sd();
+    test_round_ceil_f64();
 
     #[target_feature(enable = "sse4.1")]
-    unsafe fn test_mm_round_ss() {
-        let a = _mm_setr_ps(1.5, 3.5, 7.5, 15.5);
-        let b = _mm_setr_ps(-1.75, -4.5, -8.5, -16.5);
-        let r = _mm_round_ss::<_MM_FROUND_TO_NEAREST_INT>(a, b);
-        let e = _mm_setr_ps(-2.0, 3.5, 7.5, 15.5);
-        assert_eq_m128(r, e);
-
-        let a = _mm_setr_ps(1.5, 3.5, 7.5, 15.5);
-        let b = _mm_setr_ps(-1.75, -4.5, -8.5, -16.5);
-        let r = _mm_round_ss::<_MM_FROUND_TO_NEG_INF>(a, b);
-        let e = _mm_setr_ps(-2.0, 3.5, 7.5, 15.5);
-        assert_eq_m128(r, e);
-
-        let a = _mm_setr_ps(1.5, 3.5, 7.5, 15.5);
-        let b = _mm_setr_ps(-1.75, -4.5, -8.5, -16.5);
-        let r = _mm_round_ss::<_MM_FROUND_TO_POS_INF>(a, b);
-        let e = _mm_setr_ps(-1.0, 3.5, 7.5, 15.5);
-        assert_eq_m128(r, e);
-
-        let a = _mm_setr_ps(1.5, 3.5, 7.5, 15.5);
-        let b = _mm_setr_ps(-1.75, -4.5, -8.5, -16.5);
-        let r = _mm_round_ss::<_MM_FROUND_TO_ZERO>(a, b);
-        let e = _mm_setr_ps(-1.0, 3.5, 7.5, 15.5);
-        assert_eq_m128(r, e);
-
-        // Assume round-to-nearest by default
-        let a = _mm_setr_ps(1.5, 3.5, 7.5, 15.5);
-        let b = _mm_setr_ps(-1.75, -4.5, -8.5, -16.5);
-        let r = _mm_round_ss::<_MM_FROUND_CUR_DIRECTION>(a, b);
-        let e = _mm_setr_ps(-2.0, 3.5, 7.5, 15.5);
-        assert_eq_m128(r, e);
+    unsafe fn test_round_trunc_f64() {
+        #[target_feature(enable = "sse4.1")]
+        unsafe fn test(x: f64, res: f64) {
+            let a = _mm_setr_pd(3.5, 2.5);
+            let b = _mm_setr_pd(x, -1.5);
+            let e = _mm_setr_pd(res, 2.5);
+            let r = _mm_round_sd::<_MM_FROUND_TO_ZERO>(a, b);
+            assert_eq_m128d(r, e);
+
+            let a = _mm_set1_pd(x);
+            let e = _mm_set1_pd(res);
+            let r = _mm_round_pd::<_MM_FROUND_TO_ZERO>(a);
+            assert_eq_m128d(r, e);
+        }
+
+        // Test rounding direction
+        test(-2.5, -2.0);
+        test(-1.75, -1.0);
+        test(-1.5, -1.0);
+        test(-1.25, -1.0);
+        test(-1.0, -1.0);
+        test(0.0, 0.0);
+        test(1.0, 1.0);
+        test(1.25, 1.0);
+        test(1.5, 1.0);
+        test(1.75, 1.0);
+        test(2.5, 2.0);
+
+        // Test that each element is rounded
+        let a = _mm_setr_pd(1.5, 3.5);
+        let e = _mm_setr_pd(1.0, 3.0);
+        let r = _mm_round_pd::<_MM_FROUND_TO_ZERO>(a);
+        assert_eq_m128d(r, e);
     }
-    test_mm_round_ss();
+    test_round_trunc_f64();
 
     #[target_feature(enable = "sse4.1")]
     unsafe fn test_mm_minpos_epu16() {
diff --git a/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs b/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs
new file mode 100644
index 00000000000..7732e3f9217
--- /dev/null
+++ b/src/tools/miri/tests/pass/tls/tls_leak_main_thread_allowed.rs
@@ -0,0 +1,23 @@
+//@ignore-target-windows: Windows uses a different mechanism for `thread_local!`
+#![feature(thread_local)]
+
+use std::cell::Cell;
+
+// Thread-local variables in the main thread are basically like `static` (they live
+// as long as the program does), so make sure we treat them the same for leak purposes.
+//
+// The test covers both TLS statics and the TLS macro.
+pub fn main() {
+    thread_local! {
+        static TLS_KEY: Cell<Option<&'static i32>> = Cell::new(None);
+    }
+
+    TLS_KEY.with(|cell| {
+        cell.set(Some(Box::leak(Box::new(123))));
+    });
+
+    #[thread_local]
+    static TLS: Cell<Option<&'static i32>> = Cell::new(None);
+
+    TLS.set(Some(Box::leak(Box::new(123))));
+}
diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.rs b/src/tools/miri/tests/pass/tls/tls_macro_drop.rs
index bd06eec9cd5..bd06eec9cd5 100644
--- a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.rs
+++ b/src/tools/miri/tests/pass/tls/tls_macro_drop.rs
diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.stack.stdout b/src/tools/miri/tests/pass/tls/tls_macro_drop.stack.stdout
index b7877820a0c..b7877820a0c 100644
--- a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.stack.stdout
+++ b/src/tools/miri/tests/pass/tls/tls_macro_drop.stack.stdout
diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.tree.stdout b/src/tools/miri/tests/pass/tls/tls_macro_drop.tree.stdout
index b7877820a0c..b7877820a0c 100644
--- a/src/tools/miri/tests/pass/concurrency/tls_lib_drop.tree.stdout
+++ b/src/tools/miri/tests/pass/tls/tls_macro_drop.tree.stdout
diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.rs b/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.rs
index 2766ba36d12..f36c460ae53 100644
--- a/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.rs
+++ b/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.rs
@@ -1,4 +1,5 @@
-//! Check that destructors of the thread locals are executed on all OSes.
+//! Check that destructors of the thread locals are executed on all OSes
+//! (even when we do not support concurrency, and cannot run the other test).
 
 use std::cell::RefCell;
 
diff --git a/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.stderr b/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.stderr
index 09ec1c3c2c5..09ec1c3c2c5 100644
--- a/src/tools/miri/tests/pass/concurrency/tls_lib_drop_single_thread.stderr
+++ b/src/tools/miri/tests/pass/tls/tls_macro_drop_single_thread.stderr
diff --git a/src/tools/miri/tests/pass/concurrency/thread_locals.rs b/src/tools/miri/tests/pass/tls/tls_static.rs
index fc4c8a283dd..fc4c8a283dd 100644
--- a/src/tools/miri/tests/pass/concurrency/thread_locals.rs
+++ b/src/tools/miri/tests/pass/tls/tls_static.rs
diff --git a/tests/ui/parser/brace-in-let-chain.rs b/tests/ui/parser/brace-in-let-chain.rs
new file mode 100644
index 00000000000..1f34c73a2c3
--- /dev/null
+++ b/tests/ui/parser/brace-in-let-chain.rs
@@ -0,0 +1,58 @@
+// issue #117766
+
+#![feature(let_chains)]
+fn main() {
+    if let () = ()
+        && let () = () { //~ERROR: found a `{` in the middle of a let-chain
+        && let () = ()
+    {
+    }
+}
+
+fn quux() {
+    while let () = ()
+        && let () = () { //~ERROR: found a `{` in the middle of a let-chain
+        && let () = ()
+    {
+    }
+}
+
+fn foobar() {
+    while false {}
+    {
+        && let () = ()
+}
+
+fn fubar() {
+    while false {
+        {
+            && let () = ()
+    }
+}
+
+fn qux() {
+    let foo = false;
+    match foo {
+        _ if foo => {
+            && let () = ()
+        _ => {}
+    }
+}
+
+fn foo() {
+    {
+    && let () = ()
+}
+
+fn bar() {
+    if false {}
+    {
+        && let () = ()
+}
+
+fn baz() {
+    if false {
+        {
+            && let () = ()
+    }
+} //~ERROR: this file contains an unclosed delimiter
diff --git a/tests/ui/parser/brace-in-let-chain.stderr b/tests/ui/parser/brace-in-let-chain.stderr
new file mode 100644
index 00000000000..7182d86d001
--- /dev/null
+++ b/tests/ui/parser/brace-in-let-chain.stderr
@@ -0,0 +1,65 @@
+error: this file contains an unclosed delimiter
+  --> $DIR/brace-in-let-chain.rs:58:54
+   |
+LL | fn main() {
+   |           - unclosed delimiter
+...
+LL | fn quux() {
+   |           - unclosed delimiter
+...
+LL | fn foobar() {
+   |             - unclosed delimiter
+...
+LL | fn fubar() {
+   |            - unclosed delimiter
+...
+LL | fn qux() {
+   |          - unclosed delimiter
+...
+LL | fn foo() {
+   |          - unclosed delimiter
+...
+LL | fn bar() {
+   |          - unclosed delimiter
+...
+LL | fn baz() {
+   |          - unclosed delimiter
+LL |     if false {
+LL |         {
+   |         - this delimiter might not be properly closed...
+LL |             && let () = ()
+LL |     }
+   |     - ...as it matches this but it has different indentation
+LL | }
+   |                                                      ^
+
+error: found a `{` in the middle of a let-chain
+  --> $DIR/brace-in-let-chain.rs:14:24
+   |
+LL |         && let () = () {
+   |                        ^
+LL |         && let () = ()
+   |         ------ you might have meant to continue the let-chain here
+   |
+help: consider removing this brace to parse the `let` as part of the same chain
+   |
+LL -         && let () = () {
+LL +         && let () = ()
+   |
+
+error: found a `{` in the middle of a let-chain
+  --> $DIR/brace-in-let-chain.rs:6:24
+   |
+LL |         && let () = () {
+   |                        ^
+LL |         && let () = ()
+   |         ------ you might have meant to continue the let-chain here
+   |
+help: consider removing this brace to parse the `let` as part of the same chain
+   |
+LL -         && let () = () {
+LL +         && let () = ()
+   |
+
+error: aborting due to 3 previous errors
+