diff --git a/compiler/rustc_driver_impl/src/args.rs b/compiler/rustc_driver_impl/src/args.rs
index 5dfd37a6da4de..f645e7138e076 100644
--- a/compiler/rustc_driver_impl/src/args.rs
+++ b/compiler/rustc_driver_impl/src/args.rs
@@ -1,9 +1,7 @@
-use std::error;
-use std::fmt;
-use std::fs;
-use std::io;
+use std::{env, error, fmt, fs, io};
 
 use rustc_session::EarlyDiagCtxt;
+use rustc_span::ErrorGuaranteed;
 
 /// Expands argfiles in command line arguments.
 #[derive(Default)]
@@ -86,7 +84,7 @@ impl Expander {
     fn read_file(path: &str) -> Result<String, Error> {
         fs::read_to_string(path).map_err(|e| {
             if e.kind() == io::ErrorKind::InvalidData {
-                Error::Utf8Error(Some(path.to_string()))
+                Error::Utf8Error(path.to_string())
             } else {
                 Error::IOError(path.to_string(), e)
             }
@@ -94,22 +92,52 @@ impl Expander {
     }
 }
 
+/// Replaces any `@file` arguments with the contents of `file`, with each line of `file` as a
+/// separate argument.
+///
 /// **Note:** This function doesn't interpret argument 0 in any special way.
 /// If this function is intended to be used with command line arguments,
 /// `argv[0]` must be removed prior to calling it manually.
-pub fn arg_expand_all(early_dcx: &EarlyDiagCtxt, at_args: &[String]) -> Vec<String> {
+pub fn arg_expand_all(
+    early_dcx: &EarlyDiagCtxt,
+    at_args: &[String],
+) -> Result<Vec<String>, ErrorGuaranteed> {
     let mut expander = Expander::default();
+    let mut result = Ok(());
     for arg in at_args {
         if let Err(err) = expander.arg(arg) {
-            early_dcx.early_fatal(format!("Failed to load argument file: {err}"));
+            result = Err(early_dcx.early_err(format!("failed to load argument file: {err}")));
         }
     }
-    expander.finish()
+    result.map(|()| expander.finish())
+}
+
+/// Gets the raw unprocessed command-line arguments as Unicode strings, without doing any further
+/// processing (e.g., without `@file` expansion).
+///
+/// This function is identical to [`env::args()`] except that it emits an error when it encounters
+/// non-Unicode arguments instead of panicking.
+pub fn raw_args(early_dcx: &EarlyDiagCtxt) -> Result<Vec<String>, ErrorGuaranteed> {
+    let mut res = Ok(Vec::new());
+    for (i, arg) in env::args_os().enumerate() {
+        match arg.into_string() {
+            Ok(arg) => {
+                if let Ok(args) = &mut res {
+                    args.push(arg);
+                }
+            }
+            Err(arg) => {
+                res =
+                    Err(early_dcx.early_err(format!("argument {i} is not valid Unicode: {arg:?}")))
+            }
+        }
+    }
+    res
 }
 
 #[derive(Debug)]
-pub enum Error {
-    Utf8Error(Option<String>),
+enum Error {
+    Utf8Error(String),
     IOError(String, io::Error),
     ShellParseError(String),
 }
@@ -117,10 +145,9 @@ pub enum Error {
 impl fmt::Display for Error {
     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            Error::Utf8Error(None) => write!(fmt, "Utf8 error"),
-            Error::Utf8Error(Some(path)) => write!(fmt, "Utf8 error in {path}"),
-            Error::IOError(path, err) => write!(fmt, "IO Error: {path}: {err}"),
-            Error::ShellParseError(path) => write!(fmt, "Invalid shell-style arguments in {path}"),
+            Error::Utf8Error(path) => write!(fmt, "UTF-8 error in {path}"),
+            Error::IOError(path, err) => write!(fmt, "IO error: {path}: {err}"),
+            Error::ShellParseError(path) => write!(fmt, "invalid shell-style arguments in {path}"),
         }
     }
 }
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index 410e7eba30a9c..978087a1f5976 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -291,7 +291,7 @@ fn run_compiler(
     // the compiler with @empty_file as argv[0] and no more arguments.
     let at_args = at_args.get(1..).unwrap_or_default();
 
-    let args = args::arg_expand_all(&default_early_dcx, at_args);
+    let args = args::arg_expand_all(&default_early_dcx, at_args)?;
 
     let Some(matches) = handle_options(&default_early_dcx, &args) else { return Ok(()) };
 
@@ -1489,15 +1489,7 @@ pub fn main() -> ! {
     let mut callbacks = TimePassesCallbacks::default();
     let using_internal_features = install_ice_hook(DEFAULT_BUG_REPORT_URL, |_| ());
     let exit_code = catch_with_exit_code(|| {
-        let args = env::args_os()
-            .enumerate()
-            .map(|(i, arg)| {
-                arg.into_string().unwrap_or_else(|arg| {
-                    early_dcx.early_fatal(format!("argument {i} is not valid Unicode: {arg:?}"))
-                })
-            })
-            .collect::<Vec<_>>();
-        RunCompiler::new(&args, &mut callbacks)
+        RunCompiler::new(&args::raw_args(&early_dcx)?, &mut callbacks)
             .set_using_internal_features(using_internal_features)
             .run()
     });
diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs
index 2978491d646e8..f2cf6fe613c33 100644
--- a/compiler/rustc_mir_build/src/build/expr/into.rs
+++ b/compiler/rustc_mir_build/src/build/expr/into.rs
@@ -109,38 +109,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
                 this.cfg.goto(else_blk, source_info, join_block);
                 join_block.unit()
             }
-            ExprKind::Let { expr, ref pat } => {
-                let scope = this.local_scope();
-                let (true_block, false_block) = this.in_if_then_scope(scope, expr_span, |this| {
-                    this.lower_let_expr(block, expr, pat, scope, None, expr_span, true)
-                });
-
-                this.cfg.push_assign_constant(
-                    true_block,
-                    source_info,
-                    destination,
-                    ConstOperand {
-                        span: expr_span,
-                        user_ty: None,
-                        const_: Const::from_bool(this.tcx, true),
-                    },
-                );
-
-                this.cfg.push_assign_constant(
-                    false_block,
-                    source_info,
-                    destination,
-                    ConstOperand {
-                        span: expr_span,
-                        user_ty: None,
-                        const_: Const::from_bool(this.tcx, false),
-                    },
-                );
-
-                let join_block = this.cfg.start_new_block();
-                this.cfg.goto(true_block, source_info, join_block);
-                this.cfg.goto(false_block, source_info, join_block);
-                join_block.unit()
+            ExprKind::Let { .. } => {
+                // After desugaring, `let` expressions should only appear inside `if`
+                // expressions or `match` guards, possibly nested within a let-chain.
+                // In both cases they are specifically handled by the lowerings of
+                // those expressions, so this case is currently unreachable.
+                span_bug!(expr_span, "unexpected let expression outside of if or match-guard");
             }
             ExprKind::NeverToAny { source } => {
                 let source_expr = &this.thir[source];
diff --git a/library/core/src/error.rs b/library/core/src/error.rs
index ded17e69bd9c6..a3f2b767054e1 100644
--- a/library/core/src/error.rs
+++ b/library/core/src/error.rs
@@ -183,6 +183,7 @@ pub trait Error: Debug + Display {
     #[allow(unused_variables)]
     fn provide<'a>(&'a self, request: &mut Request<'a>) {}
 }
+
 mod private {
     // This is a hack to prevent `type_id` from being overridden by `Error`
     // implementations, since that can enable unsound downcasting.
diff --git a/library/core/src/ptr/mod.rs b/library/core/src/ptr/mod.rs
index fc5b08c9801a8..018efd4b9b34b 100644
--- a/library/core/src/ptr/mod.rs
+++ b/library/core/src/ptr/mod.rs
@@ -2071,11 +2071,16 @@ impl<F: FnPtr> fmt::Debug for F {
 /// as all other references. This macro can create a raw pointer *without* creating
 /// a reference first.
 ///
-/// The `expr` in `addr_of!(expr)` is evaluated as a place expression, but never loads
-/// from the place or requires the place to be dereferenceable. This means that
-/// `addr_of!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned.
-/// Note however that `addr_of!((*ptr).field)` still requires the projection to
-/// `field` to be in-bounds, using the same rules as [`offset`].
+/// See [`addr_of_mut`] for how to create a pointer to uninitialized data.
+/// Doing that with `addr_of` would not make much sense since one could only
+/// read the data, and that would be Undefined Behavior.
+///
+/// # Safety
+///
+/// The `expr` in `addr_of!(expr)` is evaluated as a place expression, but never loads from the
+/// place or requires the place to be dereferenceable. This means that `addr_of!((*ptr).field)`
+/// still requires the projection to `field` to be in-bounds, using the same rules as [`offset`].
+/// However, `addr_of!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned.
 ///
 /// Note that `Deref`/`Index` coercions (and their mutable counterparts) are applied inside
 /// `addr_of!` like everywhere else, in which case a reference is created to call `Deref::deref` or
@@ -2086,6 +2091,8 @@ impl<F: FnPtr> fmt::Debug for F {
 ///
 /// # Example
 ///
+/// **Correct usage: Creating a pointer to unaligned data**
+///
 /// ```
 /// use std::ptr;
 ///
@@ -2101,9 +2108,27 @@ impl<F: FnPtr> fmt::Debug for F {
 /// assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);
 /// ```
 ///
-/// See [`addr_of_mut`] for how to create a pointer to uninitialized data.
-/// Doing that with `addr_of` would not make much sense since one could only
-/// read the data, and that would be Undefined Behavior.
+/// **Incorrect usage: Out-of-bounds fields projection**
+///
+/// ```rust,no_run
+/// use std::ptr;
+///
+/// #[repr(C)]
+/// struct MyStruct {
+///     field1: i32,
+///     field2: i32,
+/// }
+///
+/// let ptr: *const MyStruct = ptr::null();
+/// let fieldptr = unsafe { ptr::addr_of!((*ptr).field2) }; // Undefined Behavior ⚠️
+/// ```
+///
+/// The field projection `.field2` would offset the pointer by 4 bytes,
+/// but the pointer is not in-bounds of an allocation for 4 bytes,
+/// so this offset is Undefined Behavior.
+/// See the [`offset`] docs for a full list of requirements for inbounds pointer arithmetic; the
+/// same requirements apply to field projections, even inside `addr_of!`. (In particular, it makes
+/// no difference whether the pointer is null or dangling.)
 #[stable(feature = "raw_ref_macros", since = "1.51.0")]
 #[rustc_macro_transparency = "semitransparent"]
 #[allow_internal_unstable(raw_ref_op)]
@@ -2120,11 +2145,12 @@ pub macro addr_of($place:expr) {
 /// as all other references. This macro can create a raw pointer *without* creating
 /// a reference first.
 ///
-/// The `expr` in `addr_of_mut!(expr)` is evaluated as a place expression, but never loads
-/// from the place or requires the place to be dereferenceable. This means that
-/// `addr_of_mut!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned.
-/// Note however that `addr_of_mut!((*ptr).field)` still requires the projection to
-/// `field` to be in-bounds, using the same rules as [`offset`].
+/// # Safety
+///
+/// The `expr` in `addr_of_mut!(expr)` is evaluated as a place expression, but never loads from the
+/// place or requires the place to be dereferenceable. This means that `addr_of_mut!((*ptr).field)`
+/// still requires the projection to `field` to be in-bounds, using the same rules as [`offset`].
+/// However, `addr_of_mut!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned.
 ///
 /// Note that `Deref`/`Index` coercions (and their mutable counterparts) are applied inside
 /// `addr_of_mut!` like everywhere else, in which case a reference is created to call `Deref::deref`
@@ -2135,7 +2161,7 @@ pub macro addr_of($place:expr) {
 ///
 /// # Examples
 ///
-/// **Creating a pointer to unaligned data:**
+/// **Correct usage: Creating a pointer to unaligned data**
 ///
 /// ```
 /// use std::ptr;
@@ -2153,7 +2179,7 @@ pub macro addr_of($place:expr) {
 /// assert_eq!({packed.f2}, 42); // `{...}` forces copying the field instead of creating a reference.
 /// ```
 ///
-/// **Creating a pointer to uninitialized data:**
+/// **Correct usage: Creating a pointer to uninitialized data**
 ///
 /// ```rust
 /// use std::{ptr, mem::MaybeUninit};
@@ -2169,6 +2195,28 @@ pub macro addr_of($place:expr) {
 /// unsafe { f1_ptr.write(true); }
 /// let init = unsafe { uninit.assume_init() };
 /// ```
+///
+/// **Incorrect usage: Out-of-bounds fields projection**
+///
+/// ```rust,no_run
+/// use std::ptr;
+///
+/// #[repr(C)]
+/// struct MyStruct {
+///     field1: i32,
+///     field2: i32,
+/// }
+///
+/// let ptr: *mut MyStruct = ptr::null_mut();
+/// let fieldptr = unsafe { ptr::addr_of_mut!((*ptr).field2) }; // Undefined Behavior ⚠️
+/// ```
+///
+/// The field projection `.field2` would offset the pointer by 4 bytes,
+/// but the pointer is not in-bounds of an allocation for 4 bytes,
+/// so this offset is Undefined Behavior.
+/// See the [`offset`] docs for a full list of requirements for inbounds pointer arithmetic; the
+/// same requirements apply to field projections, even inside `addr_of_mut!`. (In particular, it
+/// makes no difference whether the pointer is null or dangling.)
 #[stable(feature = "raw_ref_macros", since = "1.51.0")]
 #[rustc_macro_transparency = "semitransparent"]
 #[allow_internal_unstable(raw_ref_op)]
diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs
index d77ac7eb027dc..81200e0061e05 100644
--- a/library/std/src/sys/mod.rs
+++ b/library/std/src/sys/mod.rs
@@ -9,6 +9,9 @@ pub mod cmath;
 pub mod locks;
 pub mod os_str;
 pub mod path;
+#[allow(dead_code)]
+#[allow(unused_imports)]
+pub mod thread_local;
 
 // FIXME(117276): remove this, move feature implementations into individual
 //                submodules.
diff --git a/library/std/src/sys/pal/common/mod.rs b/library/std/src/sys/pal/common/mod.rs
index b35c5d30b4113..29fc0835d7666 100644
--- a/library/std/src/sys/pal/common/mod.rs
+++ b/library/std/src/sys/pal/common/mod.rs
@@ -12,8 +12,6 @@
 
 pub mod alloc;
 pub mod small_c_string;
-#[allow(unused_imports)]
-pub mod thread_local;
 
 #[cfg(test)]
 mod tests;
diff --git a/library/std/src/sys/pal/hermit/thread.rs b/library/std/src/sys/pal/hermit/thread.rs
index fee80c02d4a6f..cf45b9c23962c 100644
--- a/library/std/src/sys/pal/hermit/thread.rs
+++ b/library/std/src/sys/pal/hermit/thread.rs
@@ -2,7 +2,7 @@
 
 use super::abi;
 use super::thread_local_dtor::run_dtors;
-use crate::ffi::CStr;
+use crate::ffi::{CStr, CString};
 use crate::io;
 use crate::mem;
 use crate::num::NonZero;
@@ -71,6 +71,10 @@ impl Thread {
         // nope
     }
 
+    pub fn get_name() -> Option<CString> {
+        None
+    }
+
     #[inline]
     pub fn sleep(dur: Duration) {
         unsafe {
diff --git a/library/std/src/sys/pal/itron/thread.rs b/library/std/src/sys/pal/itron/thread.rs
index 9c1387bf4083a..814a102dd09ae 100644
--- a/library/std/src/sys/pal/itron/thread.rs
+++ b/library/std/src/sys/pal/itron/thread.rs
@@ -8,7 +8,7 @@ use super::{
 };
 use crate::{
     cell::UnsafeCell,
-    ffi::CStr,
+    ffi::{CStr, CString},
     hint, io,
     mem::ManuallyDrop,
     num::NonZero,
@@ -204,6 +204,10 @@ impl Thread {
         // nope
     }
 
+    pub fn get_name() -> Option<CString> {
+        None
+    }
+
     pub fn sleep(dur: Duration) {
         for timeout in dur2reltims(dur) {
             expect_success(unsafe { abi::dly_tsk(timeout) }, &"dly_tsk");
diff --git a/library/std/src/sys/pal/sgx/thread.rs b/library/std/src/sys/pal/sgx/thread.rs
index c797fde7fbdca..77f68bf73348f 100644
--- a/library/std/src/sys/pal/sgx/thread.rs
+++ b/library/std/src/sys/pal/sgx/thread.rs
@@ -1,6 +1,6 @@
 #![cfg_attr(test, allow(dead_code))] // why is this necessary?
 use super::unsupported;
-use crate::ffi::CStr;
+use crate::ffi::{CStr, CString};
 use crate::io;
 use crate::num::NonZero;
 use crate::time::Duration;
@@ -133,6 +133,10 @@ impl Thread {
         // which succeeds as-is with the SGX target.
     }
 
+    pub fn get_name() -> Option<CString> {
+        None
+    }
+
     pub fn sleep(dur: Duration) {
         usercalls::wait_timeout(0, dur, || true);
     }
diff --git a/library/std/src/sys/pal/teeos/thread.rs b/library/std/src/sys/pal/teeos/thread.rs
index 77f9040ead540..b76bcf9bbb0af 100644
--- a/library/std/src/sys/pal/teeos/thread.rs
+++ b/library/std/src/sys/pal/teeos/thread.rs
@@ -1,7 +1,7 @@
 use core::convert::TryInto;
 
 use crate::cmp;
-use crate::ffi::CStr;
+use crate::ffi::{CStr, CString};
 use crate::io;
 use crate::mem;
 use crate::num::NonZero;
@@ -101,6 +101,10 @@ impl Thread {
         // contact the teeos rustzone team.
     }
 
+    pub fn get_name() -> Option<CString> {
+        None
+    }
+
     /// only main thread could wait for sometime in teeos
     pub fn sleep(dur: Duration) {
         let sleep_millis = dur.as_millis();
diff --git a/library/std/src/sys/pal/uefi/thread.rs b/library/std/src/sys/pal/uefi/thread.rs
index 3d8fa27251f01..b3a4f9c53e36c 100644
--- a/library/std/src/sys/pal/uefi/thread.rs
+++ b/library/std/src/sys/pal/uefi/thread.rs
@@ -1,5 +1,5 @@
 use super::unsupported;
-use crate::ffi::CStr;
+use crate::ffi::{CStr, CString};
 use crate::io;
 use crate::num::NonZero;
 use crate::ptr::NonNull;
@@ -23,6 +23,10 @@ impl Thread {
         // nope
     }
 
+    pub fn get_name() -> Option<CString> {
+        None
+    }
+
     pub fn sleep(dur: Duration) {
         let boot_services: NonNull<r_efi::efi::BootServices> =
             crate::os::uefi::env::boot_services().expect("can't sleep").cast();
diff --git a/library/std/src/sys/pal/unix/thread.rs b/library/std/src/sys/pal/unix/thread.rs
index 864de31c6ebfc..2af6382f3daee 100644
--- a/library/std/src/sys/pal/unix/thread.rs
+++ b/library/std/src/sys/pal/unix/thread.rs
@@ -1,5 +1,5 @@
 use crate::cmp;
-use crate::ffi::CStr;
+use crate::ffi::{CStr, CString};
 use crate::io;
 use crate::mem;
 use crate::num::NonZero;
@@ -225,6 +225,44 @@ impl Thread {
         // Newlib, Emscripten, and VxWorks have no way to set a thread name.
     }
 
+    #[cfg(target_os = "linux")]
+    pub fn get_name() -> Option<CString> {
+        const TASK_COMM_LEN: usize = 16;
+        let mut name = vec![0u8; TASK_COMM_LEN];
+        let res = unsafe {
+            libc::pthread_getname_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len())
+        };
+        if res != 0 {
+            return None;
+        }
+        name.truncate(name.iter().position(|&c| c == 0)?);
+        CString::new(name).ok()
+    }
+
+    #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))]
+    pub fn get_name() -> Option<CString> {
+        let mut name = vec![0u8; libc::MAXTHREADNAMESIZE];
+        let res = unsafe {
+            libc::pthread_getname_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len())
+        };
+        if res != 0 {
+            return None;
+        }
+        name.truncate(name.iter().position(|&c| c == 0)?);
+        CString::new(name).ok()
+    }
+
+    #[cfg(not(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "tvos",
+        target_os = "watchos"
+    )))]
+    pub fn get_name() -> Option<CString> {
+        None
+    }
+
     #[cfg(not(target_os = "espidf"))]
     pub fn sleep(dur: Duration) {
         let mut secs = dur.as_secs();
diff --git a/library/std/src/sys/pal/unsupported/thread.rs b/library/std/src/sys/pal/unsupported/thread.rs
index cd1ae7f7d11cd..b3a91ee1d4cb6 100644
--- a/library/std/src/sys/pal/unsupported/thread.rs
+++ b/library/std/src/sys/pal/unsupported/thread.rs
@@ -1,5 +1,5 @@
 use super::unsupported;
-use crate::ffi::CStr;
+use crate::ffi::{CStr, CString};
 use crate::io;
 use crate::num::NonZero;
 use crate::time::Duration;
@@ -22,6 +22,10 @@ impl Thread {
         // nope
     }
 
+    pub fn get_name() -> Option<CString> {
+        None
+    }
+
     pub fn sleep(_dur: Duration) {
         panic!("can't sleep");
     }
diff --git a/library/std/src/sys/pal/wasi/thread.rs b/library/std/src/sys/pal/wasi/thread.rs
index 77d8b4378e7d5..4b116052f8f8e 100644
--- a/library/std/src/sys/pal/wasi/thread.rs
+++ b/library/std/src/sys/pal/wasi/thread.rs
@@ -1,4 +1,4 @@
-use crate::ffi::CStr;
+use crate::ffi::{CStr, CString};
 use crate::io;
 use crate::mem;
 use crate::num::NonZero;
@@ -134,6 +134,10 @@ impl Thread {
         // nope
     }
 
+    pub fn get_name() -> Option<CString> {
+        None
+    }
+
     pub fn sleep(dur: Duration) {
         let nanos = dur.as_nanos();
         assert!(nanos <= u64::MAX as u128);
diff --git a/library/std/src/sys/pal/windows/c.rs b/library/std/src/sys/pal/windows/c.rs
index b007796722baf..afa9240940494 100644
--- a/library/std/src/sys/pal/windows/c.rs
+++ b/library/std/src/sys/pal/windows/c.rs
@@ -344,6 +344,12 @@ compat_fn_with_fallback! {
         SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); E_NOTIMPL
     }
 
+    // >= Win10 1607
+    // https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreaddescription
+    pub fn GetThreadDescription(hthread: HANDLE, lpthreaddescription: *mut PWSTR) -> HRESULT {
+        SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); E_NOTIMPL
+    }
+
     // >= Win8 / Server 2012
     // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime
     pub fn GetSystemTimePreciseAsFileTime(lpsystemtimeasfiletime: *mut FILETIME) -> () {
diff --git a/library/std/src/sys/pal/windows/c/bindings.txt b/library/std/src/sys/pal/windows/c/bindings.txt
index ab2a8caf5dfd9..d008141153028 100644
--- a/library/std/src/sys/pal/windows/c/bindings.txt
+++ b/library/std/src/sys/pal/windows/c/bindings.txt
@@ -1923,6 +1923,7 @@ Windows.Win32.Foundation.HANDLE_FLAG_INHERIT
 Windows.Win32.Foundation.HANDLE_FLAG_PROTECT_FROM_CLOSE
 Windows.Win32.Foundation.HANDLE_FLAGS
 Windows.Win32.Foundation.HMODULE
+Windows.Win32.Foundation.LocalFree
 Windows.Win32.Foundation.MAX_PATH
 Windows.Win32.Foundation.NO_ERROR
 Windows.Win32.Foundation.NTSTATUS
diff --git a/library/std/src/sys/pal/windows/c/windows_sys.rs b/library/std/src/sys/pal/windows/c/windows_sys.rs
index 8eb779373f7e4..96773d91e990c 100644
--- a/library/std/src/sys/pal/windows/c/windows_sys.rs
+++ b/library/std/src/sys/pal/windows/c/windows_sys.rs
@@ -379,6 +379,10 @@ extern "system" {
     ) -> BOOL;
 }
 #[link(name = "kernel32")]
+extern "system" {
+    pub fn LocalFree(hmem: HLOCAL) -> HLOCAL;
+}
+#[link(name = "kernel32")]
 extern "system" {
     pub fn MoveFileExW(
         lpexistingfilename: PCWSTR,
@@ -3441,6 +3445,7 @@ pub type HANDLE_FLAGS = u32;
 pub const HANDLE_FLAG_INHERIT: HANDLE_FLAGS = 1u32;
 pub const HANDLE_FLAG_PROTECT_FROM_CLOSE: HANDLE_FLAGS = 2u32;
 pub const HIGH_PRIORITY_CLASS: PROCESS_CREATION_FLAGS = 128u32;
+pub type HLOCAL = *mut ::core::ffi::c_void;
 pub type HMODULE = *mut ::core::ffi::c_void;
 pub type HRESULT = i32;
 pub const IDLE_PRIORITY_CLASS: PROCESS_CREATION_FLAGS = 64u32;
diff --git a/library/std/src/sys/pal/windows/thread.rs b/library/std/src/sys/pal/windows/thread.rs
index 0f709e2ec7ba7..a8f1e9b726b1c 100644
--- a/library/std/src/sys/pal/windows/thread.rs
+++ b/library/std/src/sys/pal/windows/thread.rs
@@ -9,7 +9,7 @@ use crate::sys::handle::Handle;
 use crate::sys::stack_overflow;
 use crate::sys_common::FromInner;
 use crate::time::Duration;
-
+use alloc::ffi::CString;
 use core::ffi::c_void;
 
 use super::time::WaitableTimer;
@@ -71,6 +71,29 @@ impl Thread {
         };
     }
 
+    pub fn get_name() -> Option<CString> {
+        unsafe {
+            let mut ptr = core::ptr::null_mut();
+            let result = c::GetThreadDescription(c::GetCurrentThread(), &mut ptr);
+            if result < 0 {
+                return None;
+            }
+            let name = String::from_utf16_lossy({
+                let mut len = 0;
+                while *ptr.add(len) != 0 {
+                    len += 1;
+                }
+                core::slice::from_raw_parts(ptr, len)
+            })
+            .into_bytes();
+            // Attempt to free the memory.
+            // This should never fail but if it does then there's not much we can do about it.
+            let result = c::LocalFree(ptr.cast::<c_void>());
+            debug_assert!(result.is_null());
+            if name.is_empty() { None } else { Some(CString::from_vec_unchecked(name)) }
+        }
+    }
+
     pub fn join(self) {
         let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
         if rc == c::WAIT_FAILED {
diff --git a/library/std/src/sys/pal/xous/thread.rs b/library/std/src/sys/pal/xous/thread.rs
index 21f5954d6e2d2..f95ceb7343bd1 100644
--- a/library/std/src/sys/pal/xous/thread.rs
+++ b/library/std/src/sys/pal/xous/thread.rs
@@ -1,4 +1,4 @@
-use crate::ffi::CStr;
+use crate::ffi::{CStr, CString};
 use crate::io;
 use crate::num::NonZero;
 use crate::os::xous::ffi::{
@@ -113,6 +113,10 @@ impl Thread {
         // nope
     }
 
+    pub fn get_name() -> Option<CString> {
+        None
+    }
+
     pub fn sleep(dur: Duration) {
         // Because the sleep server works on units of `usized milliseconds`, split
         // the messages up into these chunks. This means we may run into issues
diff --git a/library/std/src/sys/pal/common/thread_local/fast_local.rs b/library/std/src/sys/thread_local/fast_local.rs
similarity index 100%
rename from library/std/src/sys/pal/common/thread_local/fast_local.rs
rename to library/std/src/sys/thread_local/fast_local.rs
diff --git a/library/std/src/sys/pal/common/thread_local/mod.rs b/library/std/src/sys/thread_local/mod.rs
similarity index 100%
rename from library/std/src/sys/pal/common/thread_local/mod.rs
rename to library/std/src/sys/thread_local/mod.rs
diff --git a/library/std/src/sys/pal/common/thread_local/os_local.rs b/library/std/src/sys/thread_local/os_local.rs
similarity index 100%
rename from library/std/src/sys/pal/common/thread_local/os_local.rs
rename to library/std/src/sys/thread_local/os_local.rs
diff --git a/library/std/src/sys/pal/common/thread_local/static_local.rs b/library/std/src/sys/thread_local/static_local.rs
similarity index 100%
rename from library/std/src/sys/pal/common/thread_local/static_local.rs
rename to library/std/src/sys/thread_local/static_local.rs
diff --git a/library/std/src/sys_common/thread_info.rs b/library/std/src/sys_common/thread_info.rs
index 8d51732e03588..ec1428ea40ec6 100644
--- a/library/std/src/sys_common/thread_info.rs
+++ b/library/std/src/sys_common/thread_info.rs
@@ -1,6 +1,7 @@
 #![allow(dead_code)] // stack_guard isn't used right now on all platforms
 
 use crate::cell::OnceCell;
+use crate::sys;
 use crate::sys::thread::guard::Guard;
 use crate::thread::Thread;
 
@@ -23,7 +24,8 @@ impl ThreadInfo {
     {
         THREAD_INFO
             .try_with(move |thread_info| {
-                let thread = thread_info.thread.get_or_init(|| Thread::new(None));
+                let thread =
+                    thread_info.thread.get_or_init(|| Thread::new(sys::thread::Thread::get_name()));
                 f(thread, &thread_info.stack_guard)
             })
             .ok()
diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs
index 76af7fec92648..85de2980133d5 100644
--- a/library/std/src/thread/mod.rs
+++ b/library/std/src/thread/mod.rs
@@ -205,7 +205,7 @@ cfg_if::cfg_if! {
         #[doc(hidden)]
         #[unstable(feature = "thread_local_internals", issue = "none")]
         pub mod local_impl {
-            pub use crate::sys::common::thread_local::{thread_local_inner, Key, abort_on_dtor_unwind};
+            pub use crate::sys::thread_local::{thread_local_inner, Key, abort_on_dtor_unwind};
         }
     }
 }
diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs
index 5d6b9e94ee91b..efd06c8df6e29 100644
--- a/library/std/src/thread/tests.rs
+++ b/library/std/src/thread/tests.rs
@@ -69,6 +69,25 @@ fn test_named_thread_truncation() {
     result.unwrap().join().unwrap();
 }
 
+#[cfg(any(
+    target_os = "windows",
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "tvos",
+    target_os = "watchos"
+))]
+#[test]
+fn test_get_os_named_thread() {
+    use crate::sys::thread::Thread;
+    let handler = thread::spawn(|| {
+        let name = c"test me please";
+        Thread::set_name(name);
+        assert_eq!(name, Thread::get_name().unwrap().as_c_str());
+    });
+    handler.join().unwrap();
+}
+
 #[test]
 #[should_panic]
 fn test_invalid_named_thread() {
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 33837fe5652ce..39d27b104cdde 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -179,21 +179,14 @@ pub fn main() {
     rustc_driver::init_logger(&early_dcx, rustc_log::LoggerConfig::from_env("RUSTDOC_LOG"));
 
     let exit_code = rustc_driver::catch_with_exit_code(|| {
-        let args = env::args_os()
-            .enumerate()
-            .map(|(i, arg)| {
-                arg.into_string().unwrap_or_else(|arg| {
-                    early_dcx.early_fatal(format!("argument {i} is not valid Unicode: {arg:?}"))
-                })
-            })
-            .collect::<Vec<_>>();
-        main_args(&mut early_dcx, &args, using_internal_features)
+        let at_args = rustc_driver::args::raw_args(&early_dcx)?;
+        main_args(&mut early_dcx, &at_args, using_internal_features)
     });
     process::exit(exit_code);
 }
 
 fn init_logging(early_dcx: &EarlyDiagCtxt) {
-    let color_logs = match std::env::var("RUSTDOC_LOG_COLOR").as_deref() {
+    let color_logs = match env::var("RUSTDOC_LOG_COLOR").as_deref() {
         Ok("always") => true,
         Ok("never") => false,
         Ok("auto") | Err(VarError::NotPresent) => io::stdout().is_terminal(),
@@ -705,7 +698,7 @@ fn main_args(
     // the compiler with @empty_file as argv[0] and no more arguments.
     let at_args = at_args.get(1..).unwrap_or_default();
 
-    let args = rustc_driver::args::arg_expand_all(early_dcx, at_args);
+    let args = rustc_driver::args::arg_expand_all(early_dcx, at_args)?;
 
     let mut options = getopts::Options::new();
     for option in opts() {
diff --git a/src/tools/clippy/src/driver.rs b/src/tools/clippy/src/driver.rs
index b966fcf9b80f2..0f7f28775850d 100644
--- a/src/tools/clippy/src/driver.rs
+++ b/src/tools/clippy/src/driver.rs
@@ -190,7 +190,7 @@ pub fn main() {
     });
 
     exit(rustc_driver::catch_with_exit_code(move || {
-        let mut orig_args: Vec<String> = env::args().collect();
+        let mut orig_args = rustc_driver::args::raw_args(&early_dcx)?;
 
         let has_sysroot_arg = |args: &mut [String]| -> bool {
             if arg_value(args, "--sysroot", |_| true).is_some() {
diff --git a/src/tools/miri/src/bin/miri.rs b/src/tools/miri/src/bin/miri.rs
index 281a32b77c5b5..a51a9df8a0b1c 100644
--- a/src/tools/miri/src/bin/miri.rs
+++ b/src/tools/miri/src/bin/miri.rs
@@ -342,6 +342,8 @@ fn main() {
     // (`install_ice_hook` might change `RUST_BACKTRACE`.)
     let env_snapshot = env::vars_os().collect::<Vec<_>>();
 
+    let args = rustc_driver::args::raw_args(&early_dcx).unwrap_or_else(|_| std::process::exit(rustc_driver::EXIT_FAILURE));
+
     // If the environment asks us to actually be rustc, then do that.
     if let Some(crate_kind) = env::var_os("MIRI_BE_RUSTC") {
         // Earliest rustc setup.
@@ -359,7 +361,7 @@ fn main() {
 
         // We cannot use `rustc_driver::main` as we need to adjust the CLI arguments.
         run_compiler(
-            env::args().collect(),
+            args,
             target_crate,
             &mut MiriBeRustCompilerCalls { target_crate },
             using_internal_features,
@@ -382,7 +384,7 @@ fn main() {
 
     // If user has explicitly enabled/disabled isolation
     let mut isolation_enabled: Option<bool> = None;
-    for arg in env::args() {
+    for arg in args {
         if rustc_args.is_empty() {
             // Very first arg: binary name.
             rustc_args.push(arg);
diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs
index 920fe16a9fcb4..afa250f16497b 100644
--- a/src/tools/tidy/src/ui_tests.rs
+++ b/src/tools/tidy/src/ui_tests.rs
@@ -18,7 +18,7 @@ const ENTRY_LIMIT: usize = 900;
 // FIXME: The following limits should be reduced eventually.
 
 const ISSUES_ENTRY_LIMIT: usize = 1781;
-const ROOT_ENTRY_LIMIT: usize = 872;
+const ROOT_ENTRY_LIMIT: usize = 866;
 
 const EXPECTED_TEST_FILE_EXTENSIONS: &[&str] = &[
     "rs",     // test source files
@@ -33,8 +33,8 @@ const EXTENSION_EXCEPTION_PATHS: &[&str] = &[
     "tests/ui/asm/named-asm-labels.s", // loading an external asm file to test named labels lint
     "tests/ui/codegen/mismatched-data-layout.json", // testing mismatched data layout w/ custom targets
     "tests/ui/check-cfg/my-awesome-platform.json",  // testing custom targets with cfgs
-    "tests/ui/commandline-argfile-badutf8.args",    // passing args via a file
-    "tests/ui/commandline-argfile.args",            // passing args via a file
+    "tests/ui/argfile/commandline-argfile-badutf8.args", // passing args via a file
+    "tests/ui/argfile/commandline-argfile.args",    // passing args via a file
     "tests/ui/crate-loading/auxiliary/libfoo.rlib", // testing loading a manually created rlib
     "tests/ui/include-macros/data.bin", // testing including data with the include macros
     "tests/ui/include-macros/file.txt", // testing including data with the include macros
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-badutf8-windows.rs b/tests/rustdoc-ui/argfile/commandline-argfile-badutf8-windows.rs
new file mode 100644
index 0000000000000..eba17ca6f7e92
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-badutf8-windows.rs
@@ -0,0 +1,17 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. This test uses backslash as the path separator for the command
+// line arguments and is only run on windows.
+//
+//@ only-windows
+//@ compile-flags: --cfg cmdline_set @{{src-base}}\argfile\commandline-argfile-badutf8.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-badutf8-windows.stderr b/tests/rustdoc-ui/argfile/commandline-argfile-badutf8-windows.stderr
new file mode 100644
index 0000000000000..b4c0d4c20d75f
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-badutf8-windows.stderr
@@ -0,0 +1,2 @@
+error: failed to load argument file: UTF-8 error in $DIR/commandline-argfile-badutf8.args
+
diff --git a/tests/rustdoc-ui/commandline-argfile-badutf8.args b/tests/rustdoc-ui/argfile/commandline-argfile-badutf8.args
similarity index 100%
rename from tests/rustdoc-ui/commandline-argfile-badutf8.args
rename to tests/rustdoc-ui/argfile/commandline-argfile-badutf8.args
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-badutf8.rs b/tests/rustdoc-ui/argfile/commandline-argfile-badutf8.rs
new file mode 100644
index 0000000000000..1b2e52af762f1
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-badutf8.rs
@@ -0,0 +1,18 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. We have a duplicated version of this test that uses backslash as
+// the path separator for the command line arguments that is only run on
+// windows.
+//
+//@ ignore-windows
+//@ compile-flags: --cfg cmdline_set @{{src-base}}/argfile/commandline-argfile-badutf8.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-badutf8.stderr b/tests/rustdoc-ui/argfile/commandline-argfile-badutf8.stderr
new file mode 100644
index 0000000000000..b4c0d4c20d75f
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-badutf8.stderr
@@ -0,0 +1,2 @@
+error: failed to load argument file: UTF-8 error in $DIR/commandline-argfile-badutf8.args
+
diff --git a/tests/ui/commandline-argfile-missing.rs b/tests/rustdoc-ui/argfile/commandline-argfile-missing-windows.rs
similarity index 55%
rename from tests/ui/commandline-argfile-missing.rs
rename to tests/rustdoc-ui/argfile/commandline-argfile-missing-windows.rs
index bb9644d66ce10..24cfd25ccadb9 100644
--- a/tests/ui/commandline-argfile-missing.rs
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-missing-windows.rs
@@ -1,8 +1,13 @@
 // Check to see if we can get parameters from an @argsfile file
 //
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. This test uses backslash as the path separator for the command
+// line arguments and is only run on windows.
+//
+//@ only-windows
 //@ normalize-stderr-test: "os error \d+" -> "os error $$ERR"
 //@ normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
-//@ compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-missing.args
+//@ compile-flags: --cfg cmdline_set @{{src-base}}\argfile\commandline-argfile-missing.args
 
 #[cfg(not(cmdline_set))]
 compile_error!("cmdline_set not set");
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-missing-windows.stderr b/tests/rustdoc-ui/argfile/commandline-argfile-missing-windows.stderr
new file mode 100644
index 0000000000000..28a20debf1cca
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-missing-windows.stderr
@@ -0,0 +1,2 @@
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
+
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-missing.rs b/tests/rustdoc-ui/argfile/commandline-argfile-missing.rs
new file mode 100644
index 0000000000000..fe6a849b0c8b6
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-missing.rs
@@ -0,0 +1,20 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. We have a duplicated version of this test that uses backslash as
+// the path separator for the command line arguments that is only run on
+// windows.
+//
+//@ ignore-windows
+//@ normalize-stderr-test: "os error \d+" -> "os error $$ERR"
+//@ normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
+//@ compile-flags: --cfg cmdline_set @{{src-base}}/argfile/commandline-argfile-missing.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-missing.stderr b/tests/rustdoc-ui/argfile/commandline-argfile-missing.stderr
new file mode 100644
index 0000000000000..28a20debf1cca
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-missing.stderr
@@ -0,0 +1,2 @@
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
+
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-multiple-windows.rs b/tests/rustdoc-ui/argfile/commandline-argfile-multiple-windows.rs
new file mode 100644
index 0000000000000..03f0aa08b1d5e
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-multiple-windows.rs
@@ -0,0 +1,19 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. This test uses backslash as the path separator for the command
+// line arguments and is only run on windows.
+//
+//@ only-windows
+//@ normalize-stderr-test: "os error \d+" -> "os error $$ERR"
+//@ normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
+//@ compile-flags: --cfg cmdline_set @{{src-base}}\argfile\commandline-argfile-missing.args @{{src-base}}\argfile\commandline-argfile-badutf8.args @{{src-base}}\argfile\commandline-argfile-missing2.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-multiple-windows.stderr b/tests/rustdoc-ui/argfile/commandline-argfile-multiple-windows.stderr
new file mode 100644
index 0000000000000..f9a6bb784bffd
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-multiple-windows.stderr
@@ -0,0 +1,6 @@
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
+
+error: failed to load argument file: UTF-8 error in $DIR/commandline-argfile-badutf8.args
+
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing2.args: No such file or directory (os error $ERR)
+
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-multiple.rs b/tests/rustdoc-ui/argfile/commandline-argfile-multiple.rs
new file mode 100644
index 0000000000000..cae33ecaad707
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-multiple.rs
@@ -0,0 +1,20 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. We have a duplicated version of this test that uses backslash as
+// the path separator for the command line arguments that is only run on
+// windows.
+//
+//@ ignore-windows
+//@ normalize-stderr-test: "os error \d+" -> "os error $$ERR"
+//@ normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
+//@ compile-flags: --cfg cmdline_set @{{src-base}}/argfile/commandline-argfile-missing.args @{{src-base}}/argfile/commandline-argfile-badutf8.args @{{src-base}}/argfile/commandline-argfile-missing2.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/rustdoc-ui/argfile/commandline-argfile-multiple.stderr b/tests/rustdoc-ui/argfile/commandline-argfile-multiple.stderr
new file mode 100644
index 0000000000000..f9a6bb784bffd
--- /dev/null
+++ b/tests/rustdoc-ui/argfile/commandline-argfile-multiple.stderr
@@ -0,0 +1,6 @@
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
+
+error: failed to load argument file: UTF-8 error in $DIR/commandline-argfile-badutf8.args
+
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing2.args: No such file or directory (os error $ERR)
+
diff --git a/tests/rustdoc-ui/commandline-argfile.args b/tests/rustdoc-ui/argfile/commandline-argfile.args
similarity index 100%
rename from tests/rustdoc-ui/commandline-argfile.args
rename to tests/rustdoc-ui/argfile/commandline-argfile.args
diff --git a/tests/rustdoc-ui/commandline-argfile.rs b/tests/rustdoc-ui/argfile/commandline-argfile.rs
similarity index 72%
rename from tests/rustdoc-ui/commandline-argfile.rs
rename to tests/rustdoc-ui/argfile/commandline-argfile.rs
index d71bc72562b75..b0b314f53ceb7 100644
--- a/tests/rustdoc-ui/commandline-argfile.rs
+++ b/tests/rustdoc-ui/argfile/commandline-argfile.rs
@@ -1,7 +1,7 @@
 // Check to see if we can get parameters from an @argsfile file
 //
 //@ check-pass
-//@ compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile.args
+//@ compile-flags: --cfg cmdline_set @{{src-base}}/argfile/commandline-argfile.args
 
 #[cfg(not(cmdline_set))]
 compile_error!("cmdline_set not set");
diff --git a/tests/rustdoc-ui/commandline-argfile-badutf8.rs b/tests/rustdoc-ui/commandline-argfile-badutf8.rs
deleted file mode 100644
index b3a19fa62741d..0000000000000
--- a/tests/rustdoc-ui/commandline-argfile-badutf8.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Check to see if we can get parameters from an @argsfile file
-//
-//@ compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-badutf8.args
-
-#[cfg(not(cmdline_set))]
-compile_error!("cmdline_set not set");
-
-#[cfg(not(unbroken))]
-compile_error!("unbroken not set");
-
-fn main() {
-}
diff --git a/tests/rustdoc-ui/commandline-argfile-badutf8.stderr b/tests/rustdoc-ui/commandline-argfile-badutf8.stderr
deleted file mode 100644
index 9af6fc0a518df..0000000000000
--- a/tests/rustdoc-ui/commandline-argfile-badutf8.stderr
+++ /dev/null
@@ -1,2 +0,0 @@
-error: Failed to load argument file: Utf8 error in $DIR/commandline-argfile-badutf8.args
-
diff --git a/tests/rustdoc-ui/commandline-argfile-missing.stderr b/tests/rustdoc-ui/commandline-argfile-missing.stderr
deleted file mode 100644
index 179ad83100419..0000000000000
--- a/tests/rustdoc-ui/commandline-argfile-missing.stderr
+++ /dev/null
@@ -1,2 +0,0 @@
-error: Failed to load argument file: IO Error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
-
diff --git a/tests/ui/argfile/commandline-argfile-badutf8-windows.rs b/tests/ui/argfile/commandline-argfile-badutf8-windows.rs
new file mode 100644
index 0000000000000..eba17ca6f7e92
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-badutf8-windows.rs
@@ -0,0 +1,17 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. This test uses backslash as the path separator for the command
+// line arguments and is only run on windows.
+//
+//@ only-windows
+//@ compile-flags: --cfg cmdline_set @{{src-base}}\argfile\commandline-argfile-badutf8.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/ui/argfile/commandline-argfile-badutf8-windows.stderr b/tests/ui/argfile/commandline-argfile-badutf8-windows.stderr
new file mode 100644
index 0000000000000..b4c0d4c20d75f
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-badutf8-windows.stderr
@@ -0,0 +1,2 @@
+error: failed to load argument file: UTF-8 error in $DIR/commandline-argfile-badutf8.args
+
diff --git a/tests/ui/commandline-argfile-badutf8.args b/tests/ui/argfile/commandline-argfile-badutf8.args
similarity index 100%
rename from tests/ui/commandline-argfile-badutf8.args
rename to tests/ui/argfile/commandline-argfile-badutf8.args
diff --git a/tests/ui/argfile/commandline-argfile-badutf8.rs b/tests/ui/argfile/commandline-argfile-badutf8.rs
new file mode 100644
index 0000000000000..1b2e52af762f1
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-badutf8.rs
@@ -0,0 +1,18 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. We have a duplicated version of this test that uses backslash as
+// the path separator for the command line arguments that is only run on
+// windows.
+//
+//@ ignore-windows
+//@ compile-flags: --cfg cmdline_set @{{src-base}}/argfile/commandline-argfile-badutf8.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/ui/argfile/commandline-argfile-badutf8.stderr b/tests/ui/argfile/commandline-argfile-badutf8.stderr
new file mode 100644
index 0000000000000..b4c0d4c20d75f
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-badutf8.stderr
@@ -0,0 +1,2 @@
+error: failed to load argument file: UTF-8 error in $DIR/commandline-argfile-badutf8.args
+
diff --git a/tests/rustdoc-ui/commandline-argfile-missing.rs b/tests/ui/argfile/commandline-argfile-missing-windows.rs
similarity index 55%
rename from tests/rustdoc-ui/commandline-argfile-missing.rs
rename to tests/ui/argfile/commandline-argfile-missing-windows.rs
index bb9644d66ce10..24cfd25ccadb9 100644
--- a/tests/rustdoc-ui/commandline-argfile-missing.rs
+++ b/tests/ui/argfile/commandline-argfile-missing-windows.rs
@@ -1,8 +1,13 @@
 // Check to see if we can get parameters from an @argsfile file
 //
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. This test uses backslash as the path separator for the command
+// line arguments and is only run on windows.
+//
+//@ only-windows
 //@ normalize-stderr-test: "os error \d+" -> "os error $$ERR"
 //@ normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
-//@ compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-missing.args
+//@ compile-flags: --cfg cmdline_set @{{src-base}}\argfile\commandline-argfile-missing.args
 
 #[cfg(not(cmdline_set))]
 compile_error!("cmdline_set not set");
diff --git a/tests/ui/argfile/commandline-argfile-missing-windows.stderr b/tests/ui/argfile/commandline-argfile-missing-windows.stderr
new file mode 100644
index 0000000000000..28a20debf1cca
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-missing-windows.stderr
@@ -0,0 +1,2 @@
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
+
diff --git a/tests/ui/argfile/commandline-argfile-missing.rs b/tests/ui/argfile/commandline-argfile-missing.rs
new file mode 100644
index 0000000000000..fe6a849b0c8b6
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-missing.rs
@@ -0,0 +1,20 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. We have a duplicated version of this test that uses backslash as
+// the path separator for the command line arguments that is only run on
+// windows.
+//
+//@ ignore-windows
+//@ normalize-stderr-test: "os error \d+" -> "os error $$ERR"
+//@ normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
+//@ compile-flags: --cfg cmdline_set @{{src-base}}/argfile/commandline-argfile-missing.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/ui/argfile/commandline-argfile-missing.stderr b/tests/ui/argfile/commandline-argfile-missing.stderr
new file mode 100644
index 0000000000000..28a20debf1cca
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-missing.stderr
@@ -0,0 +1,2 @@
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
+
diff --git a/tests/ui/argfile/commandline-argfile-multiple-windows.rs b/tests/ui/argfile/commandline-argfile-multiple-windows.rs
new file mode 100644
index 0000000000000..03f0aa08b1d5e
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-multiple-windows.rs
@@ -0,0 +1,19 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. This test uses backslash as the path separator for the command
+// line arguments and is only run on windows.
+//
+//@ only-windows
+//@ normalize-stderr-test: "os error \d+" -> "os error $$ERR"
+//@ normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
+//@ compile-flags: --cfg cmdline_set @{{src-base}}\argfile\commandline-argfile-missing.args @{{src-base}}\argfile\commandline-argfile-badutf8.args @{{src-base}}\argfile\commandline-argfile-missing2.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/ui/argfile/commandline-argfile-multiple-windows.stderr b/tests/ui/argfile/commandline-argfile-multiple-windows.stderr
new file mode 100644
index 0000000000000..f9a6bb784bffd
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-multiple-windows.stderr
@@ -0,0 +1,6 @@
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
+
+error: failed to load argument file: UTF-8 error in $DIR/commandline-argfile-badutf8.args
+
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing2.args: No such file or directory (os error $ERR)
+
diff --git a/tests/ui/argfile/commandline-argfile-multiple.rs b/tests/ui/argfile/commandline-argfile-multiple.rs
new file mode 100644
index 0000000000000..cae33ecaad707
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-multiple.rs
@@ -0,0 +1,20 @@
+// Check to see if we can get parameters from an @argsfile file
+//
+// Path replacement in .stderr files (i.e. `$DIR`) doesn't handle mixed path
+// separators. We have a duplicated version of this test that uses backslash as
+// the path separator for the command line arguments that is only run on
+// windows.
+//
+//@ ignore-windows
+//@ normalize-stderr-test: "os error \d+" -> "os error $$ERR"
+//@ normalize-stderr-test: "commandline-argfile-missing.args:[^(]*" -> "commandline-argfile-missing.args: $$FILE_MISSING "
+//@ compile-flags: --cfg cmdline_set @{{src-base}}/argfile/commandline-argfile-missing.args @{{src-base}}/argfile/commandline-argfile-badutf8.args @{{src-base}}/argfile/commandline-argfile-missing2.args
+
+#[cfg(not(cmdline_set))]
+compile_error!("cmdline_set not set");
+
+#[cfg(not(unbroken))]
+compile_error!("unbroken not set");
+
+fn main() {
+}
diff --git a/tests/ui/argfile/commandline-argfile-multiple.stderr b/tests/ui/argfile/commandline-argfile-multiple.stderr
new file mode 100644
index 0000000000000..f9a6bb784bffd
--- /dev/null
+++ b/tests/ui/argfile/commandline-argfile-multiple.stderr
@@ -0,0 +1,6 @@
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
+
+error: failed to load argument file: UTF-8 error in $DIR/commandline-argfile-badutf8.args
+
+error: failed to load argument file: IO error: $DIR/commandline-argfile-missing2.args: No such file or directory (os error $ERR)
+
diff --git a/tests/ui/commandline-argfile.args b/tests/ui/argfile/commandline-argfile.args
similarity index 100%
rename from tests/ui/commandline-argfile.args
rename to tests/ui/argfile/commandline-argfile.args
diff --git a/tests/ui/commandline-argfile.rs b/tests/ui/argfile/commandline-argfile.rs
similarity index 72%
rename from tests/ui/commandline-argfile.rs
rename to tests/ui/argfile/commandline-argfile.rs
index 8577312a3c406..387a8d033b3cd 100644
--- a/tests/ui/commandline-argfile.rs
+++ b/tests/ui/argfile/commandline-argfile.rs
@@ -1,7 +1,7 @@
 // Check to see if we can get parameters from an @argsfile file
 //
 //@ build-pass
-//@ compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile.args
+//@ compile-flags: --cfg cmdline_set @{{src-base}}/argfile/commandline-argfile.args
 
 #[cfg(not(cmdline_set))]
 compile_error!("cmdline_set not set");
diff --git a/tests/ui/commandline-argfile-badutf8.rs b/tests/ui/commandline-argfile-badutf8.rs
deleted file mode 100644
index b3a19fa62741d..0000000000000
--- a/tests/ui/commandline-argfile-badutf8.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Check to see if we can get parameters from an @argsfile file
-//
-//@ compile-flags: --cfg cmdline_set @{{src-base}}/commandline-argfile-badutf8.args
-
-#[cfg(not(cmdline_set))]
-compile_error!("cmdline_set not set");
-
-#[cfg(not(unbroken))]
-compile_error!("unbroken not set");
-
-fn main() {
-}
diff --git a/tests/ui/commandline-argfile-badutf8.stderr b/tests/ui/commandline-argfile-badutf8.stderr
deleted file mode 100644
index 9af6fc0a518df..0000000000000
--- a/tests/ui/commandline-argfile-badutf8.stderr
+++ /dev/null
@@ -1,2 +0,0 @@
-error: Failed to load argument file: Utf8 error in $DIR/commandline-argfile-badutf8.args
-
diff --git a/tests/ui/commandline-argfile-missing.stderr b/tests/ui/commandline-argfile-missing.stderr
deleted file mode 100644
index 179ad83100419..0000000000000
--- a/tests/ui/commandline-argfile-missing.stderr
+++ /dev/null
@@ -1,2 +0,0 @@
-error: Failed to load argument file: IO Error: $DIR/commandline-argfile-missing.args: $FILE_MISSING (os error $ERR)
-
diff --git a/tests/ui/shell-argfiles/shell-argfiles-badquotes-windows.stderr b/tests/ui/shell-argfiles/shell-argfiles-badquotes-windows.stderr
index 14adb1f740abb..dc219c153f781 100644
--- a/tests/ui/shell-argfiles/shell-argfiles-badquotes-windows.stderr
+++ b/tests/ui/shell-argfiles/shell-argfiles-badquotes-windows.stderr
@@ -1,2 +1,2 @@
-error: Failed to load argument file: Invalid shell-style arguments in $DIR/shell-argfiles-badquotes.args
+error: failed to load argument file: invalid shell-style arguments in $DIR/shell-argfiles-badquotes.args
 
diff --git a/tests/ui/shell-argfiles/shell-argfiles-badquotes.stderr b/tests/ui/shell-argfiles/shell-argfiles-badquotes.stderr
index 14adb1f740abb..dc219c153f781 100644
--- a/tests/ui/shell-argfiles/shell-argfiles-badquotes.stderr
+++ b/tests/ui/shell-argfiles/shell-argfiles-badquotes.stderr
@@ -1,2 +1,2 @@
-error: Failed to load argument file: Invalid shell-style arguments in $DIR/shell-argfiles-badquotes.args
+error: failed to load argument file: invalid shell-style arguments in $DIR/shell-argfiles-badquotes.args