about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs67
-rw-r--r--compiler/rustc_middle/src/query/mod.rs8
-rw-r--r--compiler/rustc_monomorphize/messages.ftl9
-rw-r--r--compiler/rustc_monomorphize/src/collector.rs4
-rw-r--r--compiler/rustc_monomorphize/src/collector/abi_check.rs162
-rw-r--r--compiler/rustc_monomorphize/src/errors.rs18
-rw-r--r--compiler/rustc_target/src/target_features.rs17
-rw-r--r--src/tools/miri/.github/workflows/ci.yml13
-rw-r--r--src/tools/miri/CONTRIBUTING.md27
-rwxr-xr-xsrc/tools/miri/ci/ci.sh2
-rw-r--r--src/tools/miri/clippy.toml2
-rw-r--r--src/tools/miri/miri-script/Cargo.lock43
-rw-r--r--src/tools/miri/miri-script/Cargo.toml1
-rw-r--r--src/tools/miri/miri-script/src/commands.rs21
-rw-r--r--src/tools/miri/miri-script/src/coverage.rs91
-rw-r--r--src/tools/miri/miri-script/src/main.rs8
-rw-r--r--src/tools/miri/miri-script/src/util.rs7
-rw-r--r--src/tools/miri/rust-version2
-rw-r--r--src/tools/miri/src/alloc_addresses/mod.rs72
-rw-r--r--src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs2
-rw-r--r--src/tools/miri/src/concurrency/sync.rs4
-rw-r--r--src/tools/miri/src/concurrency/thread.rs44
-rw-r--r--src/tools/miri/src/concurrency/weak_memory.rs1
-rw-r--r--src/tools/miri/src/eval.rs2
-rw-r--r--src/tools/miri/src/helpers.rs2
-rw-r--r--src/tools/miri/src/intrinsics/mod.rs2
-rw-r--r--src/tools/miri/src/intrinsics/simd.rs4
-rw-r--r--src/tools/miri/src/provenance_gc.rs12
-rw-r--r--src/tools/miri/src/shims/foreign_items.rs8
-rw-r--r--src/tools/miri/src/shims/io_error.rs4
-rw-r--r--src/tools/miri/src/shims/tls.rs2
-rw-r--r--src/tools/miri/src/shims/unix/android/foreign_items.rs4
-rw-r--r--src/tools/miri/src/shims/unix/android/thread.rs6
-rw-r--r--src/tools/miri/src/shims/unix/foreign_items.rs8
-rw-r--r--src/tools/miri/src/shims/unix/fs.rs2
-rw-r--r--src/tools/miri/src/shims/unix/linux/foreign_items.rs75
-rw-r--r--src/tools/miri/src/shims/unix/linux/mem.rs2
-rw-r--r--src/tools/miri/src/shims/unix/linux/mod.rs1
-rw-r--r--src/tools/miri/src/shims/unix/linux/sync.rs4
-rw-r--r--src/tools/miri/src/shims/unix/linux/syscall.rs63
-rw-r--r--src/tools/miri/src/shims/unix/macos/foreign_items.rs22
-rw-r--r--src/tools/miri/src/shims/unix/mem.rs2
-rw-r--r--src/tools/miri/src/shims/unix/mod.rs2
-rw-r--r--src/tools/miri/src/shims/unix/solarish/foreign_items.rs21
-rw-r--r--src/tools/miri/src/shims/unix/sync.rs1
-rw-r--r--src/tools/miri/src/shims/unix/thread.rs62
-rw-r--r--src/tools/miri/src/shims/windows/foreign_items.rs60
-rw-r--r--src/tools/miri/src/shims/windows/handle.rs37
-rw-r--r--src/tools/miri/src/shims/windows/sync.rs2
-rw-r--r--src/tools/miri/src/shims/windows/thread.rs10
-rw-r--r--src/tools/miri/src/shims/x86/gfni.rs4
-rw-r--r--src/tools/miri/src/shims/x86/mod.rs99
-rw-r--r--src/tools/miri/src/shims/x86/sse42.rs4
-rw-r--r--src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs1
-rw-r--r--src/tools/miri/tests/pass-dep/libc/libc-random.rs41
-rw-r--r--src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs25
-rw-r--r--src/tools/miri/tests/pass/shims/x86/intrinsics-sha.rs2
-rw-r--r--src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs193
-rw-r--r--tests/crashes/131342-2.rs40
-rw-r--r--tests/crashes/131342.rs17
-rw-r--r--tests/ui/layout/post-mono-layout-cycle-2.rs1
-rw-r--r--tests/ui/layout/post-mono-layout-cycle-2.stderr10
-rw-r--r--tests/ui/layout/post-mono-layout-cycle.rs1
-rw-r--r--tests/ui/layout/post-mono-layout-cycle.stderr10
-rw-r--r--tests/ui/simd-abi-checks.rs81
-rw-r--r--tests/ui/simd-abi-checks.stderr93
-rw-r--r--tests/ui/sse-abi-checks.rs24
-rw-r--r--tests/ui/sse-abi-checks.stderr13
68 files changed, 1324 insertions, 380 deletions
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 06a3e4a6743..51146cfd2e9 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -16,6 +16,7 @@ declare_lint_pass! {
     /// that are used by other parts of the compiler.
     HardwiredLints => [
         // tidy-alphabetical-start
+        ABI_UNSUPPORTED_VECTOR_TYPES,
         ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE,
         AMBIGUOUS_ASSOCIATED_ITEMS,
         AMBIGUOUS_GLOB_IMPORTS,
@@ -5031,3 +5032,69 @@ declare_lint! {
     };
     crate_level_only
 }
+
+declare_lint! {
+    /// The `abi_unsupported_vector_types` lint detects function definitions and calls
+    /// whose ABI depends on enabling certain target features, but those features are not enabled.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,ignore (fails on non-x86_64)
+    /// extern "C" fn missing_target_feature(_: std::arch::x86_64::__m256) {
+    ///   todo!()
+    /// }
+    ///
+    /// #[target_feature(enable = "avx")]
+    /// unsafe extern "C" fn with_target_feature(_: std::arch::x86_64::__m256) {
+    ///   todo!()
+    /// }
+    ///
+    /// fn main() {
+    ///   let v = unsafe { std::mem::zeroed() };
+    ///   unsafe { with_target_feature(v); }
+    /// }
+    /// ```
+    ///
+    /// ```text
+    /// warning: ABI error: this function call uses a avx vector type, which is not enabled in the caller
+    ///  --> lint_example.rs:18:12
+    ///   |
+    ///   |   unsafe { with_target_feature(v); }
+    ///   |            ^^^^^^^^^^^^^^^^^^^^^^ function called here
+    ///   |
+    ///   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+    ///   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+    ///   = help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
+    ///   = note: `#[warn(abi_unsupported_vector_types)]` on by default
+    ///
+    ///
+    /// warning: ABI error: this function definition uses a avx vector type, which is not enabled
+    ///  --> lint_example.rs:3:1
+    ///   |
+    ///   | pub extern "C" fn with_target_feature(_: std::arch::x86_64::__m256) {
+    ///   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function defined here
+    ///   |
+    ///   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+    ///   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+    ///   = help: consider enabling it globally (-C target-feature=+avx) or locally (#[target_feature(enable="avx")])
+    /// ```
+    ///
+    ///
+    ///
+    /// ### Explanation
+    ///
+    /// The C ABI for `__m256` requires the value to be passed in an AVX register,
+    /// which is only possible when the `avx` target feature is enabled.
+    /// Therefore, `missing_target_feature` cannot be compiled without that target feature.
+    /// A similar (but complementary) message is triggered when `with_target_feature` is called
+    /// by a function that does not enable the `avx` target feature.
+    ///
+    /// Note that this lint is very similar to the `-Wpsabi` warning in `gcc`/`clang`.
+    pub ABI_UNSUPPORTED_VECTOR_TYPES,
+    Warn,
+    "this function call or definition uses a vector type which is not enabled",
+    @future_incompatible = FutureIncompatibleInfo {
+        reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps,
+        reference: "issue #116558 <https://github.com/rust-lang/rust/issues/116558>",
+    };
+}
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 8ae89ea6e2c..54ead9a7a75 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -2325,6 +2325,14 @@ rustc_queries! {
         desc { "whether the item should be made inlinable across crates" }
         separate_provide_extern
     }
+
+    /// Check the signature of this function as well as all the call expressions inside of it
+    /// to ensure that any target features required by the ABI are enabled.
+    /// Should be called on a fully monomorphized instance.
+    query check_feature_dependent_abi(key: ty::Instance<'tcx>) {
+        desc { "check for feature-dependent ABI" }
+        cache_on_disk_if { true }
+    }
 }
 
 rustc_query_append! { define_callbacks! }
diff --git a/compiler/rustc_monomorphize/messages.ftl b/compiler/rustc_monomorphize/messages.ftl
index 7210701d482..6da387bbebc 100644
--- a/compiler/rustc_monomorphize/messages.ftl
+++ b/compiler/rustc_monomorphize/messages.ftl
@@ -1,3 +1,12 @@
+monomorphize_abi_error_disabled_vector_type_call =
+  ABI error: this function call uses a vector type that requires the `{$required_feature}` target feature, which is not enabled in the caller
+  .label = function called here
+  .help = consider enabling it globally (`-C target-feature=+{$required_feature}`) or locally (`#[target_feature(enable="{$required_feature}")]`)
+monomorphize_abi_error_disabled_vector_type_def =
+  ABI error: this function definition uses a vector type that requires the `{$required_feature}` target feature, which is not enabled
+  .label = function defined here
+  .help = consider enabling it globally (`-C target-feature=+{$required_feature}`) or locally (`#[target_feature(enable="{$required_feature}")]`)
+
 monomorphize_couldnt_dump_mono_stats =
     unexpected error occurred while dumping monomorphization stats: {$error}
 
diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs
index 50bab0da606..85151e5f093 100644
--- a/compiler/rustc_monomorphize/src/collector.rs
+++ b/compiler/rustc_monomorphize/src/collector.rs
@@ -205,6 +205,7 @@
 //! this is not implemented however: a mono item will be produced
 //! regardless of whether it is actually needed or not.
 
+mod abi_check;
 mod move_check;
 
 use std::path::PathBuf;
@@ -1207,6 +1208,8 @@ fn collect_items_of_instance<'tcx>(
     mentioned_items: &mut MonoItems<'tcx>,
     mode: CollectionMode,
 ) {
+    tcx.ensure().check_feature_dependent_abi(instance);
+
     let body = tcx.instance_mir(instance.def);
     // Naively, in "used" collection mode, all functions get added to *both* `used_items` and
     // `mentioned_items`. Mentioned items processing will then notice that they have already been
@@ -1623,4 +1626,5 @@ pub(crate) fn collect_crate_mono_items<'tcx>(
 
 pub(crate) fn provide(providers: &mut Providers) {
     providers.hooks.should_codegen_locally = should_codegen_locally;
+    abi_check::provide(providers);
 }
diff --git a/compiler/rustc_monomorphize/src/collector/abi_check.rs b/compiler/rustc_monomorphize/src/collector/abi_check.rs
new file mode 100644
index 00000000000..f1c57f1e997
--- /dev/null
+++ b/compiler/rustc_monomorphize/src/collector/abi_check.rs
@@ -0,0 +1,162 @@
+//! This module ensures that if a function's ABI requires a particular target feature,
+//! that target feature is enabled both on the callee and all callers.
+use rustc_hir::CRATE_HIR_ID;
+use rustc_middle::mir::visit::Visitor as MirVisitor;
+use rustc_middle::mir::{self, Location, traversal};
+use rustc_middle::query::Providers;
+use rustc_middle::ty::inherent::*;
+use rustc_middle::ty::{self, Instance, InstanceKind, ParamEnv, Ty, TyCtxt};
+use rustc_session::lint::builtin::ABI_UNSUPPORTED_VECTOR_TYPES;
+use rustc_span::def_id::DefId;
+use rustc_span::{DUMMY_SP, Span, Symbol};
+use rustc_target::abi::call::{FnAbi, PassMode};
+use rustc_target::abi::{BackendRepr, RegKind};
+
+use crate::errors::{AbiErrorDisabledVectorTypeCall, AbiErrorDisabledVectorTypeDef};
+
+fn uses_vector_registers(mode: &PassMode, repr: &BackendRepr) -> bool {
+    match mode {
+        PassMode::Ignore | PassMode::Indirect { .. } => false,
+        PassMode::Cast { pad_i32: _, cast } => {
+            cast.prefix.iter().any(|r| r.is_some_and(|x| x.kind == RegKind::Vector))
+                || cast.rest.unit.kind == RegKind::Vector
+        }
+        PassMode::Direct(..) | PassMode::Pair(..) => matches!(repr, BackendRepr::Vector { .. }),
+    }
+}
+
+fn do_check_abi<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    abi: &FnAbi<'tcx, Ty<'tcx>>,
+    target_feature_def: DefId,
+    mut emit_err: impl FnMut(&'static str),
+) {
+    let Some(feature_def) = tcx.sess.target.features_for_correct_vector_abi() else {
+        return;
+    };
+    let codegen_attrs = tcx.codegen_fn_attrs(target_feature_def);
+    for arg_abi in abi.args.iter().chain(std::iter::once(&abi.ret)) {
+        let size = arg_abi.layout.size;
+        if uses_vector_registers(&arg_abi.mode, &arg_abi.layout.backend_repr) {
+            // Find the first feature that provides at least this vector size.
+            let feature = match feature_def.iter().find(|(bits, _)| size.bits() <= *bits) {
+                Some((_, feature)) => feature,
+                None => {
+                    emit_err("<no available feature for this size>");
+                    continue;
+                }
+            };
+            let feature_sym = Symbol::intern(feature);
+            if !tcx.sess.unstable_target_features.contains(&feature_sym)
+                && !codegen_attrs.target_features.iter().any(|x| x.name == feature_sym)
+            {
+                emit_err(feature);
+            }
+        }
+    }
+}
+
+/// Checks that the ABI of a given instance of a function does not contain vector-passed arguments
+/// or return values for which the corresponding target feature is not enabled.
+fn check_instance_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
+    let param_env = ParamEnv::reveal_all();
+    let Ok(abi) = tcx.fn_abi_of_instance(param_env.and((instance, ty::List::empty()))) else {
+        // An error will be reported during codegen if we cannot determine the ABI of this
+        // function.
+        return;
+    };
+    do_check_abi(tcx, abi, instance.def_id(), |required_feature| {
+        let span = tcx.def_span(instance.def_id());
+        tcx.emit_node_span_lint(
+            ABI_UNSUPPORTED_VECTOR_TYPES,
+            CRATE_HIR_ID,
+            span,
+            AbiErrorDisabledVectorTypeDef { span, required_feature },
+        );
+    })
+}
+
+/// Checks that a call expression does not try to pass a vector-passed argument which requires a
+/// target feature that the caller does not have, as doing so causes UB because of ABI mismatch.
+fn check_call_site_abi<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    callee: Ty<'tcx>,
+    span: Span,
+    caller: InstanceKind<'tcx>,
+) {
+    if callee.fn_sig(tcx).abi().is_rust() {
+        // "Rust" ABI never passes arguments in vector registers.
+        return;
+    }
+    let param_env = ParamEnv::reveal_all();
+    let callee_abi = match *callee.kind() {
+        ty::FnPtr(..) => {
+            tcx.fn_abi_of_fn_ptr(param_env.and((callee.fn_sig(tcx), ty::List::empty())))
+        }
+        ty::FnDef(def_id, args) => {
+            // Intrinsics are handled separately by the compiler.
+            if tcx.intrinsic(def_id).is_some() {
+                return;
+            }
+            let instance = ty::Instance::expect_resolve(tcx, param_env, def_id, args, DUMMY_SP);
+            tcx.fn_abi_of_instance(param_env.and((instance, ty::List::empty())))
+        }
+        _ => {
+            panic!("Invalid function call");
+        }
+    };
+
+    let Ok(callee_abi) = callee_abi else {
+        // ABI failed to compute; this will not get through codegen.
+        return;
+    };
+    do_check_abi(tcx, callee_abi, caller.def_id(), |required_feature| {
+        tcx.emit_node_span_lint(
+            ABI_UNSUPPORTED_VECTOR_TYPES,
+            CRATE_HIR_ID,
+            span,
+            AbiErrorDisabledVectorTypeCall { span, required_feature },
+        );
+    });
+}
+
+struct MirCallesAbiCheck<'a, 'tcx> {
+    tcx: TyCtxt<'tcx>,
+    body: &'a mir::Body<'tcx>,
+    instance: Instance<'tcx>,
+}
+
+impl<'a, 'tcx> MirVisitor<'tcx> for MirCallesAbiCheck<'a, 'tcx> {
+    fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, _: Location) {
+        match terminator.kind {
+            mir::TerminatorKind::Call { ref func, ref fn_span, .. }
+            | mir::TerminatorKind::TailCall { ref func, ref fn_span, .. } => {
+                let callee_ty = func.ty(self.body, self.tcx);
+                let callee_ty = self.instance.instantiate_mir_and_normalize_erasing_regions(
+                    self.tcx,
+                    ty::ParamEnv::reveal_all(),
+                    ty::EarlyBinder::bind(callee_ty),
+                );
+                check_call_site_abi(self.tcx, callee_ty, *fn_span, self.body.source.instance);
+            }
+            _ => {}
+        }
+    }
+}
+
+fn check_callees_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
+    let body = tcx.instance_mir(instance.def);
+    let mut visitor = MirCallesAbiCheck { tcx, body, instance };
+    for (bb, data) in traversal::mono_reachable(body, tcx, instance) {
+        visitor.visit_basic_block_data(bb, data)
+    }
+}
+
+fn check_feature_dependent_abi<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) {
+    check_instance_abi(tcx, instance);
+    check_callees_abi(tcx, instance);
+}
+
+pub(super) fn provide(providers: &mut Providers) {
+    *providers = Providers { check_feature_dependent_abi, ..*providers }
+}
diff --git a/compiler/rustc_monomorphize/src/errors.rs b/compiler/rustc_monomorphize/src/errors.rs
index d5fae6e23cb..5048a8d5d99 100644
--- a/compiler/rustc_monomorphize/src/errors.rs
+++ b/compiler/rustc_monomorphize/src/errors.rs
@@ -92,3 +92,21 @@ pub(crate) struct StartNotFound;
 pub(crate) struct UnknownCguCollectionMode<'a> {
     pub mode: &'a str,
 }
