about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_borrowck/src/polonius/dump.rs181
-rw-r--r--compiler/rustc_hir/src/hir.rs24
-rw-r--r--compiler/rustc_hir/src/lib.rs1
-rw-r--r--compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs62
-rw-r--r--compiler/rustc_hir_analysis/src/lib.rs1
-rw-r--r--compiler/rustc_hir_typeck/src/demand.rs38
-rw-r--r--compiler/rustc_lint/messages.ftl3
-rw-r--r--compiler/rustc_lint/src/early/diagnostics.rs1
-rw-r--r--compiler/rustc_lint/src/lints.rs4
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs39
-rw-r--r--compiler/rustc_lint_defs/src/lib.rs1
-rw-r--r--compiler/rustc_metadata/messages.ftl3
-rw-r--r--compiler/rustc_metadata/src/creader.rs7
-rw-r--r--compiler/rustc_metadata/src/errors.rs7
-rw-r--r--compiler/rustc_middle/src/lib.rs1
-rw-r--r--compiler/rustc_middle/src/mir/pretty.rs63
-rw-r--r--compiler/rustc_middle/src/mir/query.rs68
-rw-r--r--compiler/rustc_middle/src/mir/terminator.rs6
-rw-r--r--compiler/rustc_middle/src/ty/context.rs80
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs60
-rw-r--r--compiler/rustc_passes/src/errors.rs2
-rw-r--r--compiler/rustc_passes/src/liveness.rs16
-rw-r--r--compiler/rustc_target/src/callconv/s390x.rs14
-rw-r--r--library/alloc/benches/btree/map.rs1
-rw-r--r--library/alloc/benches/slice.rs11
-rw-r--r--library/alloc/benches/vec.rs5
-rw-r--r--library/alloc/tests/collections/binary_heap.rs2
-rw-r--r--library/alloc/tests/lib.rs3
-rw-r--r--library/alloc/tests/slice.rs1
-rw-r--r--library/alloc/tests/sort/tests.rs9
-rw-r--r--library/alloc/tests/sync.rs1
-rw-r--r--library/alloc/tests/vec.rs4
-rw-r--r--library/core/tests/hash/mod.rs3
-rw-r--r--library/core/tests/num/flt2dec/random.rs6
-rw-r--r--library/core/tests/num/ops.rs52
-rw-r--r--library/core/tests/num/wrapping.rs2
-rw-r--r--library/std/src/f64/tests.rs3
-rw-r--r--library/std/src/io/copy/tests.rs1
-rw-r--r--library/std/src/io/tests.rs2
-rw-r--r--library/std/src/sys/alloc/wasm.rs4
-rw-r--r--library/std/src/sys/pal/wasi/mod.rs3
-rw-r--r--library/std/src/sys/pal/wasm/mod.rs2
-rw-r--r--library/std/tests/pipe_subprocess.rs2
-rw-r--r--library/std/tests/process_spawning.rs3
-rw-r--r--library/test/src/cli.rs8
-rw-r--r--library/test/src/tests.rs10
-rw-r--r--src/doc/rustc/src/platform-support/wasm32-unknown-emscripten.md2
-rw-r--r--src/doc/rustc/src/tests/index.md2
-rw-r--r--tests/codegen/s390x-simd.rs143
-rw-r--r--tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.fixed18
-rw-r--r--tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs20
-rw-r--r--tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr77
-rw-r--r--tests/ui/wasm/wasm-bindgen-broken-error.rs28
-rw-r--r--tests/ui/wasm/wasm-bindgen-broken-error.v0_1_0.stderr8
-rw-r--r--tests/ui/wasm/wasm-bindgen-broken-error.v0_2_87.stderr8
55 files changed, 730 insertions, 396 deletions
diff --git a/compiler/rustc_borrowck/src/polonius/dump.rs b/compiler/rustc_borrowck/src/polonius/dump.rs
index a6d80149034..40e801d0388 100644
--- a/compiler/rustc_borrowck/src/polonius/dump.rs
+++ b/compiler/rustc_borrowck/src/polonius/dump.rs
@@ -1,7 +1,9 @@
 use std::io;
 
-use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options};
-use rustc_middle::mir::{Body, ClosureRegionRequirements, PassWhere};
+use rustc_middle::mir::pretty::{
+    PassWhere, PrettyPrintMirOptions, create_dump_file, dump_enabled, dump_mir_to_writer,
+};
+use rustc_middle::mir::{Body, ClosureRegionRequirements};
 use rustc_middle::ty::TyCtxt;
 use rustc_session::config::MirIncludeSpans;
 
@@ -10,9 +12,6 @@ use crate::polonius::{LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSe
 use crate::{BorrowckInferCtxt, RegionInferenceContext};
 
 /// `-Zdump-mir=polonius` dumps MIR annotated with NLL and polonius specific information.
-// Note: this currently duplicates most of NLL MIR, with some additions for the localized outlives
-// constraints. This is ok for now as this dump will change in the near future to an HTML file to
-// become more useful.
 pub(crate) fn dump_polonius_mir<'tcx>(
     infcx: &BorrowckInferCtxt<'tcx>,
     body: &Body<'tcx>,
@@ -26,12 +25,100 @@ pub(crate) fn dump_polonius_mir<'tcx>(
         return;
     }
 
+    if !dump_enabled(tcx, "polonius", body.source.def_id()) {
+        return;
+    }
+
     let localized_outlives_constraints = localized_outlives_constraints
         .expect("missing localized constraints with `-Zpolonius=next`");
 
-    // We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in
-    // #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example,
-    // they're always disabled in mir-opt tests to make working with blessed dumps easier.
+    let _: io::Result<()> = try {
+        let mut file = create_dump_file(tcx, "html", false, "polonius", &0, body)?;
+        emit_polonius_dump(
+            tcx,
+            body,
+            regioncx,
+            borrow_set,
+            localized_outlives_constraints,
+            closure_region_requirements,
+            &mut file,
+        )?;
+    };
+}
+
+/// The polonius dump consists of:
+/// - the NLL MIR
+/// - the list of polonius localized constraints
+/// - a mermaid graph of the CFG
+fn emit_polonius_dump<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    body: &Body<'tcx>,
+    regioncx: &RegionInferenceContext<'tcx>,
+    borrow_set: &BorrowSet<'tcx>,
+    localized_outlives_constraints: LocalizedOutlivesConstraintSet,
+    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
+    out: &mut dyn io::Write,
+) -> io::Result<()> {
+    // Prepare the HTML dump file prologue.
+    writeln!(out, "<!DOCTYPE html>")?;
+    writeln!(out, "<html>")?;
+    writeln!(out, "<head><title>Polonius MIR dump</title></head>")?;
+    writeln!(out, "<body>")?;
+
+    // Section 1: the NLL + Polonius MIR.
+    writeln!(out, "<div>")?;
+    writeln!(out, "Raw MIR dump")?;
+    writeln!(out, "<code><pre>")?;
+    emit_html_mir(
+        tcx,
+        body,
+        regioncx,
+        borrow_set,
+        localized_outlives_constraints,
+        closure_region_requirements,
+        out,
+    )?;
+    writeln!(out, "</pre></code>")?;
+    writeln!(out, "</div>")?;
+
+    // Section 2: mermaid visualization of the CFG.
+    writeln!(out, "<div>")?;
+    writeln!(out, "Control-flow graph")?;
+    writeln!(out, "<code><pre class='mermaid'>")?;
+    emit_mermaid_cfg(body, out)?;
+    writeln!(out, "</pre></code>")?;
+    writeln!(out, "</div>")?;
+
+    // Finalize the dump with the HTML epilogue.
+    writeln!(
+        out,
+        "<script src='https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js'></script>"
+    )?;
+    writeln!(out, "<script>")?;
+    writeln!(out, "mermaid.initialize({{ startOnLoad: false, maxEdges: 100 }});")?;
+    writeln!(out, "mermaid.run({{ querySelector: '.mermaid' }})")?;
+    writeln!(out, "</script>")?;
+    writeln!(out, "</body>")?;
+    writeln!(out, "</html>")?;
+
+    Ok(())
+}
+
+/// Emits the polonius MIR, as escaped HTML.
+fn emit_html_mir<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    body: &Body<'tcx>,
+    regioncx: &RegionInferenceContext<'tcx>,
+    borrow_set: &BorrowSet<'tcx>,
+    localized_outlives_constraints: LocalizedOutlivesConstraintSet,
+    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
+    out: &mut dyn io::Write,
+) -> io::Result<()> {
+    // Buffer the regular MIR dump to be able to escape it.
+    let mut buffer = Vec::new();
+
+    // We want the NLL extra comments printed by default in NLL MIR dumps. Specifying `-Z
+    // mir-include-spans` on the CLI still has priority.
     let options = PrettyPrintMirOptions {
         include_extra_comments: matches!(
             tcx.sess.opts.unstable_opts.mir_include_spans,
@@ -39,12 +126,12 @@ pub(crate) fn dump_polonius_mir<'tcx>(
         ),
     };
 
-    dump_mir_with_options(
+    dump_mir_to_writer(
         tcx,
-        false,
         "polonius",
         &0,
         body,
+        &mut buffer,
         |pass_where, out| {
             emit_polonius_mir(
                 tcx,
@@ -57,7 +144,27 @@ pub(crate) fn dump_polonius_mir<'tcx>(
             )
         },
         options,
-    );
+    )?;
+
+    // Escape the handful of characters that need it. We don't need to be particularly efficient:
+    // we're actually writing into a buffered writer already. Note that MIR dumps are valid UTF-8.
+    let buffer = String::from_utf8_lossy(&buffer);
+    for ch in buffer.chars() {
+        let escaped = match ch {
+            '>' => "&gt;",
+            '<' => "&lt;",
+            '&' => "&amp;",
+            '\'' => "&#39;",
+            '"' => "&quot;",
+            _ => {
+                // The common case, no escaping needed.
+                write!(out, "{}", ch)?;
+                continue;
+            }
+        };
+        write!(out, "{}", escaped)?;
+    }
+    Ok(())
 }
 
 /// Produces the actual NLL + Polonius MIR sections to emit during the dumping process.
@@ -102,3 +209,55 @@ fn emit_polonius_mir<'tcx>(
 
     Ok(())
 }