+
+#[derive(LintDiagnostic)]
+#[diag(monomorphize_abi_error_disabled_vector_type_def)]
+#[help]
+pub(crate) struct AbiErrorDisabledVectorTypeDef<'a> {
+    #[label]
+    pub span: Span,
+    pub required_feature: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[diag(monomorphize_abi_error_disabled_vector_type_call)]
+#[help]
+pub(crate) struct AbiErrorDisabledVectorTypeCall<'a> {
+    #[label]
+    pub span: Span,
+    pub required_feature: &'a str,
+}
diff --git a/compiler/rustc_target/src/target_features.rs b/compiler/rustc_target/src/target_features.rs
index 96f1e257bb5..4dbaf1f7c95 100644
--- a/compiler/rustc_target/src/target_features.rs
+++ b/compiler/rustc_target/src/target_features.rs
@@ -567,6 +567,13 @@ pub fn all_rust_features() -> impl Iterator<Item = (&'static str, Stability)> {
         .map(|(f, s, _)| (f, s))
 }
 
+// These arrays represent the least-constraining feature that is required for vector types up to a
+// certain size to have their "proper" ABI on each architecture.
+// Note that they must be kept sorted by vector size.
+const X86_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] =
+    &[(128, "sse"), (256, "avx"), (512, "avx512f")];
+const AARCH64_FEATURES_FOR_CORRECT_VECTOR_ABI: &'static [(u64, &'static str)] = &[(128, "neon")];
+
 impl super::spec::Target {
     pub fn rust_target_features(&self) -> &'static [(&'static str, Stability, ImpliedFeatures)] {
         match &*self.arch {
@@ -586,6 +593,16 @@ impl super::spec::Target {
         }
     }
 
+    // Returns None if we do not support ABI checks on the given target yet.
+    pub fn features_for_correct_vector_abi(&self) -> Option<&'static [(u64, &'static str)]> {
+        match &*self.arch {
+            "x86" | "x86_64" => Some(X86_FEATURES_FOR_CORRECT_VECTOR_ABI),
+            "aarch64" => Some(AARCH64_FEATURES_FOR_CORRECT_VECTOR_ABI),
+            // FIXME: add support for non-tier1 architectures
+            _ => None,
+        }
+    }
+
     pub fn tied_target_features(&self) -> &'static [&'static [&'static str]] {
         match &*self.arch {
             "aarch64" | "arm64ec" => AARCH64_TIED_FEATURES,
diff --git a/src/tools/miri/.github/workflows/ci.yml b/src/tools/miri/.github/workflows/ci.yml
index 2e491319822..209fd622202 100644
--- a/src/tools/miri/.github/workflows/ci.yml
+++ b/src/tools/miri/.github/workflows/ci.yml
@@ -58,11 +58,20 @@ jobs:
       - name: rustdoc
         run: RUSTDOCFLAGS="-Dwarnings" ./miri doc --document-private-items
 
+  coverage:
+    name: coverage report
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: ./.github/workflows/setup
+      - name: coverage
+        run: ./miri test --coverage
+
   # Summary job for the merge queue.
   # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB!
   # And they should be added below in `cron-fail-notify` as well.
   conclusion:
-    needs: [build, style]
+    needs: [build, style, coverage]
     # We need to ensure this job does *not* get skipped if its dependencies fail,
     # because a skipped job is considered a success by GitHub. So we have to
     # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run
@@ -86,7 +95,7 @@ jobs:
         contents: write
         # ... and create a PR.
         pull-requests: write
-    needs: [build, style]
+    needs: [build, style, coverage]
     if: ${{ github.event_name == 'schedule' && failure() }}
     steps:
       # Send a Zulip notification
diff --git a/src/tools/miri/CONTRIBUTING.md b/src/tools/miri/CONTRIBUTING.md
index d0bcf68eacb..e97f4bf86d8 100644
--- a/src/tools/miri/CONTRIBUTING.md
+++ b/src/tools/miri/CONTRIBUTING.md
@@ -13,6 +13,25 @@ for a list of Miri maintainers.
 
 [Rust Zulip]: https://rust-lang.zulipchat.com
 
+### Pull review process
+
+When you get a review, please take care of the requested changes in new commits. Do not amend
+existing commits. Generally avoid force-pushing. The only time you should force push is when there
+is a conflict with the master branch (in that case you should rebase across master, not merge), and
+all the way at the end of the review process when the reviewer tells you that the PR is done and you
+should squash the commits. For the latter case, use `git rebase --keep-base ...` to squash without
+changing the base commit your PR branches off of. Use your own judgment and the reviewer's guidance
+to decide whether the PR should be squashed into a single commit or multiple logically separate
+commits. (All this is to work around the fact that Github is quite bad at dealing with force pushes
+and does not support `git range-diff`. Maybe one day Github will be good at git and then life can
+become easier.)
+
+Most PRs bounce back and forth between the reviewer and the author several times, so it is good to
+keep track of who is expected to take the next step. We are using the `S-waiting-for-review` and
+`S-waiting-for-author` labels for that. If a reviewer asked you to do some changes and you think
+they are all taken care of, post a comment saying `@rustbot ready` to mark a PR as ready for the
+next round of review.
+
 ### Larger-scale contributions
 
 If you are thinking about making a larger-scale contribution -- in particular anything that needs
@@ -45,14 +64,6 @@ process for such contributions:
 This process is largely informal, and its primary goal is to more clearly communicate expectations.
 Please get in touch with us if you have any questions!
 
-### Managing the review state
-
-Most PRs bounce back and forth between the reviewer and the author several times, so it is good to
-keep track of who is expected to take the next step. We are using the `S-waiting-for-review` and
-`S-waiting-for-author` labels for that. If a reviewer asked you to do some changes and you think
-they are all taken care of, post a comment saying `@rustbot ready` to mark a PR as ready for the
-next round of review.
-
 ## Preparing the build environment
 
 Miri heavily relies on internal and unstable rustc interfaces to execute MIR,
diff --git a/src/tools/miri/ci/ci.sh b/src/tools/miri/ci/ci.sh
index 4e7cbc50ca0..0356d7ecf10 100755
--- a/src/tools/miri/ci/ci.sh
+++ b/src/tools/miri/ci/ci.sh
@@ -154,7 +154,7 @@ case $HOST_TARGET in
     TEST_TARGET=i686-unknown-freebsd   run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe
     TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe
     TEST_TARGET=x86_64-pc-solaris      run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe
-    TEST_TARGET=aarch64-linux-android  run_tests_minimal $BASIC $UNIX time hashmap threadname pthread
+    TEST_TARGET=aarch64-linux-android  run_tests_minimal $BASIC $UNIX time hashmap random sync threadname pthread
     TEST_TARGET=wasm32-wasip2          run_tests_minimal $BASIC wasm
     TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std
     TEST_TARGET=thumbv7em-none-eabihf  run_tests_minimal no_std
diff --git a/src/tools/miri/clippy.toml b/src/tools/miri/clippy.toml
index c11912d6e68..504be47459c 100644
--- a/src/tools/miri/clippy.toml
+++ b/src/tools/miri/clippy.toml
@@ -1 +1 @@
-arithmetic-side-effects-allowed = ["rustc_abi::Size"]
+arithmetic-side-effects-allowed = ["rustc_abi::Size", "rustc_apfloat::ieee::IeeeFloat"]
diff --git a/src/tools/miri/miri-script/Cargo.lock b/src/tools/miri/miri-script/Cargo.lock
index 146e613c24b..8dad30df6d1 100644
--- a/src/tools/miri/miri-script/Cargo.lock
+++ b/src/tools/miri/miri-script/Cargo.lock
@@ -64,6 +64,12 @@ dependencies = [
 ]
 
 [[package]]
+name = "fastrand"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
+
+[[package]]
 name = "getrandom"
 version = "0.2.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -100,9 +106,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
 [[package]]
 name = "libc"
-version = "0.2.153"
+version = "0.2.159"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
+checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
 
 [[package]]
 name = "libredox"
@@ -138,12 +144,19 @@ dependencies = [
  "rustc_version",
  "serde_json",
  "shell-words",
+ "tempfile",
  "walkdir",
  "which",
  "xshell",
 ]
 
 [[package]]
+name = "once_cell"
+version = "1.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+
+[[package]]
 name = "option-ext"
 version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -195,9 +208,9 @@ dependencies = [
 
 [[package]]
 name = "rustix"
-version = "0.38.34"
+version = "0.38.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
 dependencies = [
  "bitflags",
  "errno",
@@ -277,6 +290,19 @@ dependencies = [
 ]
 
 [[package]]
+name = "tempfile"
+version = "3.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
 name = "thiserror"
 version = "1.0.57"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -358,6 +384,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
 name = "windows-targets"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/src/tools/miri/miri-script/Cargo.toml b/src/tools/miri/miri-script/Cargo.toml
index 23b9a625159..5b31d5a6ff9 100644
--- a/src/tools/miri/miri-script/Cargo.toml
+++ b/src/tools/miri/miri-script/Cargo.toml
@@ -24,3 +24,4 @@ rustc_version = "0.4"
 dunce = "1.0.4"
 directories = "5"
 serde_json = "1"
+tempfile = "3.13.0"
diff --git a/src/tools/miri/miri-script/src/commands.rs b/src/tools/miri/miri-script/src/commands.rs
index 36175c8dd2b..21029d0b5b3 100644
--- a/src/tools/miri/miri-script/src/commands.rs
+++ b/src/tools/miri/miri-script/src/commands.rs
@@ -172,7 +172,8 @@ impl Command {
             Command::Install { flags } => Self::install(flags),
             Command::Build { flags } => Self::build(flags),
             Command::Check { flags } => Self::check(flags),
-            Command::Test { bless, flags, target } => Self::test(bless, flags, target),
+            Command::Test { bless, flags, target, coverage } =>
+                Self::test(bless, flags, target, coverage),
             Command::Run { dep, verbose, many_seeds, target, edition, flags } =>
                 Self::run(dep, verbose, many_seeds, target, edition, flags),
             Command::Doc { flags } => Self::doc(flags),
@@ -458,9 +459,20 @@ impl Command {
         Ok(())
     }
 
-    fn test(bless: bool, mut flags: Vec<String>, target: Option<String>) -> Result<()> {
+    fn test(
+        bless: bool,
+        mut flags: Vec<String>,
+        target: Option<String>,
+        coverage: bool,
+    ) -> Result<()> {
         let mut e = MiriEnv::new()?;
 
+        let coverage = coverage.then_some(crate::coverage::CoverageReport::new()?);
+
+        if let Some(report) = &coverage {
+            report.add_env_vars(&mut e)?;
+        }
+
         // Prepare a sysroot. (Also builds cargo-miri, which we need.)
         e.build_miri_sysroot(/* quiet */ false, target.as_deref())?;
 
@@ -479,6 +491,11 @@ impl Command {
         // Then test, and let caller control flags.
         // Only in root project as `cargo-miri` has no tests.
         e.test(".", &flags)?;
+
+        if let Some(coverage) = &coverage {
+            coverage.show_coverage_report(&e)?;
+        }
+
         Ok(())
     }
 
diff --git a/src/tools/miri/miri-script/src/coverage.rs b/src/tools/miri/miri-script/src/coverage.rs
new file mode 100644
index 00000000000..8cafcea0d16
--- /dev/null
+++ b/src/tools/miri/miri-script/src/coverage.rs
@@ -0,0 +1,91 @@
+use std::path::PathBuf;
+
+use anyhow::{Context, Result};
+use path_macro::path;
+use tempfile::TempDir;
+use xshell::cmd;
+
+use crate::util::MiriEnv;
+
+/// CoverageReport can generate code coverage reports for miri.
+pub struct CoverageReport {
+    /// path is a temporary directory where intermediate coverage artifacts will be stored.
+    /// (The final output will be stored in a permanent location.)
+    path: TempDir,
+}
+
+impl CoverageReport {
+    /// Creates a new CoverageReport.
+    ///
+    /// # Errors
+    ///
+    /// An error will be returned if a temporary directory could not be created.
+    pub fn new() -> Result<Self> {
+        Ok(Self { path: TempDir::new()? })
+    }
+
+    /// add_env_vars will add the required environment variables to MiriEnv `e`.
+    pub fn add_env_vars(&self, e: &mut MiriEnv) -> Result<()> {
+        let mut rustflags = e.sh.var("RUSTFLAGS")?;
+        rustflags.push_str(" -C instrument-coverage");
+        e.sh.set_var("RUSTFLAGS", rustflags);
+
+        // Copy-pasting from: https://doc.rust-lang.org/rustc/instrument-coverage.html#instrumentation-based-code-coverage
+        // The format symbols below have the following meaning:
+        // - %p - The process ID.
+        // - %Nm - the instrumented binary’s signature:
+        //   The runtime creates a pool of N raw profiles, used for on-line
+        //   profile merging. The runtime takes care of selecting a raw profile
+        //   from the pool, locking it, and updating it before the program
+        //   exits. N must be between 1 and 9, and defaults to 1 if omitted
+        //   (with simply %m).
+        //
+        // Additionally the default for LLVM_PROFILE_FILE is default_%m_%p.profraw.
+        // So we just use the same template, replacing "default" with "miri".
+        let file_template = self.path.path().join("miri_%m_%p.profraw");
+        e.sh.set_var("LLVM_PROFILE_FILE", file_template);
+        Ok(())
+    }
+
+    /// show_coverage_report will print coverage information using the artifact
+    /// files in `self.path`.
+    pub fn show_coverage_report(&self, e: &MiriEnv) -> Result<()> {
+        let profraw_files = self.profraw_files()?;
+
+        let profdata_bin = path!(e.libdir / ".." / "bin" / "llvm-profdata");
+
+        let merged_file = path!(e.miri_dir / "target" / "coverage.profdata");
+
+        // Merge the profraw files
+        cmd!(e.sh, "{profdata_bin} merge -sparse {profraw_files...} -o {merged_file}")
+            .quiet()
+            .run()?;
+
+        // Create the coverage report.
+        let cov_bin = path!(e.libdir / ".." / "bin" / "llvm-cov");
+        let miri_bin =
+            e.build_get_binary(".").context("failed to get filename of miri executable")?;
+        cmd!(
+            e.sh,
+            "{cov_bin} report --instr-profile={merged_file} --object {miri_bin} --sources src/"
+        )
+        .run()?;
+
+        println!("Profile data saved in {}", merged_file.display());
+        Ok(())
+    }
+
+    /// profraw_files returns the profraw files in `self.path`.
+    ///
+    /// # Errors
+    ///
+    /// An error will be returned if `self.path` can't be read.
+    fn profraw_files(&self) -> Result<Vec<PathBuf>> {
+        Ok(std::fs::read_dir(&self.path)?
+            .filter_map(|r| r.ok())
+            .filter(|e| e.file_type().is_ok_and(|t| t.is_file()))
+            .map(|e| e.path())
+            .filter(|p| p.extension().is_some_and(|e| e == "profraw"))
+            .collect())
+    }
+}
diff --git a/src/tools/miri/miri-script/src/main.rs b/src/tools/miri/miri-script/src/main.rs
index 0620f3aaf09..a329f627903 100644
--- a/src/tools/miri/miri-script/src/main.rs
+++ b/src/tools/miri/miri-script/src/main.rs
@@ -2,6 +2,7 @@
 
 mod args;
 mod commands;
+mod coverage;
 mod util;
 
 use std::ops::Range;
@@ -34,6 +35,8 @@ pub enum Command {
         /// The cross-interpretation target.
         /// If none then the host is the target.
         target: Option<String>,
+        /// Produce coverage report if set.
+        coverage: bool,
         /// Flags that are passed through to the test harness.
         flags: Vec<String>,
     },
@@ -158,9 +161,12 @@ fn main() -> Result<()> {
             let mut target = None;
             let mut bless = false;
             let mut flags = Vec::new();
+            let mut coverage = false;
             loop {
                 if args.get_long_flag("bless")? {
                     bless = true;
+                } else if args.get_long_flag("coverage")? {
+                    coverage = true;
                 } else if let Some(val) = args.get_long_opt("target")? {
                     target = Some(val);
                 } else if let Some(flag) = args.get_other() {
@@ -169,7 +175,7 @@ fn main() -> Result<()> {
                     break;
                 }
             }
-            Command::Test { bless, flags, target }
+            Command::Test { bless, flags, target, coverage }
         }
         Some("run") => {
             let mut dep = false;
diff --git a/src/tools/miri/miri-script/src/util.rs b/src/tools/miri/miri-script/src/util.rs
index f5a6a8188a0..e6e85747d4d 100644
--- a/src/tools/miri/miri-script/src/util.rs
+++ b/src/tools/miri/miri-script/src/util.rs
@@ -41,6 +41,8 @@ pub struct MiriEnv {
     pub sysroot: PathBuf,
     /// The shell we use.
     pub sh: Shell,
+    /// The library dir in the sysroot.
+    pub libdir: PathBuf,
 }
 
 impl MiriEnv {
@@ -96,7 +98,8 @@ impl MiriEnv {
         // so that Windows can find the DLLs.
         if cfg!(windows) {
             let old_path = sh.var("PATH")?;
-            let new_path = env::join_paths(iter::once(libdir).chain(env::split_paths(&old_path)))?;
+            let new_path =
+                env::join_paths(iter::once(libdir.clone()).chain(env::split_paths(&old_path)))?;
             sh.set_var("PATH", new_path);
         }
 
@@ -111,7 +114,7 @@ impl MiriEnv {
             std::process::exit(1);
         }
 
-        Ok(MiriEnv { miri_dir, toolchain, sh, sysroot, cargo_extra_flags })
+        Ok(MiriEnv { miri_dir, toolchain, sh, sysroot, cargo_extra_flags, libdir })
     }
 
     pub fn cargo_cmd(&self, crate_dir: impl AsRef<OsStr>, cmd: &str) -> Cmd<'_> {
diff --git a/src/tools/miri/rust-version b/src/tools/miri/rust-version
index 133edd3191d..5adbfbfebfb 100644
--- a/src/tools/miri/rust-version
+++ b/src/tools/miri/rust-version
@@ -1 +1 @@
-814df6e50eaf89b90793e7d9618bb60f1f18377a
+328b759142ddeae96da83176f103200009d3e3f1
diff --git a/src/tools/miri/src/alloc_addresses/mod.rs b/src/tools/miri/src/alloc_addresses/mod.rs
index 7b377a1c4cd..f1b7811f905 100644
--- a/src/tools/miri/src/alloc_addresses/mod.rs
+++ b/src/tools/miri/src/alloc_addresses/mod.rs
@@ -111,8 +111,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
     // Returns the exposed `AllocId` that corresponds to the specified addr,
     // or `None` if the addr is out of bounds
     fn alloc_id_from_addr(&self, addr: u64, size: i64) -> Option<AllocId> {
-        let ecx = self.eval_context_ref();
-        let global_state = ecx.machine.alloc_addresses.borrow();
+        let this = self.eval_context_ref();
+        let global_state = this.machine.alloc_addresses.borrow();
         assert!(global_state.provenance_mode != ProvenanceMode::Strict);
 
         // We always search the allocation to the right of this address. So if the size is structly
@@ -134,7 +134,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // entered for addresses that are not the base address, so even zero-sized
                 // allocations will get recognized at their base address -- but all other
                 // allocations will *not* be recognized at their "end" address.
-                let size = ecx.get_alloc_info(alloc_id).0;
+                let size = this.get_alloc_info(alloc_id).0;
                 if offset < size.bytes() { Some(alloc_id) } else { None }
             }
         }?;
@@ -142,7 +142,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
         // We only use this provenance if it has been exposed.
         if global_state.exposed.contains(&alloc_id) {
             // This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed.
-            debug_assert!(ecx.is_alloc_live(alloc_id));
+            debug_assert!(this.is_alloc_live(alloc_id));
             Some(alloc_id)
         } else {
             None
@@ -155,9 +155,9 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
         alloc_id: AllocId,
         memory_kind: MemoryKind,
     ) -> InterpResult<'tcx, u64> {
-        let ecx = self.eval_context_ref();
-        let mut rng = ecx.machine.rng.borrow_mut();
-        let (size, align, kind) = ecx.get_alloc_info(alloc_id);
+        let this = self.eval_context_ref();
+        let mut rng = this.machine.rng.borrow_mut();
+        let (size, align, kind) = this.get_alloc_info(alloc_id);
         // This is either called immediately after allocation (and then cached), or when
         // adjusting `tcx` pointers (which never get freed). So assert that we are looking
         // at a live allocation. This also ensures that we never re-assign an address to an
@@ -166,12 +166,12 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
         assert!(!matches!(kind, AllocKind::Dead));
 
         // This allocation does not have a base address yet, pick or reuse one.
-        if ecx.machine.native_lib.is_some() {
+        if this.machine.native_lib.is_some() {
             // In native lib mode, we use the "real" address of the bytes for this allocation.
             // This ensures the interpreted program and native code have the same view of memory.
             let base_ptr = match kind {
                 AllocKind::LiveData => {
-                    if ecx.tcx.try_get_global_alloc(alloc_id).is_some() {
+                    if this.tcx.try_get_global_alloc(alloc_id).is_some() {
                         // For new global allocations, we always pre-allocate the memory to be able use the machine address directly.
                         let prepared_bytes = MiriAllocBytes::zeroed(size, align)
                             .unwrap_or_else(|| {
@@ -185,7 +185,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                             .unwrap();
                         ptr
                     } else {
-                        ecx.get_alloc_bytes_unchecked_raw(alloc_id)?
+                        this.get_alloc_bytes_unchecked_raw(alloc_id)?
                     }
                 }
                 AllocKind::Function | AllocKind::VTable => {
@@ -204,10 +204,10 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
         }
         // We are not in native lib mode, so we control the addresses ourselves.
         if let Some((reuse_addr, clock)) =
-            global_state.reuse.take_addr(&mut *rng, size, align, memory_kind, ecx.active_thread())
+            global_state.reuse.take_addr(&mut *rng, size, align, memory_kind, this.active_thread())
         {
             if let Some(clock) = clock {
-                ecx.acquire_clock(&clock);
+                this.acquire_clock(&clock);
             }
             interp_ok(reuse_addr)
         } else {
@@ -230,7 +230,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 .checked_add(max(size.bytes(), 1))
                 .ok_or_else(|| err_exhaust!(AddressSpaceFull))?;
             // Even if `Size` didn't overflow, we might still have filled up the address space.
-            if global_state.next_base_addr > ecx.target_usize_max() {
+            if global_state.next_base_addr > this.target_usize_max() {
                 throw_exhaust!(AddressSpaceFull);
             }
 
@@ -243,8 +243,8 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
         alloc_id: AllocId,
         memory_kind: MemoryKind,
     ) -> InterpResult<'tcx, u64> {
-        let ecx = self.eval_context_ref();
-        let mut global_state = ecx.machine.alloc_addresses.borrow_mut();
+        let this = self.eval_context_ref();
+        let mut global_state = this.machine.alloc_addresses.borrow_mut();
         let global_state = &mut *global_state;
 
         match global_state.base_addr.get(&alloc_id) {
@@ -283,22 +283,22 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     fn expose_ptr(&mut self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
-        let ecx = self.eval_context_mut();
-        let global_state = ecx.machine.alloc_addresses.get_mut();
+        let this = self.eval_context_mut();
+        let global_state = this.machine.alloc_addresses.get_mut();
         // In strict mode, we don't need this, so we can save some cycles by not tracking it.
         if global_state.provenance_mode == ProvenanceMode::Strict {
             return interp_ok(());
         }
         // Exposing a dead alloc is a no-op, because it's not possible to get a dead allocation
         // via int2ptr.
-        if !ecx.is_alloc_live(alloc_id) {
+        if !this.is_alloc_live(alloc_id) {
             return interp_ok(());
         }
         trace!("Exposing allocation id {alloc_id:?}");
-        let global_state = ecx.machine.alloc_addresses.get_mut();
+        let global_state = this.machine.alloc_addresses.get_mut();
         global_state.exposed.insert(alloc_id);
-        if ecx.machine.borrow_tracker.is_some() {
-            ecx.expose_tag(alloc_id, tag)?;
+        if this.machine.borrow_tracker.is_some() {
+            this.expose_tag(alloc_id, tag)?;
         }
         interp_ok(())
     }
@@ -306,8 +306,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     fn ptr_from_addr_cast(&self, addr: u64) -> InterpResult<'tcx, Pointer> {
         trace!("Casting {:#x} to a pointer", addr);
 
-        let ecx = self.eval_context_ref();
-        let global_state = ecx.machine.alloc_addresses.borrow();
+        let this = self.eval_context_ref();
+        let global_state = this.machine.alloc_addresses.borrow();
 
         // Potentially emit a warning.
         match global_state.provenance_mode {
@@ -319,9 +319,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }
                 PAST_WARNINGS.with_borrow_mut(|past_warnings| {
                     let first = past_warnings.is_empty();
-                    if past_warnings.insert(ecx.cur_span()) {
+                    if past_warnings.insert(this.cur_span()) {
                         // Newly inserted, so first time we see this span.
-                        ecx.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first });
+                        this.emit_diagnostic(NonHaltingDiagnostic::Int2Ptr { details: first });
                     }
                 });
             }
@@ -347,19 +347,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         tag: BorTag,
         kind: MemoryKind,
     ) -> InterpResult<'tcx, interpret::Pointer<Provenance>> {
-        let ecx = self.eval_context_ref();
+        let this = self.eval_context_ref();
 
         let (prov, offset) = ptr.into_parts(); // offset is relative (AllocId provenance)
         let alloc_id = prov.alloc_id();
 
         // Get a pointer to the beginning of this allocation.
-        let base_addr = ecx.addr_from_alloc_id(alloc_id, kind)?;
+        let base_addr = this.addr_from_alloc_id(alloc_id, kind)?;
         let base_ptr = interpret::Pointer::new(
             Provenance::Concrete { alloc_id, tag },
             Size::from_bytes(base_addr),
         );
         // Add offset with the right kind of pointer-overflowing arithmetic.
-        interp_ok(base_ptr.wrapping_offset(offset, ecx))
+        interp_ok(base_ptr.wrapping_offset(offset, this))
     }
 
     // This returns some prepared `MiriAllocBytes`, either because `addr_from_alloc_id` reserved
@@ -371,16 +371,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         bytes: &[u8],
         align: Align,
     ) -> InterpResult<'tcx, MiriAllocBytes> {
-        let ecx = self.eval_context_ref();
-        if ecx.machine.native_lib.is_some() {
+        let this = self.eval_context_ref();
+        if this.machine.native_lib.is_some() {
             // In native lib mode, MiriAllocBytes for global allocations are handled via `prepared_alloc_bytes`.
             // This additional call ensures that some `MiriAllocBytes` are always prepared, just in case
             // this function gets called before the first time `addr_from_alloc_id` gets called.
-            ecx.addr_from_alloc_id(id, kind)?;
+            this.addr_from_alloc_id(id, kind)?;
             // The memory we need here will have already been allocated during an earlier call to
             // `addr_from_alloc_id` for this allocation. So don't create a new `MiriAllocBytes` here, instead
             // fetch the previously prepared bytes from `prepared_alloc_bytes`.
-            let mut global_state = ecx.machine.alloc_addresses.borrow_mut();
+            let mut global_state = this.machine.alloc_addresses.borrow_mut();
             let mut prepared_alloc_bytes = global_state
                 .prepared_alloc_bytes
                 .remove(&id)
@@ -403,7 +403,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         ptr: interpret::Pointer<Provenance>,
         size: i64,
     ) -> Option<(AllocId, Size)> {
-        let ecx = self.eval_context_ref();
+        let this = self.eval_context_ref();
 
         let (tag, addr) = ptr.into_parts(); // addr is absolute (Tag provenance)
 
@@ -411,15 +411,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             alloc_id
         } else {
             // A wildcard pointer.
-            ecx.alloc_id_from_addr(addr.bytes(), size)?
+            this.alloc_id_from_addr(addr.bytes(), size)?
         };
 
         // This cannot fail: since we already have a pointer with that provenance, adjust_alloc_root_pointer
         // must have been called in the past, so we can just look up the address in the map.
-        let base_addr = *ecx.machine.alloc_addresses.borrow().base_addr.get(&alloc_id).unwrap();
+        let base_addr = *this.machine.alloc_addresses.borrow().base_addr.get(&alloc_id).unwrap();
 
         // Wrapping "addr - base_addr"
-        let rel_offset = ecx.truncate_to_target_usize(addr.bytes().wrapping_sub(base_addr));
+        let rel_offset = this.truncate_to_target_usize(addr.bytes().wrapping_sub(base_addr));
         Some((alloc_id, Size::from_bytes(rel_offset)))
     }
 }
diff --git a/src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs b/src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs
index f024796c0a7..dc3370f1251 100644
--- a/src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs
+++ b/src/tools/miri/src/borrow_tracker/stacked_borrows/stack.rs
@@ -354,7 +354,7 @@ impl<'tcx> Stack {
         self.borrows.get(idx).cloned()
     }
 
-    #[allow(clippy::len_without_is_empty)] // Stacks are never empty
+    #[expect(clippy::len_without_is_empty)] // Stacks are never empty
     pub fn len(&self) -> usize {
         self.borrows.len()
     }
diff --git a/src/tools/miri/src/concurrency/sync.rs b/src/tools/miri/src/concurrency/sync.rs
index fcd5d464114..78e5ad5deb2 100644
--- a/src/tools/miri/src/concurrency/sync.rs
+++ b/src/tools/miri/src/concurrency/sync.rs
@@ -696,7 +696,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         retval_succ: Scalar,
         retval_timeout: Scalar,
         dest: MPlaceTy<'tcx>,
-        errno_timeout: Scalar,
+        errno_timeout: IoError,
     ) {
         let this = self.eval_context_mut();
         let thread = this.active_thread();
@@ -713,7 +713,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     retval_succ: Scalar,
                     retval_timeout: Scalar,
                     dest: MPlaceTy<'tcx>,
-                    errno_timeout: Scalar,
+                    errno_timeout: IoError,
                 }
                 @unblock = |this| {
                     let futex = this.machine.sync.futexes.get(&addr).unwrap();
diff --git a/src/tools/miri/src/concurrency/thread.rs b/src/tools/miri/src/concurrency/thread.rs
index 281242bf373..7477494281d 100644
--- a/src/tools/miri/src/concurrency/thread.rs
+++ b/src/tools/miri/src/concurrency/thread.rs
@@ -1,7 +1,6 @@
 //! Implements threads.
 
 use std::mem;
-use std::num::TryFromIntError;
 use std::sync::atomic::Ordering::Relaxed;
 use std::task::Poll;
 use std::time::{Duration, SystemTime};
@@ -127,26 +126,6 @@ impl Idx for ThreadId {
     }
 }
 
-impl TryFrom<u64> for ThreadId {
-    type Error = TryFromIntError;
-    fn try_from(id: u64) -> Result<Self, Self::Error> {
-        u32::try_from(id).map(Self)
-    }
-}
-
-impl TryFrom<i128> for ThreadId {
-    type Error = TryFromIntError;
-    fn try_from(id: i128) -> Result<Self, Self::Error> {
-        u32::try_from(id).map(Self)
-    }
-}
-
-impl From<u32> for ThreadId {
-    fn from(id: u32) -> Self {
-        Self(id)
-    }
-}
-
 impl From<ThreadId> for u64 {
     fn from(t: ThreadId) -> Self {
         t.0.into()
@@ -448,6 +427,10 @@ pub enum TimeoutAnchor {
     Absolute,
 }
 
+/// An error signaling that the requested thread doesn't exist.
+#[derive(Debug, Copy, Clone)]
+pub struct ThreadNotFound;
+
 /// A set of threads.
 #[derive(Debug)]
 pub struct ThreadManager<'tcx> {
@@ -509,6 +492,16 @@ impl<'tcx> ThreadManager<'tcx> {
         }
     }
 
+    pub fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadNotFound> {
+        if let Ok(id) = id.try_into()
+            && usize::try_from(id).is_ok_and(|id| id < self.threads.len())
+        {
+            Ok(ThreadId(id))
+        } else {
+            Err(ThreadNotFound)
+        }
+    }
+
     /// Check if we have an allocation for the given thread local static for the
     /// active thread.
     fn get_thread_local_alloc_id(&self, def_id: DefId) -> Option<StrictPointer> {
@@ -534,6 +527,7 @@ impl<'tcx> ThreadManager<'tcx> {
     ) -> &mut Vec<Frame<'tcx, Provenance, FrameExtra<'tcx>>> {
         &mut self.threads[self.active_thread].stack
     }
+
     pub fn all_stacks(
         &self,
     ) -> impl Iterator<Item = (ThreadId, &[Frame<'tcx, Provenance, FrameExtra<'tcx>>])> {
@@ -868,6 +862,11 @@ trait EvalContextPrivExt<'tcx>: MiriInterpCxExt<'tcx> {
 // Public interface to thread management.
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
+    #[inline]
+    fn thread_id_try_from(&self, id: impl TryInto<u32>) -> Result<ThreadId, ThreadNotFound> {
+        self.eval_context_ref().machine.threads.thread_id_try_from(id)
+    }
+
     /// Get a thread-specific allocation id for the given thread-local static.
     /// If needed, allocate a new one.
     fn get_or_create_thread_local_alloc(
@@ -1160,8 +1159,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     /// Set the name of the current thread. The buffer must not include the null terminator.
     #[inline]
     fn set_thread_name(&mut self, thread: ThreadId, new_thread_name: Vec<u8>) {
-        let this = self.eval_context_mut();
-        this.machine.threads.set_thread_name(thread, new_thread_name);
+        self.eval_context_mut().machine.threads.set_thread_name(thread, new_thread_name);
     }
 
     #[inline]
diff --git a/src/tools/miri/src/concurrency/weak_memory.rs b/src/tools/miri/src/concurrency/weak_memory.rs
index 800c301a821..c610f1999f7 100644
--- a/src/tools/miri/src/concurrency/weak_memory.rs
+++ b/src/tools/miri/src/concurrency/weak_memory.rs
@@ -300,7 +300,6 @@ impl<'tcx> StoreBuffer {
         interp_ok(())
     }
 
-    #[allow(clippy::if_same_then_else, clippy::needless_bool)]
     /// Selects a valid store element in the buffer.
     fn fetch_store<R: rand::Rng + ?Sized>(
         &self,
diff --git a/src/tools/miri/src/eval.rs b/src/tools/miri/src/eval.rs
index cbd93fbd047..1e56e104918 100644
--- a/src/tools/miri/src/eval.rs
+++ b/src/tools/miri/src/eval.rs
@@ -423,7 +423,7 @@ pub fn create_ecx<'tcx>(
 /// Evaluates the entry function specified by `entry_id`.
 /// Returns `Some(return_code)` if program executed completed.
 /// Returns `None` if an evaluation error occurred.
-#[allow(clippy::needless_lifetimes)]
+#[expect(clippy::needless_lifetimes)]
 pub fn eval_entry<'tcx>(
     tcx: TyCtxt<'tcx>,
     entry_id: DefId,
diff --git a/src/tools/miri/src/helpers.rs b/src/tools/miri/src/helpers.rs
index 74439d36e32..526030bef2e 100644
--- a/src/tools/miri/src/helpers.rs
+++ b/src/tools/miri/src/helpers.rs
@@ -156,7 +156,7 @@ pub fn iter_exported_symbols<'tcx>(
     for cnum in dependency_format.1.iter().enumerate().filter_map(|(num, &linkage)| {
         // We add 1 to the number because that's what rustc also does everywhere it
         // calls `CrateNum::new`...
-        #[allow(clippy::arithmetic_side_effects)]
+        #[expect(clippy::arithmetic_side_effects)]
         (linkage != Linkage::NotLinked).then_some(CrateNum::new(num + 1))
     }) {
         // We can ignore `_export_info` here: we are a Rust crate, and everything is exported
diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs
index 895beec507b..272dca1594e 100644
--- a/src/tools/miri/src/intrinsics/mod.rs
+++ b/src/tools/miri/src/intrinsics/mod.rs
@@ -292,7 +292,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let b = this.read_scalar(b)?.to_f32()?;
                 let c = this.read_scalar(c)?.to_f32()?;
                 let fuse: bool = this.machine.rng.get_mut().gen();
-                #[allow(clippy::arithmetic_side_effects)] // float ops don't overflow
                 let res = if fuse {
                     // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
                     a.to_host().mul_add(b.to_host(), c.to_host()).to_soft()
@@ -308,7 +307,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let b = this.read_scalar(b)?.to_f64()?;
                 let c = this.read_scalar(c)?.to_f64()?;
                 let fuse: bool = this.machine.rng.get_mut().gen();
-                #[allow(clippy::arithmetic_side_effects)] // float ops don't overflow
                 let res = if fuse {
                     // FIXME: Using host floats, to work around https://github.com/rust-lang/rustc_apfloat/issues/11
                     a.to_host().mul_add(b.to_host(), c.to_host()).to_soft()
diff --git a/src/tools/miri/src/intrinsics/simd.rs b/src/tools/miri/src/intrinsics/simd.rs
index 38a67802749..d5c417e7231 100644
--- a/src/tools/miri/src/intrinsics/simd.rs
+++ b/src/tools/miri/src/intrinsics/simd.rs
@@ -750,7 +750,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
                     let val = if simd_element_to_bool(mask)? {
                         // Size * u64 is implemented as always checked
-                        #[allow(clippy::arithmetic_side_effects)]
                         let ptr = ptr.wrapping_offset(dest.layout.size * i, this);
                         let place = this.ptr_to_mplace(ptr, dest.layout);
                         this.read_immediate(&place)?
@@ -774,7 +773,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
                     if simd_element_to_bool(mask)? {
                         // Size * u64 is implemented as always checked
-                        #[allow(clippy::arithmetic_side_effects)]
                         let ptr = ptr.wrapping_offset(val.layout.size * i, this);
                         let place = this.ptr_to_mplace(ptr, val.layout);
                         this.write_immediate(*val, &place)?
@@ -831,7 +829,7 @@ fn simd_bitmask_index(idx: u32, vec_len: u32, endianness: Endian) -> u32 {
     assert!(idx < vec_len);
     match endianness {
         Endian::Little => idx,
-        #[allow(clippy::arithmetic_side_effects)] // idx < vec_len
+        #[expect(clippy::arithmetic_side_effects)] // idx < vec_len
         Endian::Big => vec_len - 1 - idx, // reverse order of bits
     }
 }
diff --git a/src/tools/miri/src/provenance_gc.rs b/src/tools/miri/src/provenance_gc.rs
index c5a35bc14f5..6042a9eb2eb 100644
--- a/src/tools/miri/src/provenance_gc.rs
+++ b/src/tools/miri/src/provenance_gc.rs
@@ -89,6 +89,18 @@ impl VisitProvenance for Scalar {
     }
 }
 
+impl VisitProvenance for IoError {
+    fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
+        use crate::shims::io_error::IoError::*;
+        match self {
+            LibcError(_name) => (),
+            WindowsError(_name) => (),
+            HostError(_io_error) => (),
+            Raw(scalar) => scalar.visit_provenance(visit),
+        }
+    }
+}
+
 impl VisitProvenance for Immediate<Provenance> {
     fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
         match self {
diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs
index 18578c7acc9..8f7c56a2907 100644
--- a/src/tools/miri/src/shims/foreign_items.rs
+++ b/src/tools/miri/src/shims/foreign_items.rs
@@ -21,7 +21,7 @@ use crate::*;
 #[derive(Debug, Copy, Clone)]
 pub struct DynSym(Symbol);
 
-#[allow(clippy::should_implement_trait)]
+#[expect(clippy::should_implement_trait)]
 impl DynSym {
     pub fn from_str(name: &str) -> Self {
         DynSym(Symbol::intern(name))
@@ -648,7 +648,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let val = this.read_scalar(val)?.to_i32()?;
                 let num = this.read_target_usize(num)?;
                 // The docs say val is "interpreted as unsigned char".
-                #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
+                #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
                 let val = val as u8;
 
                 // C requires that this must always be a valid pointer (C18 §7.1.4).
@@ -661,7 +661,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                     .position(|&c| c == val)
                 {
                     let idx = u64::try_from(idx).unwrap();
-                    #[allow(clippy::arithmetic_side_effects)] // idx < num, so this never wraps
+                    #[expect(clippy::arithmetic_side_effects)] // idx < num, so this never wraps
                     let new_ptr = ptr.wrapping_offset(Size::from_bytes(num - idx - 1), this);
                     this.write_pointer(new_ptr, dest)?;
                 } else {
@@ -675,7 +675,7 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let val = this.read_scalar(val)?.to_i32()?;
                 let num = this.read_target_usize(num)?;
                 // The docs say val is "interpreted as unsigned char".
-                #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
+                #[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
                 let val = val as u8;
 
                 // C requires that this must always be a valid pointer (C18 §7.1.4).
diff --git a/src/tools/miri/src/shims/io_error.rs b/src/tools/miri/src/shims/io_error.rs
index 04491f0542b..0cbb4850b7f 100644
--- a/src/tools/miri/src/shims/io_error.rs
+++ b/src/tools/miri/src/shims/io_error.rs
@@ -7,6 +7,7 @@ use crate::*;
 #[derive(Debug)]
 pub enum IoError {
     LibcError(&'static str),
+    WindowsError(&'static str),
     HostError(io::Error),
     Raw(Scalar),
 }
@@ -113,6 +114,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let errno = match err.into() {
             HostError(err) => this.io_error_to_errnum(err)?,
             LibcError(name) => this.eval_libc(name),
+            WindowsError(name) => this.eval_windows("c", name),
             Raw(val) => val,
         };
         let errno_place = this.last_error_place()?;
@@ -186,7 +188,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     }
 
     /// The inverse of `io_error_to_errnum`.
-    #[allow(clippy::needless_return)]
+    #[expect(clippy::needless_return)]
     fn try_errnum_to_io_error(
         &self,
         errnum: Scalar,
diff --git a/src/tools/miri/src/shims/tls.rs b/src/tools/miri/src/shims/tls.rs
index 6e147c58571..46a417689a2 100644
--- a/src/tools/miri/src/shims/tls.rs
+++ b/src/tools/miri/src/shims/tls.rs
@@ -53,7 +53,7 @@ impl<'tcx> Default for TlsData<'tcx> {
 impl<'tcx> TlsData<'tcx> {
     /// Generate a new TLS key with the given destructor.
     /// `max_size` determines the integer size the key has to fit in.
-    #[allow(clippy::arithmetic_side_effects)]
+    #[expect(clippy::arithmetic_side_effects)]
     pub fn create_tls_key(
         &mut self,
         dtor: Option<ty::Instance<'tcx>>,
diff --git a/src/tools/miri/src/shims/unix/android/foreign_items.rs b/src/tools/miri/src/shims/unix/android/foreign_items.rs
index 27465c4e8bd..80ad40e1624 100644
--- a/src/tools/miri/src/shims/unix/android/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/android/foreign_items.rs
@@ -2,6 +2,7 @@ use rustc_abi::ExternAbi;
 use rustc_span::Symbol;
 
 use crate::shims::unix::android::thread::prctl;
+use crate::shims::unix::linux::syscall::syscall;
 use crate::*;
 
 pub fn is_dyn_sym(_name: &str) -> bool {
@@ -26,6 +27,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
             }
 
+            // Dynamically invoked syscalls
+            "syscall" => syscall(this, link_name, abi, args, dest)?,
+
             // Threading
             "prctl" => prctl(this, link_name, abi, args, dest)?,
 
diff --git a/src/tools/miri/src/shims/unix/android/thread.rs b/src/tools/miri/src/shims/unix/android/thread.rs
index 1da13d48252..093b7405ccd 100644
--- a/src/tools/miri/src/shims/unix/android/thread.rs
+++ b/src/tools/miri/src/shims/unix/android/thread.rs
@@ -2,7 +2,7 @@ use rustc_abi::{ExternAbi, Size};
 use rustc_span::Symbol;
 
 use crate::helpers::check_min_arg_count;
-use crate::shims::unix::thread::EvalContextExt as _;
+use crate::shims::unix::thread::{EvalContextExt as _, ThreadNameResult};
 use crate::*;
 
 const TASK_COMM_LEN: usize = 16;
@@ -32,7 +32,7 @@ pub fn prctl<'tcx>(
             // https://www.man7.org/linux/man-pages/man2/PR_SET_NAME.2const.html
             let res =
                 this.pthread_setname_np(thread, name, TASK_COMM_LEN, /* truncate */ true)?;
-            assert!(res);
+            assert_eq!(res, ThreadNameResult::Ok);
             Scalar::from_u32(0)
         }
         op if op == pr_get_name => {
@@ -46,7 +46,7 @@ pub fn prctl<'tcx>(
                 CheckInAllocMsg::MemoryAccessTest,
             )?;
             let res = this.pthread_getname_np(thread, name, len, /* truncate*/ false)?;
-            assert!(res);
+            assert_eq!(res, ThreadNameResult::Ok);
             Scalar::from_u32(0)
         }
         op => throw_unsup_format!("Miri does not support `prctl` syscall with op={}", op),
diff --git a/src/tools/miri/src/shims/unix/foreign_items.rs b/src/tools/miri/src/shims/unix/foreign_items.rs
index d59d6712c4f..55202a08149 100644
--- a/src/tools/miri/src/shims/unix/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/foreign_items.rs
@@ -603,13 +603,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             }
             "pthread_join" => {
                 let [thread, retval] = this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
-                this.pthread_join(thread, retval)?;
-                this.write_null(dest)?;
+                let res = this.pthread_join(thread, retval)?;
+                this.write_scalar(res, dest)?;
             }
             "pthread_detach" => {
                 let [thread] = this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
-                this.pthread_detach(thread)?;
-                this.write_null(dest)?;
+                let res = this.pthread_detach(thread)?;
+                this.write_scalar(res, dest)?;
             }
             "pthread_self" => {
                 let [] = this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
diff --git a/src/tools/miri/src/shims/unix/fs.rs b/src/tools/miri/src/shims/unix/fs.rs
index 7eaf33ace0f..091def7ac65 100644
--- a/src/tools/miri/src/shims/unix/fs.rs
+++ b/src/tools/miri/src/shims/unix/fs.rs
@@ -385,7 +385,7 @@ pub struct DirTable {
 }
 
 impl DirTable {
-    #[allow(clippy::arithmetic_side_effects)]
+    #[expect(clippy::arithmetic_side_effects)]
     fn insert_new(&mut self, read_dir: ReadDir) -> u64 {
         let id = self.next_id;
         self.next_id += 1;
diff --git a/src/tools/miri/src/shims/unix/linux/foreign_items.rs b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
index c7860ac99c3..85f0d6e1330 100644
--- a/src/tools/miri/src/shims/unix/linux/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/linux/foreign_items.rs
@@ -4,8 +4,7 @@ use rustc_span::Symbol;
 use self::shims::unix::linux::epoll::EvalContextExt as _;
 use self::shims::unix::linux::eventfd::EvalContextExt as _;
 use self::shims::unix::linux::mem::EvalContextExt as _;
-use self::shims::unix::linux::sync::futex;
-use crate::helpers::check_min_arg_count;
+use self::shims::unix::linux::syscall::syscall;
 use crate::machine::{SIGRTMAX, SIGRTMIN};
 use crate::shims::unix::*;
 use crate::*;
@@ -82,13 +81,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             "pthread_setname_np" => {
                 let [thread, name] =
                     this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
-                let res = this.pthread_setname_np(
+                let res = match this.pthread_setname_np(
                     this.read_scalar(thread)?,
                     this.read_scalar(name)?,
                     TASK_COMM_LEN,
                     /* truncate */ false,
-                )?;
-                let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
+                )? {
+                    ThreadNameResult::Ok => Scalar::from_u32(0),
+                    ThreadNameResult::NameTooLong => this.eval_libc("ERANGE"),
+                    // Act like we faild to open `/proc/self/task/$tid/comm`.
+                    ThreadNameResult::ThreadNotFound => this.eval_libc("ENOENT"),
+                };
                 this.write_scalar(res, dest)?;
             }
             "pthread_getname_np" => {
@@ -98,14 +101,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // In case of glibc, the length of the output buffer must
                 // be not shorter than TASK_COMM_LEN.
                 let len = this.read_scalar(len)?;
-                let res = if len.to_target_usize(this)? >= TASK_COMM_LEN as u64
-                    && this.pthread_getname_np(
+                let res = if len.to_target_usize(this)? >= TASK_COMM_LEN as u64 {
+                    match this.pthread_getname_np(
                         this.read_scalar(thread)?,
                         this.read_scalar(name)?,
                         len,
                         /* truncate*/ false,
                     )? {
-                    Scalar::from_u32(0)
+                        ThreadNameResult::Ok => Scalar::from_u32(0),
+                        ThreadNameResult::NameTooLong => unreachable!(),
+                        // Act like we faild to open `/proc/self/task/$tid/comm`.
+                        ThreadNameResult::ThreadNotFound => this.eval_libc("ENOENT"),
+                    }
                 } else {
                     this.eval_libc("ERANGE")
                 };
@@ -119,57 +126,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
             // Dynamically invoked syscalls
             "syscall" => {
-                // We do not use `check_shim` here because `syscall` is variadic. The argument
-                // count is checked bellow.
-                this.check_abi_and_shim_symbol_clash(
-                    abi,
-                    ExternAbi::C { unwind: false },
-                    link_name,
-                )?;
-                // The syscall variadic function is legal to call with more arguments than needed,
-                // extra arguments are simply ignored. The important check is that when we use an
-                // argument, we have to also check all arguments *before* it to ensure that they
-                // have the right type.
-
-                let sys_getrandom = this.eval_libc("SYS_getrandom").to_target_usize(this)?;
-                let sys_futex = this.eval_libc("SYS_futex").to_target_usize(this)?;
-                let sys_eventfd2 = this.eval_libc("SYS_eventfd2").to_target_usize(this)?;
-
-                let [op] = check_min_arg_count("syscall", args)?;
-                match this.read_target_usize(op)? {
-                    // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
-                    // is called if a `HashMap` is created the regular way (e.g. HashMap<K, V>).
-                    num if num == sys_getrandom => {
-                        // Used by getrandom 0.1
-                        // The first argument is the syscall id, so skip over it.
-                        let [_, ptr, len, flags] =
-                            check_min_arg_count("syscall(SYS_getrandom, ...)", args)?;
-
-                        let ptr = this.read_pointer(ptr)?;
-                        let len = this.read_target_usize(len)?;
-                        // The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
-                        // neither of which have any effect on our current PRNG.
-                        // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
-                        let _flags = this.read_scalar(flags)?.to_i32()?;
-
-                        this.gen_random(ptr, len)?;
-                        this.write_scalar(Scalar::from_target_usize(len, this), dest)?;
-                    }
-                    // `futex` is used by some synchronization primitives.
-                    num if num == sys_futex => {
-                        futex(this, args, dest)?;
-                    }
-                    num if num == sys_eventfd2 => {
-                        let [_, initval, flags] =
-                            check_min_arg_count("syscall(SYS_evetfd2, ...)", args)?;
-
-                        let result = this.eventfd(initval, flags)?;
-                        this.write_int(result.to_i32()?, dest)?;
-                    }
-                    num => {
-                        throw_unsup_format!("syscall: unsupported syscall number {num}");
-                    }
-                }
+                syscall(this, link_name, abi, args, dest)?;
             }
 
             // Miscellaneous
diff --git a/src/tools/miri/src/shims/unix/linux/mem.rs b/src/tools/miri/src/shims/unix/linux/mem.rs
index 7597df27326..8e796d5dce5 100644
--- a/src/tools/miri/src/shims/unix/linux/mem.rs
+++ b/src/tools/miri/src/shims/unix/linux/mem.rs
@@ -22,7 +22,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         let flags = this.read_scalar(flags)?.to_i32()?;
 
         // old_address must be a multiple of the page size
-        #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
+        #[expect(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
         if old_address.addr().bytes() % this.machine.page_size != 0 || new_size == 0 {
             this.set_last_error(LibcError("EINVAL"))?;
             return interp_ok(this.eval_libc("MAP_FAILED"));
diff --git a/src/tools/miri/src/shims/unix/linux/mod.rs b/src/tools/miri/src/shims/unix/linux/mod.rs
index 84b604eb9b8..159e5aca031 100644
--- a/src/tools/miri/src/shims/unix/linux/mod.rs
+++ b/src/tools/miri/src/shims/unix/linux/mod.rs
@@ -3,3 +3,4 @@ pub mod eventfd;
 pub mod foreign_items;
 pub mod mem;
 pub mod sync;
+pub mod syscall;
diff --git a/src/tools/miri/src/shims/unix/linux/sync.rs b/src/tools/miri/src/shims/unix/linux/sync.rs
index c258be78f76..6d5747d7c15 100644
--- a/src/tools/miri/src/shims/unix/linux/sync.rs
+++ b/src/tools/miri/src/shims/unix/linux/sync.rs
@@ -150,7 +150,7 @@ pub fn futex<'tcx>(
                     Scalar::from_target_isize(0, this), // retval_succ
                     Scalar::from_target_isize(-1, this), // retval_timeout
                     dest.clone(),
-                    this.eval_libc("ETIMEDOUT"), // errno_timeout
+                    LibcError("ETIMEDOUT"), // errno_timeout
                 );
             } else {
                 // The futex value doesn't match the expected value, so we return failure
@@ -182,7 +182,7 @@ pub fn futex<'tcx>(
             // before doing the syscall.
             this.atomic_fence(AtomicFenceOrd::SeqCst)?;
             let mut n = 0;
-            #[allow(clippy::arithmetic_side_effects)]
+            #[expect(clippy::arithmetic_side_effects)]
             for _ in 0..val {
                 if this.futex_wake(addr_usize, bitset)? {
                     n += 1;
diff --git a/src/tools/miri/src/shims/unix/linux/syscall.rs b/src/tools/miri/src/shims/unix/linux/syscall.rs
new file mode 100644
index 00000000000..0d7032adab4
--- /dev/null
+++ b/src/tools/miri/src/shims/unix/linux/syscall.rs
@@ -0,0 +1,63 @@
+use rustc_abi::ExternAbi;
+use rustc_span::Symbol;
+
+use self::shims::unix::linux::eventfd::EvalContextExt as _;
+use crate::helpers::check_min_arg_count;
+use crate::shims::unix::linux::sync::futex;
+use crate::*;
+
+pub fn syscall<'tcx>(
+    this: &mut MiriInterpCx<'tcx>,
+    link_name: Symbol,
+    abi: ExternAbi,
+    args: &[OpTy<'tcx>],
+    dest: &MPlaceTy<'tcx>,
+) -> InterpResult<'tcx> {
+    // We do not use `check_shim` here because `syscall` is variadic. The argument
+    // count is checked bellow.
+    this.check_abi_and_shim_symbol_clash(abi, ExternAbi::C { unwind: false }, link_name)?;
+    // The syscall variadic function is legal to call with more arguments than needed,
+    // extra arguments are simply ignored. The important check is that when we use an
+    // argument, we have to also check all arguments *before* it to ensure that they
+    // have the right type.
+
+    let sys_getrandom = this.eval_libc("SYS_getrandom").to_target_usize(this)?;
+    let sys_futex = this.eval_libc("SYS_futex").to_target_usize(this)?;
+    let sys_eventfd2 = this.eval_libc("SYS_eventfd2").to_target_usize(this)?;
+
+    let [op] = check_min_arg_count("syscall", args)?;
+    match this.read_target_usize(op)? {
+        // `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
+        // is called if a `HashMap` is created the regular way (e.g. HashMap<K, V>).
+        num if num == sys_getrandom => {
+            // Used by getrandom 0.1
+            // The first argument is the syscall id, so skip over it.
+            let [_, ptr, len, flags] = check_min_arg_count("syscall(SYS_getrandom, ...)", args)?;
+
+            let ptr = this.read_pointer(ptr)?;
+            let len = this.read_target_usize(len)?;
+            // The only supported flags are GRND_RANDOM and GRND_NONBLOCK,
+            // neither of which have any effect on our current PRNG.
+            // See <https://github.com/rust-lang/rust/pull/79196> for a discussion of argument sizes.
+            let _flags = this.read_scalar(flags)?.to_i32()?;
+
+            this.gen_random(ptr, len)?;
+            this.write_scalar(Scalar::from_target_usize(len, this), dest)?;
+        }
+        // `futex` is used by some synchronization primitives.
+        num if num == sys_futex => {
+            futex(this, args, dest)?;
+        }
+        num if num == sys_eventfd2 => {
+            let [_, initval, flags] = check_min_arg_count("syscall(SYS_evetfd2, ...)", args)?;
+
+            let result = this.eventfd(initval, flags)?;
+            this.write_int(result.to_i32()?, dest)?;
+        }
+        num => {
+            throw_unsup_format!("syscall: unsupported syscall number {num}");
+        }
+    };
+
+    interp_ok(())
+}
diff --git a/src/tools/miri/src/shims/unix/macos/foreign_items.rs b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
index b77b46e325d..003025916cd 100644
--- a/src/tools/miri/src/shims/unix/macos/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/macos/foreign_items.rs
@@ -181,18 +181,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // are met, then the name is set and 0 is returned. Otherwise, if
                 // the specified name is lomnger than MAXTHREADNAMESIZE, then
                 // ENAMETOOLONG is returned.
-                //
-                // FIXME: the real implementation maybe returns ESRCH if the thread ID is invalid.
                 let thread = this.pthread_self()?;
-                let res = if this.pthread_setname_np(
+                let res = match this.pthread_setname_np(
                     thread,
                     this.read_scalar(name)?,
                     this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?.try_into().unwrap(),
                     /* truncate */ false,
                 )? {
-                    Scalar::from_u32(0)
-                } else {
-                    this.eval_libc("ENAMETOOLONG")
+                    ThreadNameResult::Ok => Scalar::from_u32(0),
+                    ThreadNameResult::NameTooLong => this.eval_libc("ENAMETOOLONG"),
+                    ThreadNameResult::ThreadNotFound => unreachable!(),
                 };
                 // Contrary to the manpage, `pthread_setname_np` on macOS still
                 // returns an integer indicating success.
@@ -210,15 +208,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // https://github.com/apple-oss-distributions/libpthread/blob/c032e0b076700a0a47db75528a282b8d3a06531a/src/pthread.c#L1160-L1175.
                 // The key part is the strlcpy, which truncates the resulting value,
                 // but always null terminates (except for zero sized buffers).
-                //
-                // FIXME: the real implementation returns ESRCH if the thread ID is invalid.
-                let res = Scalar::from_u32(0);
-                this.pthread_getname_np(
+                let res = match this.pthread_getname_np(
                     this.read_scalar(thread)?,
                     this.read_scalar(name)?,
                     this.read_scalar(len)?,
                     /* truncate */ true,
-                )?;
+                )? {
+                    ThreadNameResult::Ok => Scalar::from_u32(0),
+                    // `NameTooLong` is possible when the buffer is zero sized,
+                    ThreadNameResult::NameTooLong => Scalar::from_u32(0),
+                    ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"),
+                };
                 this.write_scalar(res, dest)?;
             }
 
diff --git a/src/tools/miri/src/shims/unix/mem.rs b/src/tools/miri/src/shims/unix/mem.rs
index 88e04240b11..5531b944e17 100644
--- a/src/tools/miri/src/shims/unix/mem.rs
+++ b/src/tools/miri/src/shims/unix/mem.rs
@@ -132,7 +132,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         // addr must be a multiple of the page size, but apart from that munmap is just implemented
         // as a dealloc.
-        #[allow(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
+        #[expect(clippy::arithmetic_side_effects)] // PAGE_SIZE is nonzero
         if addr.addr().bytes() % this.machine.page_size != 0 {
             return this.set_last_error_and_return_i32(LibcError("EINVAL"));
         }
diff --git a/src/tools/miri/src/shims/unix/mod.rs b/src/tools/miri/src/shims/unix/mod.rs
index 9bc310e8d0a..c8c25c636ee 100644
--- a/src/tools/miri/src/shims/unix/mod.rs
+++ b/src/tools/miri/src/shims/unix/mod.rs
@@ -21,7 +21,7 @@ pub use self::fs::{DirTable, EvalContextExt as _};
 pub use self::linux::epoll::EpollInterestTable;
 pub use self::mem::EvalContextExt as _;
 pub use self::sync::EvalContextExt as _;
-pub use self::thread::EvalContextExt as _;
+pub use self::thread::{EvalContextExt as _, ThreadNameResult};
 pub use self::unnamed_socket::EvalContextExt as _;
 
 // Make up some constants.
diff --git a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
index efdc64f45fc..526b64cff69 100644
--- a/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
+++ b/src/tools/miri/src/shims/unix/solarish/foreign_items.rs
@@ -26,26 +26,33 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 // THREAD_NAME_MAX allows a thread name of 31+1 length
                 // https://github.com/illumos/illumos-gate/blob/7671517e13b8123748eda4ef1ee165c6d9dba7fe/usr/src/uts/common/sys/thread.h#L613
                 let max_len = 32;
-                let res = this.pthread_setname_np(
+                // See https://illumos.org/man/3C/pthread_setname_np for the error codes.
+                let res = match this.pthread_setname_np(
                     this.read_scalar(thread)?,
                     this.read_scalar(name)?,
                     max_len,
                     /* truncate */ false,
-                )?;
-                let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
+                )? {
+                    ThreadNameResult::Ok => Scalar::from_u32(0),
+                    ThreadNameResult::NameTooLong => this.eval_libc("ERANGE"),
+                    ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"),
+                };
                 this.write_scalar(res, dest)?;
             }
             "pthread_getname_np" => {
                 let [thread, name, len] =
                     this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
-                // https://github.com/illumos/illumos-gate/blob/c56822be04b6c157c8b6f2281e47214c3b86f657/usr/src/lib/libc/port/threads/thr.c#L2449-L2480
-                let res = this.pthread_getname_np(
+                // See https://illumos.org/man/3C/pthread_getname_np for the error codes.
+                let res = match this.pthread_getname_np(
                     this.read_scalar(thread)?,
                     this.read_scalar(name)?,
                     this.read_scalar(len)?,
                     /* truncate */ false,
-                )?;
-                let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
+                )? {
+                    ThreadNameResult::Ok => Scalar::from_u32(0),
+                    ThreadNameResult::NameTooLong => this.eval_libc("ERANGE"),
+                    ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"),
+                };
                 this.write_scalar(res, dest)?;
             }
 
diff --git a/src/tools/miri/src/shims/unix/sync.rs b/src/tools/miri/src/shims/unix/sync.rs
index 677002e79d2..850626d89ac 100644
--- a/src/tools/miri/src/shims/unix/sync.rs
+++ b/src/tools/miri/src/shims/unix/sync.rs
@@ -685,7 +685,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         let id = rwlock_get_data(this, rwlock_op)?.id;
 
-        #[allow(clippy::if_same_then_else)]
         if this.rwlock_reader_unlock(id)? || this.rwlock_writer_unlock(id)? {
             interp_ok(())
         } else {
diff --git a/src/tools/miri/src/shims/unix/thread.rs b/src/tools/miri/src/shims/unix/thread.rs
index 52c135f8540..3d990a1a042 100644
--- a/src/tools/miri/src/shims/unix/thread.rs
+++ b/src/tools/miri/src/shims/unix/thread.rs
@@ -2,6 +2,13 @@ use rustc_abi::ExternAbi;
 
 use crate::*;
 
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum ThreadNameResult {
+    Ok,
+    NameTooLong,
+    ThreadNotFound,
+}
+
 impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
 pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
     fn pthread_create(
@@ -30,7 +37,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         interp_ok(())
     }
 
-    fn pthread_join(&mut self, thread: &OpTy<'tcx>, retval: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
+    fn pthread_join(
+        &mut self,
+        thread: &OpTy<'tcx>,
+        retval: &OpTy<'tcx>,
+    ) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
         if !this.ptr_is_null(this.read_pointer(retval)?)? {
@@ -38,22 +49,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             throw_unsup_format!("Miri supports pthread_join only with retval==NULL");
         }
 
-        let thread_id = this.read_scalar(thread)?.to_int(this.libc_ty_layout("pthread_t").size)?;
-        this.join_thread_exclusive(thread_id.try_into().expect("thread ID should fit in u32"))?;
+        let thread = this.read_scalar(thread)?.to_int(this.libc_ty_layout("pthread_t").size)?;
+        let Ok(thread) = this.thread_id_try_from(thread) else {
+            return interp_ok(this.eval_libc("ESRCH"));
+        };
 
-        interp_ok(())
+        this.join_thread_exclusive(thread)?;
+
+        interp_ok(Scalar::from_u32(0))
     }
 
-    fn pthread_detach(&mut self, thread: &OpTy<'tcx>) -> InterpResult<'tcx, ()> {
+    fn pthread_detach(&mut self, thread: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
-        let thread_id = this.read_scalar(thread)?.to_int(this.libc_ty_layout("pthread_t").size)?;
-        this.detach_thread(
-            thread_id.try_into().expect("thread ID should fit in u32"),
-            /*allow_terminated_joined*/ false,
-        )?;
+        let thread = this.read_scalar(thread)?.to_int(this.libc_ty_layout("pthread_t").size)?;
+        let Ok(thread) = this.thread_id_try_from(thread) else {
+            return interp_ok(this.eval_libc("ESRCH"));
+        };
+        this.detach_thread(thread, /*allow_terminated_joined*/ false)?;
 
-        interp_ok(())
+        interp_ok(Scalar::from_u32(0))
     }
 
     fn pthread_self(&mut self) -> InterpResult<'tcx, Scalar> {
@@ -65,18 +80,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
     /// Set the name of the specified thread. If the name including the null terminator
     /// is longer or equals to `name_max_len`, then if `truncate` is set the truncated name
-    /// is used as the thread name, otherwise `false` is returned.
+    /// is used as the thread name, otherwise [`ThreadNameResult::NameTooLong`] is returned.
+    /// If the specified thread wasn't found, [`ThreadNameResult::ThreadNotFound`] is returned.
     fn pthread_setname_np(
         &mut self,
         thread: Scalar,
         name: Scalar,
         name_max_len: usize,
         truncate: bool,
-    ) -> InterpResult<'tcx, bool> {
+    ) -> InterpResult<'tcx, ThreadNameResult> {
         let this = self.eval_context_mut();
 
         let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?;
-        let thread = ThreadId::try_from(thread).unwrap();
+        let Ok(thread) = this.thread_id_try_from(thread) else {
+            return interp_ok(ThreadNameResult::ThreadNotFound);
+        };
         let name = name.to_pointer(this)?;
         let mut name = this.read_c_str(name)?.to_owned();
 
@@ -85,29 +103,32 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
             if truncate {
                 name.truncate(name_max_len.saturating_sub(1));
             } else {
-                return interp_ok(false);
+                return interp_ok(ThreadNameResult::NameTooLong);
             }
         }
 
         this.set_thread_name(thread, name);
 
-        interp_ok(true)
+        interp_ok(ThreadNameResult::Ok)
     }
 
     /// Get the name of the specified thread. If the thread name doesn't fit
     /// the buffer, then if `truncate` is set the truncated name is written out,
-    /// otherwise `false` is returned.
+    /// otherwise [`ThreadNameResult::NameTooLong`] is returned. If the specified
+    /// thread wasn't found, [`ThreadNameResult::ThreadNotFound`] is returned.
     fn pthread_getname_np(
         &mut self,
         thread: Scalar,
         name_out: Scalar,
         len: Scalar,
         truncate: bool,
-    ) -> InterpResult<'tcx, bool> {
+    ) -> InterpResult<'tcx, ThreadNameResult> {
         let this = self.eval_context_mut();
 
         let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?;
-        let thread = ThreadId::try_from(thread).unwrap();
+        let Ok(thread) = this.thread_id_try_from(thread) else {
+            return interp_ok(ThreadNameResult::ThreadNotFound);
+        };
         let name_out = name_out.to_pointer(this)?;
         let len = len.to_target_usize(this)?;
 
@@ -119,8 +140,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         };
 
         let (success, _written) = this.write_c_str(name, name_out, len)?;
+        let res = if success { ThreadNameResult::Ok } else { ThreadNameResult::NameTooLong };
 
-        interp_ok(success)
+        interp_ok(res)
     }
 
     fn sched_yield(&mut self) -> InterpResult<'tcx, ()> {
diff --git a/src/tools/miri/src/shims/windows/foreign_items.rs b/src/tools/miri/src/shims/windows/foreign_items.rs
index 2c7e8d2f2e7..504efed3cfd 100644
--- a/src/tools/miri/src/shims/windows/foreign_items.rs
+++ b/src/tools/miri/src/shims/windows/foreign_items.rs
@@ -10,6 +10,10 @@ use crate::shims::os_str::bytes_to_os_str;
 use crate::shims::windows::*;
 use crate::*;
 
+// The NTSTATUS STATUS_INVALID_HANDLE (0xC0000008) encoded as a HRESULT by setting the N bit.
+// (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0642cb2f-2075-4469-918c-4441e69c548a)
+const STATUS_INVALID_HANDLE: u32 = 0xD0000008;
+
 pub fn is_dyn_sym(name: &str) -> bool {
     // std does dynamic detection for these symbols
     matches!(
@@ -25,7 +29,7 @@ fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
 }
 
 #[cfg(unix)]
-#[allow(clippy::get_first, clippy::arithmetic_side_effects)]
+#[expect(clippy::get_first, clippy::arithmetic_side_effects)]
 fn win_absolute<'tcx>(path: &Path) -> InterpResult<'tcx, io::Result<PathBuf>> {
     // We are on Unix, so we need to implement parts of the logic ourselves.
     let bytes = path.as_os_str().as_encoded_bytes();
@@ -484,14 +488,14 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let thread_id =
                     this.CreateThread(security, stacksize, start, arg, flags, thread)?;
 
-                this.write_scalar(Handle::Thread(thread_id).to_scalar(this), dest)?;
+                this.write_scalar(Handle::Thread(thread_id.to_u32()).to_scalar(this), dest)?;
             }
             "WaitForSingleObject" => {
                 let [handle, timeout] =
                     this.check_shim(abi, ExternAbi::System { unwind: false }, link_name, args)?;
 
                 let ret = this.WaitForSingleObject(handle, timeout)?;
-                this.write_scalar(Scalar::from_u32(ret), dest)?;
+                this.write_scalar(ret, dest)?;
             }
             "GetCurrentThread" => {
                 let [] =
@@ -510,15 +514,20 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let name = this.read_wide_str(this.read_pointer(name)?)?;
 
                 let thread = match Handle::from_scalar(handle, this)? {
-                    Some(Handle::Thread(thread)) => thread,
-                    Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.active_thread(),
+                    Some(Handle::Thread(thread)) => this.thread_id_try_from(thread),
+                    Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => Ok(this.active_thread()),
                     _ => this.invalid_handle("SetThreadDescription")?,
                 };
+                let res = match thread {
+                    Ok(thread) => {
+                        // FIXME: use non-lossy conversion
+                        this.set_thread_name(thread, String::from_utf16_lossy(&name).into_bytes());
+                        Scalar::from_u32(0)
+                    }
+                    Err(_) => Scalar::from_u32(STATUS_INVALID_HANDLE),
+                };
 
-                // FIXME: use non-lossy conversion
-                this.set_thread_name(thread, String::from_utf16_lossy(&name).into_bytes());
-
-                this.write_null(dest)?;
+                this.write_scalar(res, dest)?;
             }
             "GetThreadDescription" => {
                 let [handle, name_ptr] =
@@ -528,20 +537,25 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let name_ptr = this.deref_pointer(name_ptr)?; // the pointer where we should store the ptr to the name
 
                 let thread = match Handle::from_scalar(handle, this)? {
-                    Some(Handle::Thread(thread)) => thread,
-                    Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.active_thread(),
-                    _ => this.invalid_handle("SetThreadDescription")?,
+                    Some(Handle::Thread(thread)) => this.thread_id_try_from(thread),
+                    Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => Ok(this.active_thread()),
+                    _ => this.invalid_handle("GetThreadDescription")?,
+                };
+                let (name, res) = match thread {
+                    Ok(thread) => {
+                        // Looks like the default thread name is empty.
+                        let name = this.get_thread_name(thread).unwrap_or(b"").to_owned();
+                        let name = this.alloc_os_str_as_wide_str(
+                            bytes_to_os_str(&name)?,
+                            MiriMemoryKind::WinLocal.into(),
+                        )?;
+                        (Scalar::from_maybe_pointer(name, this), Scalar::from_u32(0))
+                    }
+                    Err(_) => (Scalar::null_ptr(this), Scalar::from_u32(STATUS_INVALID_HANDLE)),
                 };
-                // Looks like the default thread name is empty.
-                let name = this.get_thread_name(thread).unwrap_or(b"").to_owned();
-                let name = this.alloc_os_str_as_wide_str(
-                    bytes_to_os_str(&name)?,
-                    MiriMemoryKind::WinLocal.into(),
-                )?;
-
-                this.write_scalar(Scalar::from_maybe_pointer(name, this), &name_ptr)?;
 
-                this.write_null(dest)?;
+                this.write_scalar(name, &name_ptr)?;
+                this.write_scalar(res, dest)?;
             }
 
             // Miscellaneous
@@ -630,9 +644,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let [handle] =
                     this.check_shim(abi, ExternAbi::System { unwind: false }, link_name, args)?;
 
-                this.CloseHandle(handle)?;
+                let ret = this.CloseHandle(handle)?;
 
-                this.write_int(1, dest)?;
+                this.write_scalar(ret, dest)?;
             }
             "GetModuleFileNameW" => {
                 let [handle, filename, size] =
diff --git a/src/tools/miri/src/shims/windows/handle.rs b/src/tools/miri/src/shims/windows/handle.rs
index 21da3d3cdb5..b40c00efedd 100644
--- a/src/tools/miri/src/shims/windows/handle.rs
+++ b/src/tools/miri/src/shims/windows/handle.rs
@@ -14,7 +14,7 @@ pub enum PseudoHandle {
 pub enum Handle {
     Null,
     Pseudo(PseudoHandle),
-    Thread(ThreadId),
+    Thread(u32),
 }
 
 impl PseudoHandle {
@@ -51,7 +51,7 @@ impl Handle {
         match self {
             Self::Null => 0,
             Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
-            Self::Thread(thread) => thread.to_u32(),
+            Self::Thread(thread) => thread,
         }
     }
 
@@ -63,7 +63,7 @@ impl Handle {
         let floor_log2 = variant_count.ilog2();
 
         // we need to add one for non powers of two to compensate for the difference
-        #[allow(clippy::arithmetic_side_effects)] // cannot overflow
+        #[expect(clippy::arithmetic_side_effects)] // cannot overflow
         if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
     }
 
@@ -88,15 +88,14 @@ impl Handle {
 
         // packs the data into the lower `data_size` bits
         // and packs the discriminant right above the data
-        #[allow(clippy::arithmetic_side_effects)] // cannot overflow
-        return discriminant << data_size | data;
+        discriminant << data_size | data
     }
 
     fn new(discriminant: u32, data: u32) -> Option<Self> {
         match discriminant {
             Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
             Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
-            Self::THREAD_DISCRIMINANT => Some(Self::Thread(data.into())),
+            Self::THREAD_DISCRIMINANT => Some(Self::Thread(data)),
             _ => None,
         }
     }
@@ -107,11 +106,10 @@ impl Handle {
         let data_size = u32::BITS.strict_sub(disc_size);
 
         // the lower `data_size` bits of this mask are 1
-        #[allow(clippy::arithmetic_side_effects)] // cannot overflow
+        #[expect(clippy::arithmetic_side_effects)] // cannot overflow
         let data_mask = 2u32.pow(data_size) - 1;
 
         // the discriminant is stored right above the lower `data_size` bits
-        #[allow(clippy::arithmetic_side_effects)] // cannot overflow
         let discriminant = handle >> data_size;
 
         // the data is stored in the lower `data_size` bits
@@ -123,7 +121,7 @@ impl Handle {
     pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar {
         // 64-bit handles are sign extended 32-bit handles
         // see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
-        #[allow(clippy::cast_possible_wrap)] // we want it to wrap
+        #[expect(clippy::cast_possible_wrap)] // we want it to wrap
         let signed_handle = self.to_packed() as i32;
         Scalar::from_target_isize(signed_handle.into(), cx)
     }
@@ -134,7 +132,7 @@ impl Handle {
     ) -> InterpResult<'tcx, Option<Self>> {
         let sign_extended_handle = handle.to_target_isize(cx)?;
 
-        #[allow(clippy::cast_sign_loss)] // we want to lose the sign
+        #[expect(clippy::cast_sign_loss)] // we want to lose the sign
         let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
             signed_handle as u32
         } else {
@@ -156,17 +154,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         )))
     }
 
-    fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx> {
+    fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
         let handle = this.read_scalar(handle_op)?;
-
-        match Handle::from_scalar(handle, this)? {
-            Some(Handle::Thread(thread)) =>
-                this.detach_thread(thread, /*allow_terminated_joined*/ true)?,
+        let ret = match Handle::from_scalar(handle, this)? {
+            Some(Handle::Thread(thread)) => {
+                if let Ok(thread) = this.thread_id_try_from(thread) {
+                    this.detach_thread(thread, /*allow_terminated_joined*/ true)?;
+                    this.eval_windows("c", "TRUE")
+                } else {
+                    this.invalid_handle("CloseHandle")?
+                }
+            }
             _ => this.invalid_handle("CloseHandle")?,
-        }
+        };
 
-        interp_ok(())
+        interp_ok(ret)
     }
 }
diff --git a/src/tools/miri/src/shims/windows/sync.rs b/src/tools/miri/src/shims/windows/sync.rs
index fac9c7b5fc5..7263958411f 100644
--- a/src/tools/miri/src/shims/windows/sync.rs
+++ b/src/tools/miri/src/shims/windows/sync.rs
@@ -202,7 +202,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 Scalar::from_i32(1), // retval_succ
                 Scalar::from_i32(0), // retval_timeout
                 dest.clone(),
-                this.eval_windows("c", "ERROR_TIMEOUT"), // errno_timeout
+                IoError::WindowsError("ERROR_TIMEOUT"), // errno_timeout
             );
         }
 
diff --git a/src/tools/miri/src/shims/windows/thread.rs b/src/tools/miri/src/shims/windows/thread.rs
index fd3ef1413ed..7af15fc647c 100644
--- a/src/tools/miri/src/shims/windows/thread.rs
+++ b/src/tools/miri/src/shims/windows/thread.rs
@@ -59,14 +59,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
         &mut self,
         handle_op: &OpTy<'tcx>,
         timeout_op: &OpTy<'tcx>,
-    ) -> InterpResult<'tcx, u32> {
+    ) -> InterpResult<'tcx, Scalar> {
         let this = self.eval_context_mut();
 
         let handle = this.read_scalar(handle_op)?;
         let timeout = this.read_scalar(timeout_op)?.to_u32()?;
 
         let thread = match Handle::from_scalar(handle, this)? {
-            Some(Handle::Thread(thread)) => thread,
+            Some(Handle::Thread(thread)) =>
+                match this.thread_id_try_from(thread) {
+                    Ok(thread) => thread,
+                    Err(_) => this.invalid_handle("WaitForSingleObject")?,
+                },
             // Unlike on posix, the outcome of joining the current thread is not documented.
             // On current Windows, it just deadlocks.
             Some(Handle::Pseudo(PseudoHandle::CurrentThread)) => this.active_thread(),
@@ -79,6 +83,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
 
         this.join_thread(thread)?;
 
-        interp_ok(0)
+        interp_ok(this.eval_windows("c", "WAIT_OBJECT_0"))
     }
 }
diff --git a/src/tools/miri/src/shims/x86/gfni.rs b/src/tools/miri/src/shims/x86/gfni.rs
index 5edbcbac3f6..7b92d422cc5 100644
--- a/src/tools/miri/src/shims/x86/gfni.rs
+++ b/src/tools/miri/src/shims/x86/gfni.rs
@@ -136,7 +136,7 @@ fn affine_transform<'tcx>(
 // This is a evaluated at compile time. Trait based conversion is not available.
 /// See <https://www.corsix.org/content/galois-field-instructions-2021-cpus> for the
 /// definition of `gf_inv` which was used for the creation of this table.
-#[allow(clippy::cast_possible_truncation)]
+#[expect(clippy::cast_possible_truncation)]
 static TABLE: [u8; 256] = {
     let mut array = [0; 256];
 
@@ -163,7 +163,7 @@ static TABLE: [u8; 256] = {
 /// polynomial representation with the reduction polynomial x^8 + x^4 + x^3 + x + 1.
 /// See <https://www.corsix.org/content/galois-field-instructions-2021-cpus> for details.
 // This is a const function. Trait based conversion is not available.
-#[allow(clippy::cast_possible_truncation)]
+#[expect(clippy::cast_possible_truncation)]
 const fn gf2p8_mul(left: u8, right: u8) -> u8 {
     // This implementation is based on the `gf2p8mul_byte` definition found inside the Intel intrinsics guide.
     // See https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=gf2p8mul
diff --git a/src/tools/miri/src/shims/x86/mod.rs b/src/tools/miri/src/shims/x86/mod.rs
index 9744aa3a071..433e9e966f2 100644
--- a/src/tools/miri/src/shims/x86/mod.rs
+++ b/src/tools/miri/src/shims/x86/mod.rs
@@ -95,11 +95,22 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 }
             }
 
-            "pclmulqdq" => {
+            "pclmulqdq" | "pclmulqdq.256" | "pclmulqdq.512" => {
+                let mut len = 2; // in units of 64bits
+                this.expect_target_feature_for_intrinsic(link_name, "pclmulqdq")?;
+                if unprefixed_name.ends_with(".256") {
+                    this.expect_target_feature_for_intrinsic(link_name, "vpclmulqdq")?;
+                    len = 4;
+                } else if unprefixed_name.ends_with(".512") {
+                    this.expect_target_feature_for_intrinsic(link_name, "vpclmulqdq")?;
+                    this.expect_target_feature_for_intrinsic(link_name, "avx512f")?;
+                    len = 8;
+                }
+
                 let [left, right, imm] =
                     this.check_shim(abi, ExternAbi::C { unwind: false }, link_name, args)?;
 
-                pclmulqdq(this, left, right, imm, dest)?;
+                pclmulqdq(this, left, right, imm, dest, len)?;
             }
 
             name if name.starts_with("bmi.") => {
@@ -386,7 +397,6 @@ enum FloatUnaryOp {
 }
 
 /// Performs `which` scalar operation on `op` and returns the result.
-#[allow(clippy::arithmetic_side_effects)] // floating point operations without side effects
 fn unary_op_f32<'tcx>(
     this: &mut crate::MiriInterpCx<'tcx>,
     which: FloatUnaryOp,
@@ -415,7 +425,7 @@ fn unary_op_f32<'tcx>(
 }
 
 /// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale).
-#[allow(clippy::arithmetic_side_effects)] // floating point arithmetic cannot panic
+#[expect(clippy::arithmetic_side_effects)] // floating point arithmetic cannot panic
 fn apply_random_float_error<F: rustc_apfloat::Float>(
     this: &mut crate::MiriInterpCx<'_>,
     val: F,
@@ -1122,7 +1132,7 @@ fn pmulhrsw<'tcx>(
 
         // The result of this operation can overflow a signed 16-bit integer.
         // When `left` and `right` are -0x8000, the result is 0x8000.
-        #[allow(clippy::cast_possible_truncation)]
+        #[expect(clippy::cast_possible_truncation)]
         let res = res as i16;
 
         this.write_scalar(Scalar::from_i16(res), &dest)?;
@@ -1134,9 +1144,12 @@ fn pmulhrsw<'tcx>(
 /// Perform a carry-less multiplication of two 64-bit integers, selected from `left` and `right` according to `imm8`,
 /// and store the results in `dst`.
 ///
-/// `left` and `right` are both vectors of type 2 x i64. Only bits 0 and 4 of `imm8` matter;
+/// `left` and `right` are both vectors of type `len` x i64. Only bits 0 and 4 of `imm8` matter;
 /// they select the element of `left` and `right`, respectively.
 ///
+/// `len` is the SIMD vector length (in counts of `i64` values). It is expected to be one of
+/// `2`, `4`, or `8`.
+///
 /// <https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html#text=_mm_clmulepi64_si128>
 fn pclmulqdq<'tcx>(
     this: &mut MiriInterpCx<'tcx>,
@@ -1144,51 +1157,55 @@ fn pclmulqdq<'tcx>(
     right: &OpTy<'tcx>,
     imm8: &OpTy<'tcx>,
     dest: &MPlaceTy<'tcx>,
+    len: u64,
 ) -> InterpResult<'tcx, ()> {
     assert_eq!(left.layout, right.layout);
     assert_eq!(left.layout.size, dest.layout.size);
+    assert!([2u64, 4, 8].contains(&len));
 
-    // Transmute to `[u64; 2]`
+    // Transmute the input into arrays of `[u64; len]`.
+    // Transmute the output into an array of `[u128, len / 2]`.
 
-    let array_layout = this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u64, 2))?;
-    let left = left.transmute(array_layout, this)?;
-    let right = right.transmute(array_layout, this)?;
-    let dest = dest.transmute(array_layout, this)?;
+    let src_layout = this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u64, len))?;
+    let dest_layout = this.layout_of(Ty::new_array(this.tcx.tcx, this.tcx.types.u128, len / 2))?;
+
+    let left = left.transmute(src_layout, this)?;
+    let right = right.transmute(src_layout, this)?;
+    let dest = dest.transmute(dest_layout, this)?;
 
     let imm8 = this.read_scalar(imm8)?.to_u8()?;
 
-    // select the 64-bit integer from left that the user specified (low or high)
-    let index = if (imm8 & 0x01) == 0 { 0 } else { 1 };
-    let left = this.read_scalar(&this.project_index(&left, index)?)?.to_u64()?;
-
-    // select the 64-bit integer from right that the user specified (low or high)
-    let index = if (imm8 & 0x10) == 0 { 0 } else { 1 };
-    let right = this.read_scalar(&this.project_index(&right, index)?)?.to_u64()?;
-
-    // Perform carry-less multiplication
-    //
-    // This operation is like long multiplication, but ignores all carries.
-    // That idea corresponds to the xor operator, which is used in the implementation.
-    //
-    // Wikipedia has an example https://en.wikipedia.org/wiki/Carry-less_product#Example
-    let mut result: u128 = 0;
-
-    for i in 0..64 {
-        // if the i-th bit in right is set
-        if (right & (1 << i)) != 0 {
-            // xor result with `left` shifted to the left by i positions
-            result ^= u128::from(left) << i;
+    for i in 0..(len / 2) {
+        let lo = i.strict_mul(2);
+        let hi = i.strict_mul(2).strict_add(1);
+
+        // select the 64-bit integer from left that the user specified (low or high)
+        let index = if (imm8 & 0x01) == 0 { lo } else { hi };
+        let left = this.read_scalar(&this.project_index(&left, index)?)?.to_u64()?;
+
+        // select the 64-bit integer from right that the user specified (low or high)
+        let index = if (imm8 & 0x10) == 0 { lo } else { hi };
+        let right = this.read_scalar(&this.project_index(&right, index)?)?.to_u64()?;
+
+        // Perform carry-less multiplication.
+        //
+        // This operation is like long multiplication, but ignores all carries.
+        // That idea corresponds to the xor operator, which is used in the implementation.
+        //
+        // Wikipedia has an example https://en.wikipedia.org/wiki/Carry-less_product#Example
+        let mut result: u128 = 0;
+
+        for i in 0..64 {
+            // if the i-th bit in right is set
+            if (right & (1 << i)) != 0 {
+                // xor result with `left` shifted to the left by i positions
+                result ^= u128::from(left) << i;
+            }
         }
-    }
-
-    let result_low = (result & 0xFFFF_FFFF_FFFF_FFFF) as u64;
-    let result_high = (result >> 64) as u64;
-
-    let dest_low = this.project_index(&dest, 0)?;
-    this.write_scalar(Scalar::from_u64(result_low), &dest_low)?;
 
-    let dest_high = this.project_index(&dest, 1)?;
-    this.write_scalar(Scalar::from_u64(result_high), &dest_high)?;
+        let dest = this.project_index(&dest, i)?;
+        this.write_scalar(Scalar::from_u128(result), &dest)?;
+    }
 
     interp_ok(())
 }
diff --git a/src/tools/miri/src/shims/x86/sse42.rs b/src/tools/miri/src/shims/x86/sse42.rs
index 4bd87b719b0..cc7cfab5041 100644
--- a/src/tools/miri/src/shims/x86/sse42.rs
+++ b/src/tools/miri/src/shims/x86/sse42.rs
@@ -68,7 +68,7 @@ const USE_SIGNED: u8 = 2;
 /// The mask may be negated if negation flags inside the immediate byte are set.
 ///
 /// For more information, see the Intel Software Developer's Manual, Vol. 2b, Chapter 4.1.
-#[allow(clippy::arithmetic_side_effects)]
+#[expect(clippy::arithmetic_side_effects)]
 fn compare_strings<'tcx>(
     this: &mut MiriInterpCx<'tcx>,
     str1: &OpTy<'tcx>,
@@ -444,7 +444,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
                 let crc = if bit_size == 64 {
                     // The 64-bit version will only consider the lower 32 bits,
                     // while the upper 32 bits get discarded.
-                    #[allow(clippy::cast_possible_truncation)]
+                    #[expect(clippy::cast_possible_truncation)]
                     u128::from((left.to_u64()? as u32).reverse_bits())
                 } else {
                     u128::from(left.to_u32()?.reverse_bits())
diff --git a/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs b/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs
index 20e642a0a29..2a36c10f7d4 100644
--- a/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs
+++ b/src/tools/miri/tests/pass-dep/concurrency/linux-futex.rs
@@ -1,4 +1,5 @@
 //@only-target: linux
+//@only-target: android
 //@compile-flags: -Zmiri-disable-isolation
 
 // FIXME(static_mut_refs): Do not allow `static_mut_refs` lint
diff --git a/src/tools/miri/tests/pass-dep/libc/libc-random.rs b/src/tools/miri/tests/pass-dep/libc/libc-random.rs
index 8f4398cbd8f..7c4010f6c0a 100644
--- a/src/tools/miri/tests/pass-dep/libc/libc-random.rs
+++ b/src/tools/miri/tests/pass-dep/libc/libc-random.rs
@@ -28,26 +28,27 @@ fn test_getrandom() {
 
     let mut buf = [0u8; 5];
     unsafe {
-        #[cfg(target_os = "linux")]
-        assert_eq!(
-            libc::syscall(
-                libc::SYS_getrandom,
-                ptr::null_mut::<libc::c_void>(),
-                0 as libc::size_t,
-                0 as libc::c_uint,
-            ),
-            0,
-        );
-        #[cfg(target_os = "linux")]
-        assert_eq!(
-            libc::syscall(
-                libc::SYS_getrandom,
-                buf.as_mut_ptr() as *mut libc::c_void,
-                5 as libc::size_t,
-                0 as libc::c_uint,
-            ),
-            5,
-        );
+        #[cfg(any(target_os = "linux", target_os = "android"))]
+        {
+            assert_eq!(
+                libc::syscall(
+                    libc::SYS_getrandom,
+                    ptr::null_mut::<libc::c_void>(),
+                    0 as libc::size_t,
+                    0 as libc::c_uint,
+                ),
+                0,
+            );
+            assert_eq!(
+                libc::syscall(
+                    libc::SYS_getrandom,
+                    buf.as_mut_ptr() as *mut libc::c_void,
+                    5 as libc::size_t,
+                    0 as libc::c_uint,
+                ),
+                5,
+            );
+        }
 
         assert_eq!(
             libc::getrandom(ptr::null_mut::<libc::c_void>(), 0 as libc::size_t, 0 as libc::c_uint),
diff --git a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs
index 0e5b501bbcc..cf634bc6890 100644
--- a/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs
+++ b/src/tools/miri/tests/pass-dep/libc/pthread-threadname.rs
@@ -199,4 +199,29 @@ fn main() {
         .unwrap()
         .join()
         .unwrap();
+
+    // Now set the name for a non-existing thread and verify error codes.
+    // (FreeBSD doesn't return an error code.)
+    #[cfg(not(target_os = "freebsd"))]
+    {
+        let invalid_thread = 0xdeadbeef;
+        let error = {
+            cfg_if::cfg_if! {
+                if #[cfg(target_os = "linux")] {
+                    libc::ENOENT
+                } else {
+                    libc::ESRCH
+                }
+            }
+        };
+        #[cfg(not(target_os = "macos"))]
+        {
+            // macOS has no `setname` function accepting a thread id as the first argument.
+            let res = unsafe { libc::pthread_setname_np(invalid_thread, [0].as_ptr()) };
+            assert_eq!(res, error);
+        }
+        let mut buf = [0; 64];
+        let res = unsafe { libc::pthread_getname_np(invalid_thread, buf.as_mut_ptr(), buf.len()) };
+        assert_eq!(res, error);
+    }
 }
diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-sha.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-sha.rs
index 4e892e6e3cb..ae5731bc8a6 100644
--- a/src/tools/miri/tests/pass/shims/x86/intrinsics-sha.rs
+++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-sha.rs
@@ -181,7 +181,7 @@ unsafe fn schedule(v0: __m128i, v1: __m128i, v2: __m128i, v3: __m128i) -> __m128
 }
 
 // we use unaligned loads with `__m128i` pointers
-#[allow(clippy::cast_ptr_alignment)]
+#[expect(clippy::cast_ptr_alignment)]
 #[target_feature(enable = "sha,sse2,ssse3,sse4.1")]
 unsafe fn digest_blocks(state: &mut [u32; 8], blocks: &[[u8; 64]]) {
     #[allow(non_snake_case)]
diff --git a/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs
new file mode 100644
index 00000000000..68964728e4e
--- /dev/null
+++ b/src/tools/miri/tests/pass/shims/x86/intrinsics-x86-vpclmulqdq.rs
@@ -0,0 +1,193 @@
+// We're testing x86 target specific features
+//@revisions: avx512 avx
+//@only-target: x86_64 i686
+//@[avx512]compile-flags: -C target-feature=+vpclmulqdq,+avx512f
+//@[avx]compile-flags: -C target-feature=+vpclmulqdq,+avx2
+
+// The constants in the tests below are just bit patterns. They should not
+// be interpreted as integers; signedness does not make sense for them, but
+// __mXXXi happens to be defined in terms of signed integers.
+#![allow(overflowing_literals)]
+#![feature(avx512_target_feature)]
+#![feature(stdarch_x86_avx512)]
+
+#[cfg(target_arch = "x86")]
+use std::arch::x86::*;
+#[cfg(target_arch = "x86_64")]
+use std::arch::x86_64::*;
+use std::mem::transmute;
+
+fn main() {
+    // Mostly copied from library/stdarch/crates/core_arch/src/x86/vpclmulqdq.rs
+
+    assert!(is_x86_feature_detected!("pclmulqdq"));
+    assert!(is_x86_feature_detected!("vpclmulqdq"));
+
+    unsafe {
+        test_mm256_clmulepi64_epi128();
+
+        if is_x86_feature_detected!("avx512f") {
+            test_mm512_clmulepi64_epi128();
+        }
+    }
+}
+
+macro_rules! verify_kat_pclmul {
+    ($broadcast:ident, $clmul:ident, $assert:ident) => {
+        // Constants taken from https://software.intel.com/sites/default/files/managed/72/cc/clmul-wp-rev-2.02-2014-04-20.pdf
+        let a = _mm_set_epi64x(0x7b5b546573745665, 0x63746f725d53475d);
+        let a = $broadcast(a);
+        let b = _mm_set_epi64x(0x4869285368617929, 0x5b477565726f6e5d);
+        let b = $broadcast(b);
+        let r00 = _mm_set_epi64x(0x1d4d84c85c3440c0, 0x929633d5d36f0451);
+        let r00 = $broadcast(r00);
+        let r01 = _mm_set_epi64x(0x1bd17c8d556ab5a1, 0x7fa540ac2a281315);
+        let r01 = $broadcast(r01);
+        let r10 = _mm_set_epi64x(0x1a2bf6db3a30862f, 0xbabf262df4b7d5c9);
+        let r10 = $broadcast(r10);
+        let r11 = _mm_set_epi64x(0x1d1e1f2c592e7c45, 0xd66ee03e410fd4ed);
+        let r11 = $broadcast(r11);
+
+        $assert($clmul::<0x00>(a, b), r00);
+        $assert($clmul::<0x10>(a, b), r01);
+        $assert($clmul::<0x01>(a, b), r10);
+        $assert($clmul::<0x11>(a, b), r11);
+
+        let a0 = _mm_set_epi64x(0x0000000000000000, 0x8000000000000000);
+        let a0 = $broadcast(a0);
+        let r = _mm_set_epi64x(0x4000000000000000, 0x0000000000000000);
+        let r = $broadcast(r);
+        $assert($clmul::<0x00>(a0, a0), r);
+    }
+}
+
+// this function tests one of the possible 4 instances
+// with different inputs across lanes for the 512-bit version
+#[target_feature(enable = "vpclmulqdq,avx512f")]
+unsafe fn verify_512_helper(
+    linear: unsafe fn(__m128i, __m128i) -> __m128i,
+    vectorized: unsafe fn(__m512i, __m512i) -> __m512i,
+) {
+    let a = _mm512_set_epi64(
+        0xDCB4DB3657BF0B7D,
+        0x18DB0601068EDD9F,
+        0xB76B908233200DC5,
+        0xE478235FA8E22D5E,
+        0xAB05CFFA2621154C,
+        0x1171B47A186174C9,
+        0x8C6B6C0E7595CEC9,
+        0xBE3E7D4934E961BD,
+    );
+    let b = _mm512_set_epi64(
+        0x672F6F105A94CEA7,
+        0x8298B8FFCA5F829C,
+        0xA3927047B3FB61D8,
+        0x978093862CDE7187,
+        0xB1927AB22F31D0EC,
+        0xA9A5DA619BE4D7AF,
+        0xCA2590F56884FDC6,
+        0x19BE9F660038BDB5,
+    );
+
+    let a_decomp = transmute::<_, [__m128i; 4]>(a);
+    let b_decomp = transmute::<_, [__m128i; 4]>(b);
+
+    let r = vectorized(a, b);
+
+    let e_decomp = [
+        linear(a_decomp[0], b_decomp[0]),
+        linear(a_decomp[1], b_decomp[1]),
+        linear(a_decomp[2], b_decomp[2]),
+        linear(a_decomp[3], b_decomp[3]),
+    ];
+    let e = transmute::<_, __m512i>(e_decomp);
+
+    assert_eq_m512i(r, e)
+}
+
+// this function tests one of the possible 4 instances
+// with different inputs across lanes for the 256-bit version
+#[target_feature(enable = "vpclmulqdq")]
+unsafe fn verify_256_helper(
+    linear: unsafe fn(__m128i, __m128i) -> __m128i,
+    vectorized: unsafe fn(__m256i, __m256i) -> __m256i,
+) {
+    let a = _mm256_set_epi64x(
+        0xDCB4DB3657BF0B7D,
+        0x18DB0601068EDD9F,
+        0xB76B908233200DC5,
+        0xE478235FA8E22D5E,
+    );
+    let b = _mm256_set_epi64x(
+        0x672F6F105A94CEA7,
+        0x8298B8FFCA5F829C,
+        0xA3927047B3FB61D8,
+        0x978093862CDE7187,
+    );
+
+    let a_decomp = transmute::<_, [__m128i; 2]>(a);
+    let b_decomp = transmute::<_, [__m128i; 2]>(b);
+
+    let r = vectorized(a, b);
+
+    let e_decomp = [linear(a_decomp[0], b_decomp[0]), linear(a_decomp[1], b_decomp[1])];
+    let e = transmute::<_, __m256i>(e_decomp);
+
+    assert_eq_m256i(r, e)
+}
+
+#[target_feature(enable = "vpclmulqdq,avx512f")]
+unsafe fn test_mm512_clmulepi64_epi128() {
+    verify_kat_pclmul!(_mm512_broadcast_i32x4, _mm512_clmulepi64_epi128, assert_eq_m512i);
+
+    verify_512_helper(
+        |a, b| _mm_clmulepi64_si128::<0x00>(a, b),
+        |a, b| _mm512_clmulepi64_epi128::<0x00>(a, b),
+    );
+    verify_512_helper(
+        |a, b| _mm_clmulepi64_si128::<0x01>(a, b),
+        |a, b| _mm512_clmulepi64_epi128::<0x01>(a, b),
+    );
+    verify_512_helper(
+        |a, b| _mm_clmulepi64_si128::<0x10>(a, b),
+        |a, b| _mm512_clmulepi64_epi128::<0x10>(a, b),
+    );
+    verify_512_helper(
+        |a, b| _mm_clmulepi64_si128::<0x11>(a, b),
+        |a, b| _mm512_clmulepi64_epi128::<0x11>(a, b),
+    );
+}
+
+#[target_feature(enable = "vpclmulqdq")]
+unsafe fn test_mm256_clmulepi64_epi128() {
+    verify_kat_pclmul!(_mm256_broadcastsi128_si256, _mm256_clmulepi64_epi128, assert_eq_m256i);
+
+    verify_256_helper(
+        |a, b| _mm_clmulepi64_si128::<0x00>(a, b),
+        |a, b| _mm256_clmulepi64_epi128::<0x00>(a, b),
+    );
+    verify_256_helper(
+        |a, b| _mm_clmulepi64_si128::<0x01>(a, b),
+        |a, b| _mm256_clmulepi64_epi128::<0x01>(a, b),
+    );
+    verify_256_helper(
+        |a, b| _mm_clmulepi64_si128::<0x10>(a, b),
+        |a, b| _mm256_clmulepi64_epi128::<0x10>(a, b),
+    );
+    verify_256_helper(
+        |a, b| _mm_clmulepi64_si128::<0x11>(a, b),
+        |a, b| _mm256_clmulepi64_epi128::<0x11>(a, b),
+    );
+}
+
+#[track_caller]
+#[target_feature(enable = "avx512f")]
+unsafe fn assert_eq_m512i(a: __m512i, b: __m512i) {
+    assert_eq!(transmute::<_, [u64; 8]>(a), transmute::<_, [u64; 8]>(b))
+}
+
+#[track_caller]
+#[target_feature(enable = "avx")]
+unsafe fn assert_eq_m256i(a: __m256i, b: __m256i) {
+    assert_eq!(transmute::<_, [u64; 4]>(a), transmute::<_, [u64; 4]>(b))
+}
diff --git a/tests/crashes/131342-2.rs b/tests/crashes/131342-2.rs
deleted file mode 100644
index 79b6a837a49..00000000000
--- a/tests/crashes/131342-2.rs
+++ /dev/null
@@ -1,40 +0,0 @@
-//@ known-bug: #131342
-// see also: 131342.rs
-
-fn main() {
-    problem_thingy(Once);
-}
-
-struct Once;
-
-impl Iterator for Once {
-    type Item = ();
-}
-
-fn problem_thingy(items: impl Iterator) {
-    let peeker = items.peekable();
-    problem_thingy(&peeker);
-}
-
-trait Iterator {
-    type Item;
-
-    fn peekable(self) -> Peekable<Self>
-    where
-        Self: Sized,
-    {
-        loop {}
-    }
-}
-
-struct Peekable<I: Iterator> {
-    _peeked: I::Item,
-}
-
-impl<I: Iterator> Iterator for Peekable<I> {
-    type Item = I::Item;
-}
-
-impl<I: Iterator + ?Sized> Iterator for &I {
-    type Item = I::Item;
-}
diff --git a/tests/crashes/131342.rs b/tests/crashes/131342.rs
index 7f7ee9c9ac1..f4404092917 100644
--- a/tests/crashes/131342.rs
+++ b/tests/crashes/131342.rs
@@ -1,16 +1,15 @@
 //@ known-bug: #131342
-// see also: 131342-2.rs
 
 fn main() {
-  let mut items = vec![1, 2, 3, 4, 5].into_iter();
-  problem_thingy(&mut items);
+    let mut items = vec![1, 2, 3, 4, 5].into_iter();
+    problem_thingy(&mut items);
 }
 
 fn problem_thingy(items: &mut impl Iterator<Item = u8>) {
-  let mut peeker = items.peekable();
-  match peeker.peek() {
-    Some(_) => (),
-    None => return (),
-  }
-  problem_thingy(&mut peeker);
+    let mut peeker = items.peekable();
+    match peeker.peek() {
+        Some(_) => (),
+        None => return (),
+    }
+    problem_thingy(&mut peeker);
 }
diff --git a/tests/ui/layout/post-mono-layout-cycle-2.rs b/tests/ui/layout/post-mono-layout-cycle-2.rs
index 356f1e777c7..e9a5292fbbd 100644
--- a/tests/ui/layout/post-mono-layout-cycle-2.rs
+++ b/tests/ui/layout/post-mono-layout-cycle-2.rs
@@ -45,7 +45,6 @@ where
     T: Blah,
 {
     async fn ice(&mut self) {
-        //~^ ERROR a cycle occurred during layout computation
         let arr: [(); 0] = [];
         self.t.iter(arr.into_iter()).await;
     }
diff --git a/tests/ui/layout/post-mono-layout-cycle-2.stderr b/tests/ui/layout/post-mono-layout-cycle-2.stderr
index ad01c2694fa..2e8d237844e 100644
--- a/tests/ui/layout/post-mono-layout-cycle-2.stderr
+++ b/tests/ui/layout/post-mono-layout-cycle-2.stderr
@@ -12,12 +12,12 @@ LL |           Blah::iter(self, iterator).await
    |
    = note: a recursive `async fn` call must introduce indirection such as `Box::pin` to avoid an infinitely sized future
 
-error: a cycle occurred during layout computation
-  --> $DIR/post-mono-layout-cycle-2.rs:47:5
+note: the above error was encountered while instantiating `fn Wrap::<()>::ice`
+  --> $DIR/post-mono-layout-cycle-2.rs:56:9
    |
-LL |     async fn ice(&mut self) {
-   |     ^^^^^^^^^^^^^^^^^^^^^^^
+LL |         t.ice();
+   |         ^^^^^^^
 
-error: aborting due to 2 previous errors
+error: aborting due to 1 previous error
 
 For more information about this error, try `rustc --explain E0733`.
diff --git a/tests/ui/layout/post-mono-layout-cycle.rs b/tests/ui/layout/post-mono-layout-cycle.rs
index 8d136190c00..6753c01267e 100644
--- a/tests/ui/layout/post-mono-layout-cycle.rs
+++ b/tests/ui/layout/post-mono-layout-cycle.rs
@@ -14,7 +14,6 @@ struct Wrapper<T: Trait> {
 }
 
 fn abi<T: Trait>(_: Option<Wrapper<T>>) {}
-//~^ ERROR a cycle occurred during layout computation
 
 fn indirect<T: Trait>() {
     abi::<T>(None);
diff --git a/tests/ui/layout/post-mono-layout-cycle.stderr b/tests/ui/layout/post-mono-layout-cycle.stderr
index 47f7f30b1cb..7f246b3d409 100644
--- a/tests/ui/layout/post-mono-layout-cycle.stderr
+++ b/tests/ui/layout/post-mono-layout-cycle.stderr
@@ -5,12 +5,12 @@ error[E0391]: cycle detected when computing layout of `Wrapper<()>`
    = note: cycle used when computing layout of `core::option::Option<Wrapper<()>>`
    = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
 
-error: a cycle occurred during layout computation
-  --> $DIR/post-mono-layout-cycle.rs:16:1
+note: the above error was encountered while instantiating `fn abi::<()>`
+  --> $DIR/post-mono-layout-cycle.rs:19:5
    |
-LL | fn abi<T: Trait>(_: Option<Wrapper<T>>) {}
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL |     abi::<T>(None);
+   |     ^^^^^^^^^^^^^^
 
-error: aborting due to 2 previous errors
+error: aborting due to 1 previous error
 
 For more information about this error, try `rustc --explain E0391`.
diff --git a/tests/ui/simd-abi-checks.rs b/tests/ui/simd-abi-checks.rs
new file mode 100644
index 00000000000..094c89930b7
--- /dev/null
+++ b/tests/ui/simd-abi-checks.rs
@@ -0,0 +1,81 @@
+//@ only-x86_64
+//@ build-pass
+//@ ignore-pass (test emits codegen-time warnings)
+
+#![feature(avx512_target_feature)]
+#![feature(portable_simd)]
+#![allow(improper_ctypes_definitions)]
+
+use std::arch::x86_64::*;
+
+#[repr(transparent)]
+struct Wrapper(__m256);
+
+unsafe extern "C" fn w(_: Wrapper) {
+    //~^ ABI error: this function definition uses a vector type that requires the `avx` target feature, which is not enabled
+    //~| WARNING this was previously accepted by the compiler
+    todo!()
+}
+
+unsafe extern "C" fn f(_: __m256) {
+    //~^ ABI error: this function definition uses a vector type that requires the `avx` target feature, which is not enabled
+    //~| WARNING this was previously accepted by the compiler
+    todo!()
+}
+
+unsafe extern "C" fn g() -> __m256 {
+    //~^ ABI error: this function definition uses a vector type that requires the `avx` target feature, which is not enabled
+    //~| WARNING this was previously accepted by the compiler
+    todo!()
+}
+
+#[target_feature(enable = "avx")]
+unsafe extern "C" fn favx() -> __m256 {
+    todo!()
+}
+
+// avx2 implies avx, so no error here.
+#[target_feature(enable = "avx2")]
+unsafe extern "C" fn gavx(_: __m256) {
+    todo!()
+}
+
+// No error because of "Rust" ABI.
+fn as_f64x8(d: __m512d) -> std::simd::f64x8 {
+    unsafe { std::mem::transmute(d) }
+}
+
+unsafe fn test() {
+    let arg = std::mem::transmute([0.0f64; 8]);
+    as_f64x8(arg);
+}
+
+fn main() {
+    unsafe {
+        f(g());
+        //~^ WARNING ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+        //~| WARNING ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+        //~| WARNING this was previously accepted by the compiler
+        //~| WARNING this was previously accepted by the compiler
+    }
+
+    unsafe {
+        gavx(favx());
+        //~^ WARNING ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+        //~| WARNING ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+        //~| WARNING this was previously accepted by the compiler
+        //~| WARNING this was previously accepted by the compiler
+    }
+
+    unsafe {
+        test();
+    }
+
+    unsafe {
+        w(Wrapper(g()));
+        //~^ WARNING ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+        //~| WARNING ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+        //~| WARNING this was previously accepted by the compiler
+        //~| WARNING this was previously accepted by the compiler
+    }
+}
diff --git a/tests/ui/simd-abi-checks.stderr b/tests/ui/simd-abi-checks.stderr
new file mode 100644
index 00000000000..aa7e9400169
--- /dev/null
+++ b/tests/ui/simd-abi-checks.stderr
@@ -0,0 +1,93 @@
+warning: ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+  --> $DIR/simd-abi-checks.rs:55:11
+   |
+LL |         f(g());
+   |           ^^^ function called here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+avx`) or locally (`#[target_feature(enable="avx")]`)
+   = note: `#[warn(abi_unsupported_vector_types)]` on by default
+
+warning: ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+  --> $DIR/simd-abi-checks.rs:55:9
+   |
+LL |         f(g());
+   |         ^^^^^^ function called here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+avx`) or locally (`#[target_feature(enable="avx")]`)
+
+warning: ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+  --> $DIR/simd-abi-checks.rs:63:14
+   |
+LL |         gavx(favx());
+   |              ^^^^^^ function called here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+avx`) or locally (`#[target_feature(enable="avx")]`)
+
+warning: ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+  --> $DIR/simd-abi-checks.rs:63:9
+   |
+LL |         gavx(favx());
+   |         ^^^^^^^^^^^^ function called here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+avx`) or locally (`#[target_feature(enable="avx")]`)
+
+warning: ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+  --> $DIR/simd-abi-checks.rs:75:19
+   |
+LL |         w(Wrapper(g()));
+   |                   ^^^ function called here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+avx`) or locally (`#[target_feature(enable="avx")]`)
+
+warning: ABI error: this function call uses a vector type that requires the `avx` target feature, which is not enabled in the caller
+  --> $DIR/simd-abi-checks.rs:75:9
+   |
+LL |         w(Wrapper(g()));
+   |         ^^^^^^^^^^^^^^^ function called here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+avx`) or locally (`#[target_feature(enable="avx")]`)
+
+warning: ABI error: this function definition uses a vector type that requires the `avx` target feature, which is not enabled
+  --> $DIR/simd-abi-checks.rs:26:1
+   |
+LL | unsafe extern "C" fn g() -> __m256 {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function defined here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+avx`) or locally (`#[target_feature(enable="avx")]`)
+
+warning: ABI error: this function definition uses a vector type that requires the `avx` target feature, which is not enabled
+  --> $DIR/simd-abi-checks.rs:20:1
+   |
+LL | unsafe extern "C" fn f(_: __m256) {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function defined here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+avx`) or locally (`#[target_feature(enable="avx")]`)
+
+warning: ABI error: this function definition uses a vector type that requires the `avx` target feature, which is not enabled
+  --> $DIR/simd-abi-checks.rs:14:1
+   |
+LL | unsafe extern "C" fn w(_: Wrapper) {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function defined here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+avx`) or locally (`#[target_feature(enable="avx")]`)
+
+warning: 9 warnings emitted
+
diff --git a/tests/ui/sse-abi-checks.rs b/tests/ui/sse-abi-checks.rs
new file mode 100644
index 00000000000..d2afd38fcc8
--- /dev/null
+++ b/tests/ui/sse-abi-checks.rs
@@ -0,0 +1,24 @@
+//! Ensure we trigger abi_unsupported_vector_types for target features that are usually enabled
+//! on a target, but disabled in this file via a `-C` flag.
+//@ compile-flags: --crate-type=rlib --target=i686-unknown-linux-gnu -C target-feature=-sse,-sse2
+//@ build-pass
+//@ ignore-pass (test emits codegen-time warnings)
+//@ needs-llvm-components: x86
+#![feature(no_core, lang_items, repr_simd)]
+#![no_core]
+#![allow(improper_ctypes_definitions)]
+
+#[lang = "sized"]
+trait Sized {}
+
+#[lang = "copy"]
+trait Copy {}
+
+#[repr(simd)]
+pub struct SseVector([i64; 2]);
+
+#[no_mangle]
+pub unsafe extern "C" fn f(_: SseVector) {
+    //~^ ABI error: this function definition uses a vector type that requires the `sse` target feature, which is not enabled
+    //~| WARNING this was previously accepted by the compiler
+}
diff --git a/tests/ui/sse-abi-checks.stderr b/tests/ui/sse-abi-checks.stderr
new file mode 100644
index 00000000000..77c4e1fc07a
--- /dev/null
+++ b/tests/ui/sse-abi-checks.stderr
@@ -0,0 +1,13 @@
+warning: ABI error: this function definition uses a vector type that requires the `sse` target feature, which is not enabled
+  --> $DIR/sse-abi-checks.rs:21:1
+   |
+LL | pub unsafe extern "C" fn f(_: SseVector) {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ function defined here
+   |
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #116558 <https://github.com/rust-lang/rust/issues/116558>
+   = help: consider enabling it globally (`-C target-feature=+sse`) or locally (`#[target_feature(enable="sse")]`)
+   = note: `#[warn(abi_unsupported_vector_types)]` on by default
+
+warning: 1 warning emitted
+