+
+/// Emits a mermaid flowchart of the CFG blocks and edges, similar to the graphviz version.
+fn emit_mermaid_cfg(body: &Body<'_>, out: &mut dyn io::Write) -> io::Result<()> {
+    use rustc_middle::mir::{TerminatorEdges, TerminatorKind};
+
+    // The mermaid chart type: a top-down flowchart.
+    writeln!(out, "flowchart TD")?;
+
+    // Emit the block nodes.
+    for (block_idx, block) in body.basic_blocks.iter_enumerated() {
+        let block_idx = block_idx.as_usize();
+        let cleanup = if block.is_cleanup { " (cleanup)" } else { "" };
+        writeln!(out, "{block_idx}[\"bb{block_idx}{cleanup}\"]")?;
+    }
+
+    // Emit the edges between blocks, from the terminator edges.
+    for (block_idx, block) in body.basic_blocks.iter_enumerated() {
+        let block_idx = block_idx.as_usize();
+        let terminator = block.terminator();
+        match terminator.edges() {
+            TerminatorEdges::None => {}
+            TerminatorEdges::Single(bb) => {
+                writeln!(out, "{block_idx} --> {}", bb.as_usize())?;
+            }
+            TerminatorEdges::Double(bb1, bb2) => {
+                if matches!(terminator.kind, TerminatorKind::FalseEdge { .. }) {
+                    writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
+                    writeln!(out, "{block_idx} -- imaginary --> {}", bb2.as_usize())?;
+                } else {
+                    writeln!(out, "{block_idx} --> {}", bb1.as_usize())?;
+                    writeln!(out, "{block_idx} -- unwind --> {}", bb2.as_usize())?;
+                }
+            }
+            TerminatorEdges::AssignOnReturn { return_, cleanup, .. } => {
+                for to_idx in return_ {
+                    writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
+                }
+
+                if let Some(to_idx) = cleanup {
+                    writeln!(out, "{block_idx} -- unwind --> {}", to_idx.as_usize())?;
+                }
+            }
+            TerminatorEdges::SwitchInt { targets, .. } => {
+                for to_idx in targets.all_targets() {
+                    writeln!(out, "{block_idx} --> {}", to_idx.as_usize())?;
+                }
+            }
+        }
+    }
+
+    Ok(())
+}
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index 5075ed86a6a..a396d705cbb 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -1285,13 +1285,13 @@ impl fmt::Debug for OwnerNodes<'_> {
             .field("node", &self.nodes[ItemLocalId::ZERO])
             .field(
                 "parents",
-                &self
-                    .nodes
-                    .iter_enumerated()
-                    .map(|(id, parented_node)| {
-                        debug_fn(move |f| write!(f, "({id:?}, {:?})", parented_node.parent))
-                    })
-                    .collect::<Vec<_>>(),
+                &fmt::from_fn(|f| {
+                    f.debug_list()
+                        .entries(self.nodes.iter_enumerated().map(|(id, parented_node)| {
+                            fmt::from_fn(move |f| write!(f, "({id:?}, {:?})", parented_node.parent))
+                        }))
+                        .finish()
+                }),
             )
             .field("bodies", &self.bodies)
             .field("opt_hash_including_bodies", &self.opt_hash_including_bodies)
@@ -4638,15 +4638,5 @@ mod size_asserts {
     // tidy-alphabetical-end
 }
 
-fn debug_fn(f: impl Fn(&mut fmt::Formatter<'_>) -> fmt::Result) -> impl fmt::Debug {
-    struct DebugFn<F>(F);
-    impl<F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result> fmt::Debug for DebugFn<F> {
-        fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
-            (self.0)(fmt)
-        }
-    }
-    DebugFn(f)
-}
-
 #[cfg(test)]
 mod tests;
diff --git a/compiler/rustc_hir/src/lib.rs b/compiler/rustc_hir/src/lib.rs
index 8ec2054bf53..705c167e258 100644
--- a/compiler/rustc_hir/src/lib.rs
+++ b/compiler/rustc_hir/src/lib.rs
@@ -6,6 +6,7 @@
 #![allow(internal_features)]
 #![feature(associated_type_defaults)]
 #![feature(closure_track_caller)]
+#![feature(debug_closure_helpers)]
 #![feature(exhaustive_patterns)]
 #![feature(let_chains)]
 #![feature(never_type)]
diff --git a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
index 582a2c7a0fc..72baf5c4b58 100644
--- a/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
+++ b/compiler/rustc_hir_analysis/src/collect/resolve_bound_vars.rs
@@ -184,29 +184,11 @@ enum Scope<'a> {
     },
 }
 
-#[derive(Copy, Clone, Debug)]
-enum BinderScopeType {
-    /// Any non-concatenating binder scopes.
-    Normal,
-    /// Within a syntactic trait ref, there may be multiple poly trait refs that
-    /// are nested (under the `associated_type_bounds` feature). The binders of
-    /// the inner poly trait refs are extended from the outer poly trait refs
-    /// and don't increase the late bound depth. If you had
-    /// `T: for<'a>  Foo<Bar: for<'b> Baz<'a, 'b>>`, then the `for<'b>` scope
-    /// would be `Concatenating`. This also used in trait refs in where clauses
-    /// where we have two binders `for<> T: for<> Foo` (I've intentionally left
-    /// out any lifetimes because they aren't needed to show the two scopes).
-    /// The inner `for<>` has a scope of `Concatenating`.
-    Concatenating,
-}
-
-// A helper struct for debugging scopes without printing parent scopes
-struct TruncatedScopeDebug<'a>(&'a Scope<'a>);
-
-impl<'a> fmt::Debug for TruncatedScopeDebug<'a> {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self.0 {
-            Scope::Binder { bound_vars, scope_type, hir_id, where_bound_origin, s: _ } => f
+impl<'a> Scope<'a> {
+    // A helper for debugging scopes without printing parent scopes
+    fn debug_truncated(&'a self) -> impl fmt::Debug + 'a {
+        fmt::from_fn(move |f| match self {
+            Self::Binder { bound_vars, scope_type, hir_id, where_bound_origin, s: _ } => f
                 .debug_struct("Binder")
                 .field("bound_vars", bound_vars)
                 .field("scope_type", scope_type)
@@ -214,38 +196,54 @@ impl<'a> fmt::Debug for TruncatedScopeDebug<'a> {
                 .field("where_bound_origin", where_bound_origin)
                 .field("s", &"..")
                 .finish(),
-            Scope::Opaque { captures, def_id, s: _ } => f
+            Self::Opaque { captures, def_id, s: _ } => f
                 .debug_struct("Opaque")
                 .field("def_id", def_id)
                 .field("captures", &captures.borrow())
                 .field("s", &"..")
                 .finish(),
-            Scope::Body { id, s: _ } => {
+            Self::Body { id, s: _ } => {
                 f.debug_struct("Body").field("id", id).field("s", &"..").finish()
             }
-            Scope::ObjectLifetimeDefault { lifetime, s: _ } => f
+            Self::ObjectLifetimeDefault { lifetime, s: _ } => f
                 .debug_struct("ObjectLifetimeDefault")
                 .field("lifetime", lifetime)
                 .field("s", &"..")
                 .finish(),
-            Scope::Supertrait { bound_vars, s: _ } => f
+            Self::Supertrait { bound_vars, s: _ } => f
                 .debug_struct("Supertrait")
                 .field("bound_vars", bound_vars)
                 .field("s", &"..")
                 .finish(),
-            Scope::TraitRefBoundary { s: _ } => f.debug_struct("TraitRefBoundary").finish(),
-            Scope::LateBoundary { s: _, what, deny_late_regions } => f
+            Self::TraitRefBoundary { s: _ } => f.debug_struct("TraitRefBoundary").finish(),
+            Self::LateBoundary { s: _, what, deny_late_regions } => f
                 .debug_struct("LateBoundary")
                 .field("what", what)
                 .field("deny_late_regions", deny_late_regions)
                 .finish(),
-            Scope::Root { opt_parent_item } => {
+            Self::Root { opt_parent_item } => {
                 f.debug_struct("Root").field("opt_parent_item", &opt_parent_item).finish()
             }
-        }
+        })
     }
 }
 
+#[derive(Copy, Clone, Debug)]
+enum BinderScopeType {
+    /// Any non-concatenating binder scopes.
+    Normal,
+    /// Within a syntactic trait ref, there may be multiple poly trait refs that
+    /// are nested (under the `associated_type_bounds` feature). The binders of
+    /// the inner poly trait refs are extended from the outer poly trait refs
+    /// and don't increase the late bound depth. If you had
+    /// `T: for<'a>  Foo<Bar: for<'b> Baz<'a, 'b>>`, then the `for<'b>` scope
+    /// would be `Concatenating`. This also used in trait refs in where clauses
+    /// where we have two binders `for<> T: for<> Foo` (I've intentionally left
+    /// out any lifetimes because they aren't needed to show the two scopes).
+    /// The inner `for<>` has a scope of `Concatenating`.
+    Concatenating,
+}
+
 type ScopeRef<'a> = &'a Scope<'a>;
 
 pub(crate) fn provide(providers: &mut Providers) {
@@ -1144,7 +1142,7 @@ impl<'a, 'tcx> BoundVarContext<'a, 'tcx> {
     {
         let BoundVarContext { tcx, map, .. } = self;
         let mut this = BoundVarContext { tcx: *tcx, map, scope: &wrap_scope };
-        let span = debug_span!("scope", scope = ?TruncatedScopeDebug(this.scope));
+        let span = debug_span!("scope", scope = ?this.scope.debug_truncated());
         {
             let _enter = span.enter();
             f(&mut this);
diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs
index a42a168234f..bc7d4365eee 100644
--- a/compiler/rustc_hir_analysis/src/lib.rs
+++ b/compiler/rustc_hir_analysis/src/lib.rs
@@ -63,6 +63,7 @@ This API is completely unstable and subject to change.
 #![doc(rust_logo)]
 #![feature(assert_matches)]
 #![feature(coroutines)]
+#![feature(debug_closure_helpers)]
 #![feature(if_let_guard)]
 #![feature(iter_from_coroutine)]
 #![feature(iter_intersperse)]
diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs
index 367e7c6de95..bc076670585 100644
--- a/compiler/rustc_hir_typeck/src/demand.rs
+++ b/compiler/rustc_hir_typeck/src/demand.rs
@@ -851,32 +851,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
             && let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = pat.kind
 
             // Look for the type corresponding to the argument pattern we have in the argument list.
-            && let Some(ty_sugg) = fn_decl
+            && let Some(ty_ref) = fn_decl
                 .inputs
                 .iter()
-                .filter_map(|ty| {
-                    if ty.span == *ty_span
-                        && let hir::TyKind::Ref(lt, x) = ty.kind
-                    {
-                        // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
-                        Some((
-                            x.ty.span.shrink_to_lo(),
-                            format!(
-                                "{}mut ",
-                                if lt.ident.span.lo() == lt.ident.span.hi() { "" } else { " " }
-                            ),
-                        ))
-                    } else {
-                        None
-                    }
+                .filter_map(|ty| match ty.kind {
+                    hir::TyKind::Ref(lt, mut_ty) if ty.span == *ty_span => Some((lt, mut_ty)),
+                    _ => None,
                 })
                 .next()
         {
-            let sugg = vec![
-                ty_sugg,
+            let mut sugg = if ty_ref.1.mutbl.is_mut() {
+                // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028).
+                vec![]
+            } else {
+                // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
+                vec![(
+                    ty_ref.1.ty.span.shrink_to_lo(),
+                    format!(
+                        "{}mut ",
+                        if ty_ref.0.ident.span.lo() == ty_ref.0.ident.span.hi() { "" } else { " " },
+                    ),
+                )]
+            };
+            sugg.extend([
                 (pat.span.until(ident.span), String::new()),
                 (lhs.span.shrink_to_lo(), "*".to_string()),
-            ];
+            ]);
             // We suggest changing the argument from `mut ident: &Ty` to `ident: &'_ mut Ty` and the
             // assignment from `ident = val;` to `*ident = val;`.
             err.multipart_suggestion_verbose(
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index 7e64f7f002f..0c1bd08474f 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -974,6 +974,3 @@ lint_uses_power_alignment = repr(C) does not follow the power alignment rule. Th
 
 lint_variant_size_differences =
     enum variant is more than three times larger ({$largest} bytes) than the next largest
-
-lint_wasm_c_abi =
-    older versions of the `wasm-bindgen` crate will be incompatible with future versions of Rust; please update to `wasm-bindgen` v0.2.88
diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs
index 6d73715562b..92dbc76a7b5 100644
--- a/compiler/rustc_lint/src/early/diagnostics.rs
+++ b/compiler/rustc_lint/src/early/diagnostics.rs
@@ -430,7 +430,6 @@ pub(super) fn decorate_lint(
         BuiltinLintDiag::UnusedCrateDependency { extern_crate, local_crate } => {
             lints::UnusedCrateDependency { extern_crate, local_crate }.decorate_lint(diag)
         }
-        BuiltinLintDiag::WasmCAbi => lints::WasmCAbi.decorate_lint(diag),
         BuiltinLintDiag::IllFormedAttributeInput { suggestions } => {
             lints::IllFormedAttributeInput {
                 num_suggestions: suggestions.len(),
diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs
index 0b7d1f45f2a..3163bc8a300 100644
--- a/compiler/rustc_lint/src/lints.rs
+++ b/compiler/rustc_lint/src/lints.rs
@@ -2554,10 +2554,6 @@ pub(crate) struct UnusedCrateDependency {
 }
 
 #[derive(LintDiagnostic)]
-#[diag(lint_wasm_c_abi)]
-pub(crate) struct WasmCAbi;
-
-#[derive(LintDiagnostic)]
 #[diag(lint_ill_formed_attribute_input)]
 pub(crate) struct IllFormedAttributeInput {
     pub num_suggestions: usize,
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 5cdd18f5ea9..8bf01adc5b5 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -144,7 +144,6 @@ declare_lint_pass! {
         UNUSED_VARIABLES,
         USELESS_DEPRECATED,
         WARNINGS,
-        WASM_C_ABI,
         // tidy-alphabetical-end
     ]
 }
@@ -4682,44 +4681,6 @@ declare_lint! {
 }
 
 declare_lint! {
-    /// The `wasm_c_abi` lint detects crate dependencies that are incompatible
-    /// with future versions of Rust that will emit spec-compliant C ABI.
-    ///
-    /// ### Example
-    ///
-    /// ```rust,ignore (needs extern crate)
-    /// #![deny(wasm_c_abi)]
-    /// ```
-    ///
-    /// This will produce:
-    ///
-    /// ```text
-    /// error: the following packages contain code that will be rejected by a future version of Rust: wasm-bindgen v0.2.87
-    ///   |
-    /// note: the lint level is defined here
-    ///  --> src/lib.rs:1:9
-    ///   |
-    /// 1 | #![deny(wasm_c_abi)]
-    ///   |         ^^^^^^^^^^
-    /// ```
-    ///
-    /// ### Explanation
-    ///
-    /// Rust has historically emitted non-spec-compliant C ABI. This has caused
-    /// incompatibilities between other compilers and Wasm targets. In a future
-    /// version of Rust this will be fixed and therefore dependencies relying
-    /// on the non-spec-compliant C ABI will stop functioning.
-    pub WASM_C_ABI,
-    Deny,
-    "detects dependencies that are incompatible with the Wasm C ABI",
-    @future_incompatible = FutureIncompatibleInfo {
-        reason: FutureIncompatibilityReason::FutureReleaseErrorReportInDeps,
-        reference: "issue #71871 <https://github.com/rust-lang/rust/issues/71871>",
-    };
-    crate_level_only
-}
-
-declare_lint! {
     /// The `uncovered_param_in_projection` lint detects a violation of one of Rust's orphan rules for
     /// foreign trait implementations that concerns the use of type parameters inside trait associated
     /// type paths ("projections") whose output may not be a local type that is mistakenly considered
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 9f4c5d89d0e..3f1e98556ef 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -795,7 +795,6 @@ pub enum BuiltinLintDiag {
         extern_crate: Symbol,
         local_crate: Symbol,
     },
-    WasmCAbi,
     IllFormedAttributeInput {
         suggestions: Vec<String>,
     },
diff --git a/compiler/rustc_metadata/messages.ftl b/compiler/rustc_metadata/messages.ftl
index 6d7d88fa8d7..d2b5ae53185 100644
--- a/compiler/rustc_metadata/messages.ftl
+++ b/compiler/rustc_metadata/messages.ftl
@@ -290,6 +290,9 @@ metadata_unsupported_abi =
 metadata_unsupported_abi_i686 =
     ABI not supported by `#[link(kind = "raw-dylib")]` on i686
 
+metadata_wasm_c_abi =
+    older versions of the `wasm-bindgen` crate are incompatible with current versions of Rust; please update to `wasm-bindgen` v0.2.88
+
 metadata_wasm_import_form =
     wasm import module must be of the form `wasm_import_module = "string"`
 
diff --git a/compiler/rustc_metadata/src/creader.rs b/compiler/rustc_metadata/src/creader.rs
index a6db12f8932..a6cd0ecafd0 100644
--- a/compiler/rustc_metadata/src/creader.rs
+++ b/compiler/rustc_metadata/src/creader.rs
@@ -1076,12 +1076,7 @@ impl<'a, 'tcx> CrateLoader<'a, 'tcx> {
             // Make a point span rather than covering the whole file
             let span = krate.spans.inner_span.shrink_to_lo();
 
-            self.sess.psess.buffer_lint(
-                lint::builtin::WASM_C_ABI,
-                span,
-                ast::CRATE_NODE_ID,
-                BuiltinLintDiag::WasmCAbi,
-            );
+            self.sess.dcx().emit_err(errors::WasmCAbi { span });
         }
     }
 
diff --git a/compiler/rustc_metadata/src/errors.rs b/compiler/rustc_metadata/src/errors.rs
index b6c5619ec18..cefc6498f68 100644
--- a/compiler/rustc_metadata/src/errors.rs
+++ b/compiler/rustc_metadata/src/errors.rs
@@ -732,3 +732,10 @@ pub struct ImportNameTypeRaw {
     #[primary_span]
     pub span: Span,
 }
+
+#[derive(Diagnostic)]
+#[diag(metadata_wasm_c_abi)]
+pub(crate) struct WasmCAbi {
+    #[primary_span]
+    pub span: Span,
+}
diff --git a/compiler/rustc_middle/src/lib.rs b/compiler/rustc_middle/src/lib.rs
index 04a06ba7464..bbe23d8abe8 100644
--- a/compiler/rustc_middle/src/lib.rs
+++ b/compiler/rustc_middle/src/lib.rs
@@ -40,6 +40,7 @@
 #![feature(const_type_name)]
 #![feature(core_intrinsics)]
 #![feature(coroutines)]
+#![feature(debug_closure_helpers)]
 #![feature(decl_macro)]
 #![feature(discriminant_kind)]
 #![feature(extern_types)]
diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs
index ea35323ccc7..3b4fba97e60 100644
--- a/compiler/rustc_middle/src/mir/pretty.rs
+++ b/compiler/rustc_middle/src/mir/pretty.rs
@@ -1,8 +1,7 @@
 use std::collections::BTreeSet;
 use std::fmt::{Display, Write as _};
-use std::fs;
-use std::io::{self, Write as _};
 use std::path::{Path, PathBuf};
+use std::{fs, io};
 
 use rustc_abi::Size;
 use rustc_ast::InlineAsmTemplatePiece;
@@ -149,37 +148,59 @@ pub fn dump_enabled(tcx: TyCtxt<'_>, pass_name: &str, def_id: DefId) -> bool {
 // `def_path_str()` would otherwise trigger `type_of`, and this can
 // run while we are already attempting to evaluate `type_of`.
 
+/// Most use-cases of dumping MIR should use the [dump_mir] entrypoint instead, which will also
+/// check if dumping MIR is enabled, and if this body matches the filters passed on the CLI.
+///
+/// That being said, if the above requirements have been validated already, this function is where
+/// most of the MIR dumping occurs, if one needs to export it to a file they have created with
+/// [create_dump_file], rather than to a new file created as part of [dump_mir], or to stdout/stderr
+/// for debugging purposes.
+pub fn dump_mir_to_writer<'tcx, F>(
+    tcx: TyCtxt<'tcx>,
+    pass_name: &str,
+    disambiguator: &dyn Display,
+    body: &Body<'tcx>,
+    w: &mut dyn io::Write,
+    mut extra_data: F,
+    options: PrettyPrintMirOptions,
+) -> io::Result<()>
+where
+    F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>,
+{
+    // see notes on #41697 above
+    let def_path =
+        ty::print::with_forced_impl_filename_line!(tcx.def_path_str(body.source.def_id()));
+    // ignore-tidy-odd-backticks the literal below is fine
+    write!(w, "// MIR for `{def_path}")?;
+    match body.source.promoted {
+        None => write!(w, "`")?,
+        Some(promoted) => write!(w, "::{promoted:?}`")?,
+    }
+    writeln!(w, " {disambiguator} {pass_name}")?;
+    if let Some(ref layout) = body.coroutine_layout_raw() {
+        writeln!(w, "/* coroutine_layout = {layout:#?} */")?;
+    }
+    writeln!(w)?;
+    extra_data(PassWhere::BeforeCFG, w)?;
+    write_user_type_annotations(tcx, body, w)?;
+    write_mir_fn(tcx, body, &mut extra_data, w, options)?;
+    extra_data(PassWhere::AfterCFG, w)
+}
+
 fn dump_matched_mir_node<'tcx, F>(
     tcx: TyCtxt<'tcx>,
     pass_num: bool,
     pass_name: &str,
     disambiguator: &dyn Display,
     body: &Body<'tcx>,
-    mut extra_data: F,
+    extra_data: F,
     options: PrettyPrintMirOptions,
 ) where
     F: FnMut(PassWhere, &mut dyn io::Write) -> io::Result<()>,
 {
     let _: io::Result<()> = try {
         let mut file = create_dump_file(tcx, "mir", pass_num, pass_name, disambiguator, body)?;
-        // see notes on #41697 above
-        let def_path =
-            ty::print::with_forced_impl_filename_line!(tcx.def_path_str(body.source.def_id()));
-        // ignore-tidy-odd-backticks the literal below is fine
-        write!(file, "// MIR for `{def_path}")?;
-        match body.source.promoted {
-            None => write!(file, "`")?,
-            Some(promoted) => write!(file, "::{promoted:?}`")?,
-        }
-        writeln!(file, " {disambiguator} {pass_name}")?;
-        if let Some(ref layout) = body.coroutine_layout_raw() {
-            writeln!(file, "/* coroutine_layout = {layout:#?} */")?;
-        }
-        writeln!(file)?;
-        extra_data(PassWhere::BeforeCFG, &mut file)?;
-        write_user_type_annotations(tcx, body, &mut file)?;
-        write_mir_fn(tcx, body, &mut extra_data, &mut file, options)?;
-        extra_data(PassWhere::AfterCFG, &mut file)?;
+        dump_mir_to_writer(tcx, pass_name, disambiguator, body, &mut file, extra_data, options)?;
     };
 
     if tcx.sess.opts.unstable_opts.dump_mir_graphviz {
diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs
index db5da941f1e..50494355e3e 100644
--- a/compiler/rustc_middle/src/mir/query.rs
+++ b/compiler/rustc_middle/src/mir/query.rs
@@ -1,6 +1,5 @@
 //! Values computed by queries that use MIR.
 
-use std::cell::Cell;
 use std::fmt::{self, Debug};
 
 use rustc_abi::{FieldIdx, VariantIdx};
@@ -62,55 +61,26 @@ pub struct CoroutineLayout<'tcx> {
 
 impl Debug for CoroutineLayout<'_> {
     fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
-        /// Prints an iterator of (key, value) tuples as a map.
-        struct MapPrinter<'a, K, V>(Cell<Option<Box<dyn Iterator<Item = (K, V)> + 'a>>>);
-        impl<'a, K, V> MapPrinter<'a, K, V> {
-            fn new(iter: impl Iterator<Item = (K, V)> + 'a) -> Self {
-                Self(Cell::new(Some(Box::new(iter))))
-            }
-        }
-        impl<'a, K: Debug, V: Debug> Debug for MapPrinter<'a, K, V> {
-            fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
-                fmt.debug_map().entries(self.0.take().unwrap()).finish()
-            }
-        }
-
-        /// Prints the coroutine variant name.
-        struct GenVariantPrinter(VariantIdx);
-        impl From<VariantIdx> for GenVariantPrinter {
-            fn from(idx: VariantIdx) -> Self {
-                GenVariantPrinter(idx)
-            }
-        }
-        impl Debug for GenVariantPrinter {
-            fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
-                let variant_name = ty::CoroutineArgs::variant_name(self.0);
-                if fmt.alternate() {
-                    write!(fmt, "{:9}({:?})", variant_name, self.0)
-                } else {
-                    write!(fmt, "{variant_name}")
-                }
-            }
-        }
-
-        /// Forces its contents to print in regular mode instead of alternate mode.
-        struct OneLinePrinter<T>(T);
-        impl<T: Debug> Debug for OneLinePrinter<T> {
-            fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
-                write!(fmt, "{:?}", self.0)
-            }
-        }
-
         fmt.debug_struct("CoroutineLayout")
-            .field("field_tys", &MapPrinter::new(self.field_tys.iter_enumerated()))
-            .field(
-                "variant_fields",
-                &MapPrinter::new(
-                    self.variant_fields
-                        .iter_enumerated()
-                        .map(|(k, v)| (GenVariantPrinter(k), OneLinePrinter(v))),
-                ),
-            )
+            .field_with("field_tys", |fmt| {
+                fmt.debug_map().entries(self.field_tys.iter_enumerated()).finish()
+            })
+            .field_with("variant_fields", |fmt| {
+                let mut map = fmt.debug_map();
+                for (idx, fields) in self.variant_fields.iter_enumerated() {
+                    map.key_with(|fmt| {
+                        let variant_name = ty::CoroutineArgs::variant_name(idx);
+                        if fmt.alternate() {
+                            write!(fmt, "{variant_name:9}({idx:?})")
+                        } else {
+                            write!(fmt, "{variant_name}")
+                        }
+                    });
+                    // Force variant fields to print in regular mode instead of alternate mode.
+                    map.value_with(|fmt| write!(fmt, "{fields:?}"));
+                }
+                map.finish()
+            })
             .field("storage_conflicts", &self.storage_conflicts)
             .finish()
     }
diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs
index 473b817aed0..c04a8251fbc 100644
--- a/compiler/rustc_middle/src/mir/terminator.rs
+++ b/compiler/rustc_middle/src/mir/terminator.rs
@@ -581,9 +581,11 @@ impl<'tcx> TerminatorKind<'tcx> {
 pub enum TerminatorEdges<'mir, 'tcx> {
     /// For terminators that have no successor, like `return`.
     None,
-    /// For terminators that a single successor, like `goto`, and `assert` without cleanup block.
+    /// For terminators that have a single successor, like `goto`, and `assert` without a cleanup
+    /// block.
     Single(BasicBlock),
-    /// For terminators that two successors, `assert` with cleanup block and `falseEdge`.
+    /// For terminators that have two successors, like `assert` with a cleanup block, and
+    /// `falseEdge`.
     Double(BasicBlock, BasicBlock),
     /// Special action for `Yield`, `Call` and `InlineAsm` terminators.
     AssignOnReturn {
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index aeb734ba3f6..d1079743004 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -2325,51 +2325,41 @@ macro_rules! sty_debug_print {
 }
 
 impl<'tcx> TyCtxt<'tcx> {
-    pub fn debug_stats(self) -> impl std::fmt::Debug + 'tcx {
-        struct DebugStats<'tcx>(TyCtxt<'tcx>);
-
-        impl<'tcx> std::fmt::Debug for DebugStats<'tcx> {
-            fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-                sty_debug_print!(
-                    fmt,
-                    self.0,
-                    Adt,
-                    Array,
-                    Slice,
-                    RawPtr,
-                    Ref,
-                    FnDef,
-                    FnPtr,
-                    UnsafeBinder,
-                    Placeholder,
-                    Coroutine,
-                    CoroutineWitness,
-                    Dynamic,
-                    Closure,
-                    CoroutineClosure,
-                    Tuple,
-                    Bound,
-                    Param,
-                    Infer,
-                    Alias,
-                    Pat,
-                    Foreign
-                )?;
-
-                writeln!(fmt, "GenericArgs interner: #{}", self.0.interners.args.len())?;
-                writeln!(fmt, "Region interner: #{}", self.0.interners.region.len())?;
-                writeln!(
-                    fmt,
-                    "Const Allocation interner: #{}",
-                    self.0.interners.const_allocation.len()
-                )?;
-                writeln!(fmt, "Layout interner: #{}", self.0.interners.layout.len())?;
-
-                Ok(())
-            }
-        }
-
-        DebugStats(self)
+    pub fn debug_stats(self) -> impl fmt::Debug + 'tcx {
+        fmt::from_fn(move |fmt| {
+            sty_debug_print!(
+                fmt,
+                self,
+                Adt,
+                Array,
+                Slice,
+                RawPtr,
+                Ref,
+                FnDef,
+                FnPtr,
+                UnsafeBinder,
+                Placeholder,
+                Coroutine,
+                CoroutineWitness,
+                Dynamic,
+                Closure,
+                CoroutineClosure,
+                Tuple,
+                Bound,
+                Param,
+                Infer,
+                Alias,
+                Pat,
+                Foreign
+            )?;
+
+            writeln!(fmt, "GenericArgs interner: #{}", self.interners.args.len())?;
+            writeln!(fmt, "Region interner: #{}", self.interners.region.len())?;
+            writeln!(fmt, "Const Allocation interner: #{}", self.interners.const_allocation.len())?;
+            writeln!(fmt, "Layout interner: #{}", self.interners.layout.len())?;
+
+            Ok(())
+        })
     }
 }
 
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 25d8eb9b453..714a60cb179 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -1598,45 +1598,35 @@ impl<'a> Parser<'a> {
     // Only used when debugging.
     #[allow(unused)]
     pub(crate) fn debug_lookahead(&self, lookahead: usize) -> impl fmt::Debug + '_ {
-        struct DebugParser<'dbg> {
-            parser: &'dbg Parser<'dbg>,
-            lookahead: usize,
-        }
-
-        impl fmt::Debug for DebugParser<'_> {
-            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-                let Self { parser, lookahead } = self;
-                let mut dbg_fmt = f.debug_struct("Parser"); // or at least, one view of
-
-                // we don't need N spans, but we want at least one, so print all of prev_token
-                dbg_fmt.field("prev_token", &parser.prev_token);
-                let mut tokens = vec![];
-                for i in 0..*lookahead {
-                    let tok = parser.look_ahead(i, |tok| tok.kind.clone());
-                    let is_eof = tok == TokenKind::Eof;
-                    tokens.push(tok);
-                    if is_eof {
-                        // Don't look ahead past EOF.
-                        break;
-                    }
-                }
-                dbg_fmt.field_with("tokens", |field| field.debug_list().entries(tokens).finish());
-                dbg_fmt.field("approx_token_stream_pos", &parser.num_bump_calls);
-
-                // some fields are interesting for certain values, as they relate to macro parsing
-                if let Some(subparser) = parser.subparser_name {
-                    dbg_fmt.field("subparser_name", &subparser);
-                }
-                if let Recovery::Forbidden = parser.recovery {
-                    dbg_fmt.field("recovery", &parser.recovery);
+        fmt::from_fn(move |f| {
+            let mut dbg_fmt = f.debug_struct("Parser"); // or at least, one view of
+
+            // we don't need N spans, but we want at least one, so print all of prev_token
+            dbg_fmt.field("prev_token", &self.prev_token);
+            let mut tokens = vec![];
+            for i in 0..lookahead {
+                let tok = self.look_ahead(i, |tok| tok.kind.clone());
+                let is_eof = tok == TokenKind::Eof;
+                tokens.push(tok);
+                if is_eof {
+                    // Don't look ahead past EOF.
+                    break;
                 }
+            }
+            dbg_fmt.field_with("tokens", |field| field.debug_list().entries(tokens).finish());
+            dbg_fmt.field("approx_token_stream_pos", &self.num_bump_calls);
 
-                // imply there's "more to know" than this view
-                dbg_fmt.finish_non_exhaustive()
+            // some fields are interesting for certain values, as they relate to macro parsing
+            if let Some(subparser) = self.subparser_name {
+                dbg_fmt.field("subparser_name", &subparser);
+            }
+            if let Recovery::Forbidden = self.recovery {
+                dbg_fmt.field("recovery", &self.recovery);
             }
-        }
 
-        DebugParser { parser: self, lookahead }
+            // imply there's "more to know" than this view
+            dbg_fmt.finish_non_exhaustive()
+        })
     }
 
     pub fn clear_expected_token_types(&mut self) {
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 196a0a46962..0aa50ad19ff 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1791,7 +1791,7 @@ pub(crate) struct UnusedAssign {
 pub(crate) struct UnusedAssignSuggestion {
     pub pre: &'static str,
     #[suggestion_part(code = "{pre}mut ")]
-    pub ty_span: Span,
+    pub ty_span: Option<Span>,
     #[suggestion_part(code = "")]
     pub ty_ref_span: Span,
     #[suggestion_part(code = "*")]
diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs
index 426899a4d5c..73da8855e10 100644
--- a/compiler/rustc_passes/src/liveness.rs
+++ b/compiler/rustc_passes/src/liveness.rs
@@ -1620,24 +1620,28 @@ impl<'tcx> Liveness<'_, 'tcx> {
             && let item = self.ir.tcx.hir_owner_node(item_id)
             && let Some(fn_decl) = item.fn_decl()
             && let hir::PatKind::Binding(hir::BindingMode::MUT, _hir_id, ident, _) = pat.kind
-            && let Some((ty_span, pre)) = fn_decl
+            && let Some((lt, mut_ty)) = fn_decl
                 .inputs
                 .iter()
                 .filter_map(|ty| {
                     if ty.span == *ty_span
                         && let hir::TyKind::Ref(lt, mut_ty) = ty.kind
                     {
-                        // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
-                        Some((
-                            mut_ty.ty.span.shrink_to_lo(),
-                            if lt.ident.span.lo() == lt.ident.span.hi() { "" } else { " " },
-                        ))
+                        Some((lt, mut_ty))
                     } else {
                         None
                     }
                 })
                 .next()
         {
+            let ty_span = if mut_ty.mutbl.is_mut() {
+                // Leave `&'name mut Ty` and `&mut Ty` as they are (#136028).
+                None
+            } else {
+                // `&'name Ty` -> `&'name mut Ty` or `&Ty` -> `&mut Ty`
+                Some(mut_ty.ty.span.shrink_to_lo())
+            };
+            let pre = if lt.ident.span.lo() == lt.ident.span.hi() { "" } else { " " };
             Some(errors::UnusedAssignSuggestion {
                 ty_span,
                 pre,
diff --git a/compiler/rustc_target/src/callconv/s390x.rs b/compiler/rustc_target/src/callconv/s390x.rs
index c99eb9226ef..a73c1a0f46c 100644
--- a/compiler/rustc_target/src/callconv/s390x.rs
+++ b/compiler/rustc_target/src/callconv/s390x.rs
@@ -38,9 +38,17 @@ where
     }
 
     let size = arg.layout.size;
-    if size.bits() <= 128 && arg.layout.is_single_vector_element(cx, size) {
-        arg.cast_to(Reg { kind: RegKind::Vector, size });
-        return;
+    if size.bits() <= 128 {
+        if let BackendRepr::Vector { .. } = arg.layout.backend_repr {
+            // pass non-wrapped vector types using `PassMode::Direct`
+            return;
+        }
+
+        if arg.layout.is_single_vector_element(cx, size) {
+            // pass non-transparant wrappers around a vector as `PassMode::Cast`
+            arg.cast_to(Reg { kind: RegKind::Vector, size });
+            return;
+        }
     }
     if !arg.layout.is_aggregate() && size.bits() <= 64 {
         arg.extend_integer_width_to(64);
diff --git a/library/alloc/benches/btree/map.rs b/library/alloc/benches/btree/map.rs
index b8119c9f0eb..5b15aaeddbc 100644
--- a/library/alloc/benches/btree/map.rs
+++ b/library/alloc/benches/btree/map.rs
@@ -353,6 +353,7 @@ pub fn iter_10k(b: &mut Bencher) {
 }
 
 #[bench]
+#[cfg_attr(target_os = "emscripten", ignore)] // hits an OOM
 pub fn iter_1m(b: &mut Bencher) {
     bench_iter(b, 1_000, 1_000_000);
 }
diff --git a/library/alloc/benches/slice.rs b/library/alloc/benches/slice.rs
index 48c74c4491d..c45c3722712 100644
--- a/library/alloc/benches/slice.rs
+++ b/library/alloc/benches/slice.rs
@@ -366,14 +366,25 @@ rotate!(rotate_medium_half, gen_random, 9158, 9158 / 2);
 rotate!(rotate_medium_half_plus_one, gen_random, 9158, 9158 / 2 + 1);
 
 // Intended to use more RAM than the machine has cache
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_by1, gen_random, 5 * 1024 * 1024, 1);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_by9199_u64, gen_random, 5 * 1024 * 1024, 9199);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_by9199_bytes, gen_random_bytes, 5 * 1024 * 1024, 9199);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_by9199_strings, gen_strings, 5 * 1024 * 1024, 9199);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_by9199_big, gen_big_random, 5 * 1024 * 1024, 9199);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_by1234577_u64, gen_random, 5 * 1024 * 1024, 1234577);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_by1234577_bytes, gen_random_bytes, 5 * 1024 * 1024, 1234577);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_by1234577_strings, gen_strings, 5 * 1024 * 1024, 1234577);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_by1234577_big, gen_big_random, 5 * 1024 * 1024, 1234577);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_half, gen_random, 5 * 1024 * 1024, 5 * 1024 * 1024 / 2);
+#[cfg(not(target_os = "emscripten"))] // hits an OOM
 rotate!(rotate_huge_half_plus_one, gen_random, 5 * 1024 * 1024, 5 * 1024 * 1024 / 2 + 1);
diff --git a/library/alloc/benches/vec.rs b/library/alloc/benches/vec.rs
index d29ffae9d70..a725ad6894b 100644
--- a/library/alloc/benches/vec.rs
+++ b/library/alloc/benches/vec.rs
@@ -547,6 +547,11 @@ fn bench_in_place_collect_droppable(b: &mut Bencher) {
     })
 }
 
+// node.js gives out of memory error to use with length 1_100_000
+#[cfg(target_os = "emscripten")]
+const LEN: usize = 4096;
+
+#[cfg(not(target_os = "emscripten"))]
 const LEN: usize = 16384;
 
 #[bench]
diff --git a/library/alloc/tests/collections/binary_heap.rs b/library/alloc/tests/collections/binary_heap.rs
index 55405ffe8c4..95f4c3e614f 100644
--- a/library/alloc/tests/collections/binary_heap.rs
+++ b/library/alloc/tests/collections/binary_heap.rs
@@ -502,9 +502,7 @@ fn test_retain_catch_unwind() {
 // even if the order might not be correct.
 //
 // Destructors must be called exactly once per element.
-// FIXME: re-enable emscripten once it can unwind again
 #[test]
-#[cfg(not(target_os = "emscripten"))]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn panic_safe() {
     use std::cmp;
diff --git a/library/alloc/tests/lib.rs b/library/alloc/tests/lib.rs
index d8364d750fa..391ff04a4b8 100644
--- a/library/alloc/tests/lib.rs
+++ b/library/alloc/tests/lib.rs
@@ -94,9 +94,6 @@ fn test_rng() -> rand_xorshift::XorShiftRng {
     rand::SeedableRng::from_seed(seed)
 }
 
-// FIXME: Instantiated functions with i128 in the signature is not supported in Emscripten.
-// See https://github.com/kripken/emscripten-fastcomp/issues/169
-#[cfg(not(target_os = "emscripten"))]
 #[test]
 fn test_boxed_hasher() {
     let ordinary_hash = hash(&5u32);
diff --git a/library/alloc/tests/slice.rs b/library/alloc/tests/slice.rs
index 9625e3d2b5e..f990a41b679 100644
--- a/library/alloc/tests/slice.rs
+++ b/library/alloc/tests/slice.rs
@@ -1414,7 +1414,6 @@ fn test_box_slice_clone() {
 
 #[test]
 #[allow(unused_must_use)] // here, we care about the side effects of `.clone()`
-#[cfg_attr(target_os = "emscripten", ignore)]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_box_slice_clone_panics() {
     use std::sync::Arc;
diff --git a/library/alloc/tests/sort/tests.rs b/library/alloc/tests/sort/tests.rs
index 4cc79010e8f..d321f8df518 100644
--- a/library/alloc/tests/sort/tests.rs
+++ b/library/alloc/tests/sort/tests.rs
@@ -11,7 +11,14 @@ use crate::sort::{Sort, known_good_stable_sort, patterns};
 #[cfg(miri)]
 const TEST_LENGTHS: &[usize] = &[2, 3, 4, 7, 10, 15, 20, 24, 33, 50, 100, 171, 300];
 
-#[cfg(not(miri))]
+// node.js gives out of memory error to use with length 1_100_000
+#[cfg(all(not(miri), target_os = "emscripten"))]
+const TEST_LENGTHS: &[usize] = &[
+    2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 20, 24, 30, 32, 33, 35, 50, 100, 200, 500, 1_000,
+    2_048, 5_000, 10_000, 100_000,
+];
+
+#[cfg(all(not(miri), not(target_os = "emscripten")))]
 const TEST_LENGTHS: &[usize] = &[
     2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16, 17, 20, 24, 30, 32, 33, 35, 50, 100, 200, 500, 1_000,
     2_048, 5_000, 10_000, 100_000, 1_100_000,
diff --git a/library/alloc/tests/sync.rs b/library/alloc/tests/sync.rs
index 7a9a4abfdc6..6d3ab1b1d11 100644
--- a/library/alloc/tests/sync.rs
+++ b/library/alloc/tests/sync.rs
@@ -128,6 +128,7 @@ fn try_unwrap() {
 }
 
 #[test]
+#[cfg_attr(any(target_os = "emscripten", target_os = "wasi"), ignore)] // no threads
 fn into_inner() {
     for _ in 0..100
     // ^ Increase chances of hitting potential race conditions
diff --git a/library/alloc/tests/vec.rs b/library/alloc/tests/vec.rs
index b24daec2968..fe1db56414e 100644
--- a/library/alloc/tests/vec.rs
+++ b/library/alloc/tests/vec.rs
@@ -1587,9 +1587,7 @@ fn extract_if_complex() {
     }
 }
 
-// FIXME: re-enable emscripten once it can unwind again
 #[test]
-#[cfg(not(target_os = "emscripten"))]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn extract_if_consumed_panic() {
     use std::rc::Rc;
@@ -1640,9 +1638,7 @@ fn extract_if_consumed_panic() {
     }
 }
 
-// FIXME: Re-enable emscripten once it can catch panics
 #[test]
-#[cfg(not(target_os = "emscripten"))]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn extract_if_unconsumed_panic() {
     use std::rc::Rc;
diff --git a/library/core/tests/hash/mod.rs b/library/core/tests/hash/mod.rs
index 9f14995f73f..1f10a4733b0 100644
--- a/library/core/tests/hash/mod.rs
+++ b/library/core/tests/hash/mod.rs
@@ -141,9 +141,6 @@ fn test_custom_state() {
     // const { assert!(hash(&Custom { hash: 6 }) == 6) };
 }
 
-// FIXME: Instantiated functions with i128 in the signature is not supported in Emscripten.
-// See https://github.com/kripken/emscripten-fastcomp/issues/169
-#[cfg(not(target_os = "emscripten"))]
 #[test]
 fn test_indirect_hasher() {
     let mut hasher = MyHasher { hash: 0 };
diff --git a/library/core/tests/num/flt2dec/random.rs b/library/core/tests/num/flt2dec/random.rs
index 99fc23af7ea..90042ae03bf 100644
--- a/library/core/tests/num/flt2dec/random.rs
+++ b/library/core/tests/num/flt2dec/random.rs
@@ -84,9 +84,6 @@ where
     F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
     G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
 {
-    if cfg!(target_os = "emscripten") {
-        return; // using rng pulls in i128 support, which doesn't work
-    }
     let mut rng = crate::test_rng();
     let f32_range = Uniform::new(0x0000_0001u32, 0x7f80_0000);
     iterate("f32_random_equivalence_test", k, n, f, g, |_| {
@@ -100,9 +97,6 @@ where
     F: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> Option<(&'a [u8], i16)>,
     G: for<'a> FnMut(&Decoded, &'a mut [MaybeUninit<u8>]) -> (&'a [u8], i16),
 {
-    if cfg!(target_os = "emscripten") {
-        return; // using rng pulls in i128 support, which doesn't work
-    }
     let mut rng = crate::test_rng();
     let f64_range = Uniform::new(0x0000_0000_0000_0001u64, 0x7ff0_0000_0000_0000);
     iterate("f64_random_equivalence_test", k, n, f, g, |_| {
diff --git a/library/core/tests/num/ops.rs b/library/core/tests/num/ops.rs
index ae8b938250e..7b2aad48978 100644
--- a/library/core/tests/num/ops.rs
+++ b/library/core/tests/num/ops.rs
@@ -51,9 +51,7 @@ macro_rules! test_op {
     };
 }
 
-test_op!(test_neg_defined, Neg::neg(0), 0, i8, i16, i32, i64, f32, f64);
-#[cfg(not(target_os = "emscripten"))]
-test_op!(test_neg_defined_128, Neg::neg(0), 0, i128);
+test_op!(test_neg_defined, Neg::neg(0), 0, i8, i16, i32, i64, i128, f32, f64);
 
 test_op!(test_not_defined_bool, Not::not(true), false, bool);
 
@@ -69,17 +67,17 @@ macro_rules! test_arith_op {
                 i16,
                 i32,
                 i64,
+                i128,
                 isize,
                 u8,
                 u16,
                 u32,
                 u64,
+                u128,
                 usize,
                 f32,
                 f64
             );
-            #[cfg(not(target_os = "emscripten"))]
-            impls_defined!($op, $method($lhs, $rhs), 0, i128, u128);
         }
     };
     ($fn_name:ident, $op:ident::$method:ident(&mut $lhs:literal, $rhs:literal)) => {
@@ -93,17 +91,17 @@ macro_rules! test_arith_op {
                 i16,
                 i32,
                 i64,
+                i128,
                 isize,
                 u8,
                 u16,
                 u32,
                 u64,
+                u128,
                 usize,
                 f32,
                 f64
             );
-            #[cfg(not(target_os = "emscripten"))]
-            impls_defined!($op, $method(&mut $lhs, $rhs), 0, i128, u128);
         }
     };
 }
@@ -131,15 +129,15 @@ macro_rules! test_bitop {
                 i16,
                 i32,
                 i64,
+                i128,
                 isize,
                 u8,
                 u16,
                 u32,
                 u64,
+                u128,
                 usize
             );
-            #[cfg(not(target_os = "emscripten"))]
-            impls_defined!($op, $method(0, 0), 0, i128, u128);
             impls_defined!($op, $method(false, false), false, bool);
         }
     };
@@ -156,15 +154,15 @@ macro_rules! test_bitop_assign {
                 i16,
                 i32,
                 i64,
+                i128,
                 isize,
                 u8,
                 u16,
                 u32,
                 u64,
+                u128,
                 usize
             );
-            #[cfg(not(target_os = "emscripten"))]
-            impls_defined!($op, $method(&mut 0, 0), 0, i128, u128);
             impls_defined!($op, $method(&mut false, false), false, bool);
         }
     };
@@ -182,9 +180,11 @@ macro_rules! test_shift_inner {
         $(impl_defined!($op, $method(0,0), 0, $lt, $rt);)+
     };
     ($op:ident::$method:ident, $lt:ty) => {
-        test_shift_inner!($op::$method, $lt, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
-        #[cfg(not(target_os = "emscripten"))]
-        test_shift_inner!($op::$method, $lt, i128, u128);
+        test_shift_inner!(
+            $op::$method, $lt,
+            i8, i16, i32, i64, i128, isize,
+            u8, u16, u32, u64, u128, usize
+        );
     };
 }
 
@@ -195,9 +195,11 @@ macro_rules! test_shift {
     ($test_name:ident, $op:ident::$method:ident) => {
         #[test]
         fn $test_name() {
-            test_shift!($op::$method, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
-            #[cfg(not(target_os = "emscripten"))]
-            test_shift!($op::$method, i128, u128);
+            test_shift!(
+                $op::$method,
+                i8, i16, i32, i64, i128, isize,
+                u8, u16, u32, u64, u128, usize
+            );
         }
     };
 }
@@ -207,9 +209,11 @@ macro_rules! test_shift_assign_inner {
         $(impl_defined!($op, $method(&mut 0,0), 0, $lt, $rt);)+
     };
     ($op:ident::$method:ident, $lt:ty) => {
-        test_shift_assign_inner!($op::$method, $lt, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
-        #[cfg(not(target_os = "emscripten"))]
-        test_shift_assign_inner!($op::$method, $lt, i128, u128);
+        test_shift_assign_inner!(
+            $op::$method, $lt,
+            i8, i16, i32, i64, i128, isize,
+            u8, u16, u32, u64, u128, usize
+        );
     };
 }
 
@@ -220,9 +224,11 @@ macro_rules! test_shift_assign {
     ($test_name:ident, $op:ident::$method:ident) => {
         #[test]
         fn $test_name() {
-            test_shift_assign!($op::$method, i8, i16, i32, i64, isize, u8, u16, u32, u64, usize);
-            #[cfg(not(target_os = "emscripten"))]
-            test_shift_assign!($op::$method, i128, u128);
+            test_shift_assign!(
+                $op::$method,
+                i8, i16, i32, i64, i128, isize,
+                u8, u16, u32, u64, u128, usize
+            );
         }
     };
 }
diff --git a/library/core/tests/num/wrapping.rs b/library/core/tests/num/wrapping.rs
index c5a71988395..0b9fca8455b 100644
--- a/library/core/tests/num/wrapping.rs
+++ b/library/core/tests/num/wrapping.rs
@@ -64,14 +64,12 @@ wrapping_test!(test_wrapping_i8, i8, i8::MIN, i8::MAX);
 wrapping_test!(test_wrapping_i16, i16, i16::MIN, i16::MAX);
 wrapping_test!(test_wrapping_i32, i32, i32::MIN, i32::MAX);
 wrapping_test!(test_wrapping_i64, i64, i64::MIN, i64::MAX);
-#[cfg(not(target_os = "emscripten"))]
 wrapping_test!(test_wrapping_i128, i128, i128::MIN, i128::MAX);
 wrapping_test!(test_wrapping_isize, isize, isize::MIN, isize::MAX);
 wrapping_test!(test_wrapping_u8, u8, u8::MIN, u8::MAX);
 wrapping_test!(test_wrapping_u16, u16, u16::MIN, u16::MAX);
 wrapping_test!(test_wrapping_u32, u32, u32::MIN, u32::MAX);
 wrapping_test!(test_wrapping_u64, u64, u64::MIN, u64::MAX);
-#[cfg(not(target_os = "emscripten"))]
 wrapping_test!(test_wrapping_u128, u128, u128::MIN, u128::MAX);
 wrapping_test!(test_wrapping_usize, usize, usize::MIN, usize::MAX);
 
diff --git a/library/std/src/f64/tests.rs b/library/std/src/f64/tests.rs
index 3fac2efe0d7..f5ba2c7b594 100644
--- a/library/std/src/f64/tests.rs
+++ b/library/std/src/f64/tests.rs
@@ -112,7 +112,6 @@ fn test_neg_zero() {
     assert_eq!(Fp::Zero, neg_zero.classify());
 }
 
-#[cfg_attr(all(target_arch = "wasm32", target_os = "emscripten"), ignore)] // issue 42630
 #[test]
 fn test_one() {
     let one: f64 = 1.0f64;
@@ -165,7 +164,6 @@ fn test_is_finite() {
     assert!((-109.2f64).is_finite());
 }
 
-#[cfg_attr(all(target_arch = "wasm32", target_os = "emscripten"), ignore)] // issue 42630
 #[test]
 fn test_is_normal() {
     let nan: f64 = f64::NAN;
@@ -183,7 +181,6 @@ fn test_is_normal() {
     assert!(!1e-308f64.is_normal());
 }
 
-#[cfg_attr(all(target_arch = "wasm32", target_os = "emscripten"), ignore)] // issue 42630
 #[test]
 fn test_classify() {
     let nan: f64 = f64::NAN;
diff --git a/library/std/src/io/copy/tests.rs b/library/std/src/io/copy/tests.rs
index 2e0eb6cdce6..25b1ece2745 100644
--- a/library/std/src/io/copy/tests.rs
+++ b/library/std/src/io/copy/tests.rs
@@ -126,6 +126,7 @@ mod io_benches {
     use crate::io::prelude::*;
 
     #[bench]
+    #[cfg_attr(target_os = "emscripten", ignore)] // no /dev
     fn bench_copy_buf_reader(b: &mut Bencher) {
         let mut file_in = File::open("/dev/zero").expect("opening /dev/zero failed");
         // use dyn to avoid specializations unrelated to readbuf
diff --git a/library/std/src/io/tests.rs b/library/std/src/io/tests.rs
index 85098b3bb18..226cc6011bc 100644
--- a/library/std/src/io/tests.rs
+++ b/library/std/src/io/tests.rs
@@ -7,7 +7,6 @@ use crate::mem::MaybeUninit;
 use crate::ops::Deref;
 
 #[test]
-#[cfg_attr(target_os = "emscripten", ignore)]
 fn read_until() {
     let mut buf = Cursor::new(&b"12"[..]);
     let mut v = Vec::new();
@@ -359,7 +358,6 @@ fn chain_zero_length_read_is_not_eof() {
 }
 
 #[bench]
-#[cfg_attr(target_os = "emscripten", ignore)]
 #[cfg_attr(miri, ignore)] // Miri isn't fast...
 fn bench_read_to_end(b: &mut test::Bencher) {
     b.iter(|| {
diff --git a/library/std/src/sys/alloc/wasm.rs b/library/std/src/sys/alloc/wasm.rs
index a308fafc68b..53fbc9529e5 100644
--- a/library/std/src/sys/alloc/wasm.rs
+++ b/library/std/src/sys/alloc/wasm.rs
@@ -1,6 +1,6 @@
 //! This is an implementation of a global allocator on wasm targets when
-//! emscripten is not in use. In that situation there's no actual runtime for us
-//! to lean on for allocation, so instead we provide our own!
+//! emscripten or wasi is not in use. In that situation there's no actual runtime
+//! for us to lean on for allocation, so instead we provide our own!
 //!
 //! The wasm instruction set has two instructions for getting the current
 //! amount of memory and growing the amount of memory. These instructions are the
diff --git a/library/std/src/sys/pal/wasi/mod.rs b/library/std/src/sys/pal/wasi/mod.rs
index 5d54c790306..361802d101d 100644
--- a/library/std/src/sys/pal/wasi/mod.rs
+++ b/library/std/src/sys/pal/wasi/mod.rs
@@ -1,8 +1,7 @@
 //! System bindings for the wasm/web platform
 //!
 //! This module contains the facade (aka platform-specific) implementations of
-//! OS level functionality for wasm. Note that this wasm is *not* the emscripten
-//! wasm, so we have no runtime here.
+//! OS level functionality for wasm.
 //!
 //! This is all super highly experimental and not actually intended for
 //! wide/production use yet, it's still all in the experimental category. This
diff --git a/library/std/src/sys/pal/wasm/mod.rs b/library/std/src/sys/pal/wasm/mod.rs
index 8141bfac49a..41fe019f110 100644
--- a/library/std/src/sys/pal/wasm/mod.rs
+++ b/library/std/src/sys/pal/wasm/mod.rs
@@ -2,7 +2,7 @@
 //!
 //! This module contains the facade (aka platform-specific) implementations of
 //! OS level functionality for wasm. Note that this wasm is *not* the emscripten
-//! wasm, so we have no runtime here.
+//! or wasi wasm, so we have no runtime here.
 //!
 //! This is all super highly experimental and not actually intended for
 //! wide/production use yet, it's still all in the experimental category. This
diff --git a/library/std/tests/pipe_subprocess.rs b/library/std/tests/pipe_subprocess.rs
index df946cdcf2b..00d99a578d5 100644
--- a/library/std/tests/pipe_subprocess.rs
+++ b/library/std/tests/pipe_subprocess.rs
@@ -1,7 +1,7 @@
 #![feature(anonymous_pipe)]
 
 fn main() {
-    #[cfg(all(not(miri), any(unix, windows)))]
+    #[cfg(all(not(miri), any(unix, windows), not(target_os = "emscripten")))]
     {
         use std::io::{Read, pipe};
         use std::{env, process};
diff --git a/library/std/tests/process_spawning.rs b/library/std/tests/process_spawning.rs
index 3e72e371ade..43b45cb2d2b 100644
--- a/library/std/tests/process_spawning.rs
+++ b/library/std/tests/process_spawning.rs
@@ -5,7 +5,8 @@ use std::{env, fs, process, str};
 mod common;
 
 #[test]
-#[cfg_attr(any(miri, target_os = "wasi"), ignore)] // Process spawning not supported by Miri and wasi
+// Process spawning not supported by Miri, Emscripten and wasi
+#[cfg_attr(any(miri, target_os = "emscripten", target_os = "wasi"), ignore)]
 fn issue_15149() {
     // If we're the parent, copy our own binary to a new directory.
     let my_path = env::current_exe().unwrap();
diff --git a/library/test/src/cli.rs b/library/test/src/cli.rs
index 52c80917623..ef6786f4316 100644
--- a/library/test/src/cli.rs
+++ b/library/test/src/cli.rs
@@ -1,7 +1,7 @@
 //! Module converting command-line arguments into test configuration.
 
 use std::env;
-use std::io::{self, IsTerminal};
+use std::io::{self, IsTerminal, Write};
 use std::path::PathBuf;
 
 use super::options::{ColorConfig, Options, OutputFormat, RunIgnored};
@@ -58,7 +58,7 @@ fn optgroups() -> getopts::Options {
         .optflag("", "bench", "Run benchmarks instead of tests")
         .optflag("", "list", "List all tests and benchmarks")
         .optflag("h", "help", "Display this message")
-        .optopt("", "logfile", "Write logs to the specified file", "PATH")
+        .optopt("", "logfile", "Write logs to the specified file (deprecated)", "PATH")
         .optflag(
             "",
             "nocapture",
@@ -281,6 +281,10 @@ fn parse_opts_impl(matches: getopts::Matches) -> OptRes {
 
     let options = Options::new().display_output(matches.opt_present("show-output"));
 
+    if logfile.is_some() {
+        let _ = write!(io::stderr(), "warning: `--logfile` is deprecated");
+    }
+
     let test_opts = TestOpts {
         list,
         filters,
diff --git a/library/test/src/tests.rs b/library/test/src/tests.rs
index abeb3642169..47f581fefae 100644
--- a/library/test/src/tests.rs
+++ b/library/test/src/tests.rs
@@ -133,9 +133,7 @@ fn ignored_tests_result_in_ignored() {
     assert_eq!(result, TrIgnored);
 }
 
-// FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251)
 #[test]
-#[cfg(not(target_os = "emscripten"))]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_should_panic() {
     fn f() -> Result<(), String> {
@@ -164,9 +162,7 @@ fn test_should_panic() {
     assert_eq!(result, TrOk);
 }
 
-// FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251)
 #[test]
-#[cfg(not(target_os = "emscripten"))]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_should_panic_good_message() {
     fn f() -> Result<(), String> {
@@ -195,9 +191,7 @@ fn test_should_panic_good_message() {
     assert_eq!(result, TrOk);
 }
 
-// FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251)
 #[test]
-#[cfg(not(target_os = "emscripten"))]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_should_panic_bad_message() {
     use crate::tests::TrFailedMsg;
@@ -231,9 +225,7 @@ fn test_should_panic_bad_message() {
     assert_eq!(result, TrFailedMsg(failed_msg.to_string()));
 }
 
-// FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251)
 #[test]
-#[cfg(not(target_os = "emscripten"))]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_should_panic_non_string_message_type() {
     use std::any::TypeId;
@@ -272,9 +264,7 @@ fn test_should_panic_non_string_message_type() {
     assert_eq!(result, TrFailedMsg(failed_msg));
 }
 
-// FIXME: Re-enable emscripten once it can catch panics again (introduced by #65251)
 #[test]
-#[cfg(not(target_os = "emscripten"))]
 #[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
 fn test_should_panic_but_succeeds() {
     let should_panic_variants = [ShouldPanic::Yes, ShouldPanic::YesWithMessage("error message")];
diff --git a/src/doc/rustc/src/platform-support/wasm32-unknown-emscripten.md b/src/doc/rustc/src/platform-support/wasm32-unknown-emscripten.md
index 7a9cd4b522b..d364852b1c1 100644
--- a/src/doc/rustc/src/platform-support/wasm32-unknown-emscripten.md
+++ b/src/doc/rustc/src/platform-support/wasm32-unknown-emscripten.md
@@ -118,7 +118,7 @@ This target is not extensively tested in CI for the rust-lang/rust repository. I
 can be tested locally, for example, with:
 
 ```sh
-./x.py test --target wasm32-unknown-emscripten --skip src/tools/linkchecker
+EMCC_CFLAGS="-s MAXIMUM_MEMORY=2GB" ./x.py test --target wasm32-unknown-emscripten --skip src/tools/linkchecker
 ```
 
 To run these tests, both `emcc` and `node` need to be in your `$PATH`. You can
diff --git a/src/doc/rustc/src/tests/index.md b/src/doc/rustc/src/tests/index.md
index 32baed9c944..d2026513d2f 100644
--- a/src/doc/rustc/src/tests/index.md
+++ b/src/doc/rustc/src/tests/index.md
@@ -268,6 +268,8 @@ Controls the format of the output. Valid options:
 
 Writes the results of the tests to the given file.
 
+This option is deprecated.
+
 #### `--report-time`
 
 ⚠️ 🚧 This option is [unstable](#unstable-options), and requires the `-Z
diff --git a/tests/codegen/s390x-simd.rs b/tests/codegen/s390x-simd.rs
new file mode 100644
index 00000000000..23181e6a103
--- /dev/null
+++ b/tests/codegen/s390x-simd.rs
@@ -0,0 +1,143 @@
+//! test that s390x vector types are passed using `PassMode::Direct`
+//! see also https://github.com/rust-lang/rust/issues/135744
+//@ add-core-stubs
+//@ compile-flags: --target s390x-unknown-linux-gnu -O
+//@ needs-llvm-components: systemz
+
+#![crate_type = "rlib"]
+#![feature(no_core, asm_experimental_arch)]
+#![feature(s390x_target_feature, simd_ffi, link_llvm_intrinsics, repr_simd)]
+#![no_core]
+
+extern crate minicore;
+use minicore::*;
+
+#[repr(simd)]
+struct i8x16([i8; 16]);
+
+#[repr(simd)]
+struct i16x8([i16; 8]);
+
+#[repr(simd)]
+struct i32x4([i32; 4]);
+
+#[repr(simd)]
+struct i64x2([i64; 2]);
+
+#[repr(simd)]
+struct f32x4([f32; 4]);
+
+#[repr(simd)]
+struct f64x2([f64; 2]);
+
+#[allow(improper_ctypes)]
+extern "C" {
+    #[link_name = "llvm.smax.v16i8"]
+    fn vmxb(a: i8x16, b: i8x16) -> i8x16;
+    #[link_name = "llvm.smax.v8i16"]
+    fn vmxh(a: i16x8, b: i16x8) -> i16x8;
+    #[link_name = "llvm.smax.v4i32"]
+    fn vmxf(a: i32x4, b: i32x4) -> i32x4;
+    #[link_name = "llvm.smax.v2i64"]
+    fn vmxg(a: i64x2, b: i64x2) -> i64x2;
+}
+
+// CHECK-LABEL: define <16 x i8> @max_i8x16
+// CHECK-SAME: <16 x i8> %a, <16 x i8> %b
+// CHECK: call <16 x i8> @llvm.smax.v16i8(<16 x i8> %a, <16 x i8> %b)
+#[no_mangle]
+#[target_feature(enable = "vector")]
+pub unsafe extern "C" fn max_i8x16(a: i8x16, b: i8x16) -> i8x16 {
+    vmxb(a, b)
+}
+
+// CHECK-LABEL: define <8 x i16> @max_i16x8
+// CHECK-SAME: <8 x i16> %a, <8 x i16> %b
+// CHECK: call <8 x i16> @llvm.smax.v8i16(<8 x i16> %a, <8 x i16> %b)
+#[no_mangle]
+#[target_feature(enable = "vector")]
+pub unsafe extern "C" fn max_i16x8(a: i16x8, b: i16x8) -> i16x8 {
+    vmxh(a, b)
+}
+
+// CHECK-LABEL: define <4 x i32> @max_i32x4
+// CHECK-SAME: <4 x i32> %a, <4 x i32> %b
+// CHECK: call <4 x i32> @llvm.smax.v4i32(<4 x i32> %a, <4 x i32> %b)
+#[no_mangle]
+#[target_feature(enable = "vector")]
+pub unsafe extern "C" fn max_i32x4(a: i32x4, b: i32x4) -> i32x4 {
+    vmxf(a, b)
+}
+
+// CHECK-LABEL: define <2 x i64> @max_i64x2
+// CHECK-SAME: <2 x i64> %a, <2 x i64> %b
+// CHECK: call <2 x i64> @llvm.smax.v2i64(<2 x i64> %a, <2 x i64> %b)
+#[no_mangle]
+#[target_feature(enable = "vector")]
+pub unsafe extern "C" fn max_i64x2(a: i64x2, b: i64x2) -> i64x2 {
+    vmxg(a, b)
+}
+
+// CHECK-LABEL: define <4 x float> @choose_f32x4
+// CHECK-SAME: <4 x float> %a, <4 x float> %b
+#[no_mangle]
+#[target_feature(enable = "vector")]
+pub unsafe extern "C" fn choose_f32x4(a: f32x4, b: f32x4, c: bool) -> f32x4 {
+    if c { a } else { b }
+}
+
+// CHECK-LABEL: define <2 x double> @choose_f64x2
+// CHECK-SAME: <2 x double> %a, <2 x double> %b
+#[no_mangle]
+#[target_feature(enable = "vector")]
+pub unsafe extern "C" fn choose_f64x2(a: f64x2, b: f64x2, c: bool) -> f64x2 {
+    if c { a } else { b }
+}
+
+#[repr(C)]
+struct Wrapper<T>(T);
+
+#[no_mangle]
+#[inline(never)]
+#[target_feature(enable = "vector")]
+pub unsafe extern "C" fn max_wrapper_i8x16(a: Wrapper<i8x16>, b: Wrapper<i8x16>) -> Wrapper<i8x16> {
+    // CHECK-LABEL: max_wrapper_i8x16
+    // CHECK-SAME: sret([16 x i8])
+    // CHECK-SAME: <16 x i8>
+    // CHECK-SAME: <16 x i8>
+    // CHECK: call <16 x i8> @llvm.smax.v16i8
+    // CHECK-SAME: <16 x i8>
+    // CHECK-SAME: <16 x i8>
+    Wrapper(vmxb(a.0, b.0))
+}
+
+#[no_mangle]
+#[inline(never)]
+#[target_feature(enable = "vector")]
+pub unsafe extern "C" fn max_wrapper_i64x2(a: Wrapper<i64x2>, b: Wrapper<i64x2>) -> Wrapper<i64x2> {
+    // CHECK-LABEL: max_wrapper_i64x2
+    // CHECK-SAME: sret([16 x i8])
+    // CHECK-SAME: <16 x i8>
+    // CHECK-SAME: <16 x i8>
+    // CHECK: call <2 x i64> @llvm.smax.v2i64
+    // CHECK-SAME: <2 x i64>
+    // CHECK-SAME: <2 x i64>
+    Wrapper(vmxg(a.0, b.0))
+}
+
+#[no_mangle]
+#[inline(never)]
+#[target_feature(enable = "vector")]
+pub unsafe extern "C" fn choose_wrapper_f64x2(
+    a: Wrapper<f64x2>,
+    b: Wrapper<f64x2>,
+    c: bool,
+) -> Wrapper<f64x2> {
+    // CHECK-LABEL: choose_wrapper_f64x2
+    // CHECK-SAME: sret([16 x i8])
+    // CHECK-SAME: <16 x i8>
+    // CHECK-SAME: <16 x i8>
+    Wrapper(choose_f64x2(a.0, b.0, c))
+}
+
+// CHECK: declare <2 x i64> @llvm.smax.v2i64(<2 x i64>, <2 x i64>)
diff --git a/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.fixed b/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.fixed
index 914ca1f3a06..b58c3a6720d 100644
--- a/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.fixed
+++ b/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.fixed
@@ -1,17 +1,26 @@
 //@ run-rustfix
 #![deny(unused_assignments, unused_variables)]
+#![allow(unused_mut)]
 struct Object;
 
 fn change_object(object: &mut Object) { //~ HELP you might have meant to mutate
-    let object2 = Object;
-    *object = object2; //~ ERROR mismatched types
+   let object2 = Object;
+   *object = object2; //~ ERROR mismatched types
 }
 
 fn change_object2(object: &mut Object) { //~ ERROR variable `object` is assigned to, but never used
+   //~^ HELP you might have meant to mutate
+   let object2 = Object;
+   *object = object2;
+   //~^ ERROR `object2` does not live long enough
+   //~| ERROR value assigned to `object` is never read
+}
+
+fn change_object3(object: &mut Object) { //~ ERROR variable `object` is assigned to, but never used
     //~^ HELP you might have meant to mutate
-    let object2 = Object;
+    let mut object2 = Object; //~ HELP consider changing this to be mutable
     *object = object2;
-    //~^ ERROR `object2` does not live long enough
+    //~^ ERROR cannot borrow `object2` as mutable
     //~| ERROR value assigned to `object` is never read
 }
 
@@ -19,4 +28,5 @@ fn main() {
     let mut object = Object;
     change_object(&mut object);
     change_object2(&mut object);
+    change_object3(&mut object);
 }
diff --git a/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs b/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs
index 331359a98d1..1fd222e0db1 100644
--- a/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs
+++ b/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs
@@ -1,17 +1,26 @@
 //@ run-rustfix
 #![deny(unused_assignments, unused_variables)]
+#![allow(unused_mut)]
 struct Object;
 
 fn change_object(mut object: &Object) { //~ HELP you might have meant to mutate
-    let object2 = Object;
-    object = object2; //~ ERROR mismatched types
+   let object2 = Object;
+   object = object2; //~ ERROR mismatched types
 }
 
 fn change_object2(mut object: &Object) { //~ ERROR variable `object` is assigned to, but never used
+   //~^ HELP you might have meant to mutate
+   let object2 = Object;
+   object = &object2;
+   //~^ ERROR `object2` does not live long enough
+   //~| ERROR value assigned to `object` is never read
+}
+
+fn change_object3(mut object: &mut Object) { //~ ERROR variable `object` is assigned to, but never used
     //~^ HELP you might have meant to mutate
-    let object2 = Object;
-    object = &object2;
-    //~^ ERROR `object2` does not live long enough
+    let object2 = Object; //~ HELP consider changing this to be mutable
+    object = &mut object2;
+    //~^ ERROR cannot borrow `object2` as mutable
     //~| ERROR value assigned to `object` is never read
 }
 
@@ -19,4 +28,5 @@ fn main() {
     let mut object = Object;
     change_object(&mut object);
     change_object2(&mut object);
+    change_object3(&mut object);
 }
diff --git a/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr b/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr
index e7e4003936a..0330853d922 100644
--- a/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr
+++ b/tests/ui/fn/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.stderr
@@ -1,24 +1,24 @@
 error[E0308]: mismatched types
-  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:7:14
+  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:8:13
    |
 LL | fn change_object(mut object: &Object) {
    |                              ------- expected due to this parameter type
-LL |     let object2 = Object;
-LL |     object = object2;
-   |              ^^^^^^^ expected `&Object`, found `Object`
+LL |    let object2 = Object;
+LL |    object = object2;
+   |             ^^^^^^^ expected `&Object`, found `Object`
    |
 help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding
    |
 LL ~ fn change_object(object: &mut Object) {
-LL |     let object2 = Object;
-LL ~     *object = object2;
+LL |    let object2 = Object;
+LL ~    *object = object2;
    |
 
 error: value assigned to `object` is never read
-  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:13:5
+  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:14:4
    |
-LL |     object = &object2;
-   |     ^^^^^^
+LL |    object = &object2;
+   |    ^^^^^^
    |
 note: the lint level is defined here
   --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:2:9
@@ -29,12 +29,12 @@ help: you might have meant to mutate the pointed at value being passed in, inste
    |
 LL ~ fn change_object2(object: &mut Object) {
 LL |
-LL |     let object2 = Object;
-LL ~     *object = object2;
+LL |    let object2 = Object;
+LL ~    *object = object2;
    |
 
 error: variable `object` is assigned to, but never used
-  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:10:23
+  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:11:23
    |
 LL | fn change_object2(mut object: &Object) {
    |                       ^^^^^^
@@ -47,23 +47,56 @@ LL | #![deny(unused_assignments, unused_variables)]
    |                             ^^^^^^^^^^^^^^^^
 
 error[E0597]: `object2` does not live long enough
-  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:13:14
+  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:14:13
    |
 LL | fn change_object2(mut object: &Object) {
    |                               - let's call the lifetime of this reference `'1`
 LL |
-LL |     let object2 = Object;
-   |         ------- binding `object2` declared here
-LL |     object = &object2;
-   |     ---------^^^^^^^^
-   |     |        |
-   |     |        borrowed value does not live long enough
-   |     assignment requires that `object2` is borrowed for `'1`
+LL |    let object2 = Object;
+   |        ------- binding `object2` declared here
+LL |    object = &object2;
+   |    ---------^^^^^^^^
+   |    |        |
+   |    |        borrowed value does not live long enough
+   |    assignment requires that `object2` is borrowed for `'1`
 ...
 LL | }
    | - `object2` dropped here while still borrowed
 
-error: aborting due to 4 previous errors
+error: value assigned to `object` is never read
+  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:22:5
+   |
+LL |     object = &mut object2;
+   |     ^^^^^^
+   |
+help: you might have meant to mutate the pointed at value being passed in, instead of changing the reference in the local binding
+   |
+LL ~ fn change_object3(object: &mut Object) {
+LL |
+LL |     let object2 = Object;
+LL ~     *object = object2;
+   |
+
+error: variable `object` is assigned to, but never used
+  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:19:23
+   |
+LL | fn change_object3(mut object: &mut Object) {
+   |                       ^^^^^^
+   |
+   = note: consider using `_object` instead
+
+error[E0596]: cannot borrow `object2` as mutable, as it is not declared as mutable
+  --> $DIR/mut-arg-of-borrowed-type-meant-to-be-arg-of-mut-borrow.rs:22:14
+   |
+LL |     object = &mut object2;
+   |              ^^^^^^^^^^^^ cannot borrow as mutable
+   |
+help: consider changing this to be mutable
+   |
+LL |     let mut object2 = Object;
+   |         +++
+
+error: aborting due to 7 previous errors
 
-Some errors have detailed explanations: E0308, E0597.
+Some errors have detailed explanations: E0308, E0596, E0597.
 For more information about an error, try `rustc --explain E0308`.
diff --git a/tests/ui/wasm/wasm-bindgen-broken-error.rs b/tests/ui/wasm/wasm-bindgen-broken-error.rs
new file mode 100644
index 00000000000..d985e879803
--- /dev/null
+++ b/tests/ui/wasm/wasm-bindgen-broken-error.rs
@@ -0,0 +1,28 @@
+//@ only-wasm32
+//@ revisions: v0_1_0 v0_2_87 v0_2_88 v0_3_0 v1_0_0
+//@[v0_1_0] check-fail
+//@[v0_1_0] rustc-env:CARGO_PKG_VERSION_MAJOR=0
+//@[v0_1_0] rustc-env:CARGO_PKG_VERSION_MINOR=1
+//@[v0_1_0] rustc-env:CARGO_PKG_VERSION_PATCH=0
+//@[v0_2_87] check-fail
+//@[v0_2_87] rustc-env:CARGO_PKG_VERSION_MAJOR=0
+//@[v0_2_87] rustc-env:CARGO_PKG_VERSION_MINOR=2
+//@[v0_2_87] rustc-env:CARGO_PKG_VERSION_PATCH=87
+//@[v0_2_88] check-pass
+//@[v0_2_88] rustc-env:CARGO_PKG_VERSION_MAJOR=0
+//@[v0_2_88] rustc-env:CARGO_PKG_VERSION_MINOR=2
+//@[v0_2_88] rustc-env:CARGO_PKG_VERSION_PATCH=88
+//@[v0_3_0] check-pass
+//@[v0_3_0] rustc-env:CARGO_PKG_VERSION_MAJOR=0
+//@[v0_3_0] rustc-env:CARGO_PKG_VERSION_MINOR=3
+//@[v0_3_0] rustc-env:CARGO_PKG_VERSION_PATCH=0
+//@[v1_0_0] check-pass
+//@[v1_0_0] rustc-env:CARGO_PKG_VERSION_MAJOR=1
+//@[v1_0_0] rustc-env:CARGO_PKG_VERSION_MINOR=0
+//@[v1_0_0] rustc-env:CARGO_PKG_VERSION_PATCH=0
+
+#![crate_name = "wasm_bindgen"]
+//[v0_1_0]~^ ERROR: older versions of the `wasm-bindgen` crate
+//[v0_2_87]~^^ ERROR: older versions of the `wasm-bindgen` crate
+
+fn main() {}
diff --git a/tests/ui/wasm/wasm-bindgen-broken-error.v0_1_0.stderr b/tests/ui/wasm/wasm-bindgen-broken-error.v0_1_0.stderr
new file mode 100644
index 00000000000..e1c1ec7ef33
--- /dev/null
+++ b/tests/ui/wasm/wasm-bindgen-broken-error.v0_1_0.stderr
@@ -0,0 +1,8 @@
+error: older versions of the `wasm-bindgen` crate are incompatible with current versions of Rust; please update to `wasm-bindgen` v0.2.88
+  --> $DIR/wasm-bindgen-broken-error.rs:24:1
+   |
+LL | #![crate_name = "wasm_bindgen"]
+   | ^
+
+error: aborting due to 1 previous error
+
diff --git a/tests/ui/wasm/wasm-bindgen-broken-error.v0_2_87.stderr b/tests/ui/wasm/wasm-bindgen-broken-error.v0_2_87.stderr
new file mode 100644
index 00000000000..e1c1ec7ef33
--- /dev/null
+++ b/tests/ui/wasm/wasm-bindgen-broken-error.v0_2_87.stderr
@@ -0,0 +1,8 @@
+error: older versions of the `wasm-bindgen` crate are incompatible with current versions of Rust; please update to `wasm-bindgen` v0.2.88
+  --> $DIR/wasm-bindgen-broken-error.rs:24:1
+   |
+LL | #![crate_name = "wasm_bindgen"]
+   | ^
+
+error: aborting due to 1 previous error
+