From c0822ed9b8807ff350e24377e9d1999cc28edf0f Mon Sep 17 00:00:00 2001
From: jyn <github@jyn.dev>
Date: Sun, 24 Dec 2023 18:38:00 -0500
Subject: [PATCH 1/9] show linker warnings even if it returns 0

---
 compiler/rustc_codegen_ssa/messages.ftl       |  2 ++
 compiler/rustc_codegen_ssa/src/back/link.rs   | 27 +++++++++++++++++--
 compiler/rustc_codegen_ssa/src/base.rs        |  4 ++-
 compiler/rustc_codegen_ssa/src/lib.rs         |  1 +
 tests/run-make/linker-warning/fake-linker.sh  | 17 ++++++++++++
 tests/run-make/linker-warning/rmake.rs        | 19 +++++++++++++
 .../rust-lld-by-default-beta-stable/rmake.rs  |  6 +----
 .../rust-lld-by-default-nightly/rmake.rs      | 17 ++++--------
 .../run-make/rust-lld-custom-target/rmake.rs  |  7 ++---
 tests/run-make/rust-lld/rmake.rs              |  9 ++++---
 10 files changed, 82 insertions(+), 27 deletions(-)
 create mode 100755 tests/run-make/linker-warning/fake-linker.sh

diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl
index 484f467068a14..ff49630de4cb9 100644
--- a/compiler/rustc_codegen_ssa/messages.ftl
+++ b/compiler/rustc_codegen_ssa/messages.ftl
@@ -183,6 +183,8 @@ codegen_ssa_linker_file_stem = couldn't extract file stem from specified linker
 codegen_ssa_linker_not_found = linker `{$linker_path}` not found
     .note = {$error}
 
+codegen_ssa_linker_output = {$inner}
+
 codegen_ssa_linker_unsupported_modifier = `as-needed` modifier not supported for current linker
 
 codegen_ssa_linking_failed = linking with `{$linker_path}` failed: {$exit_status}
diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index df35b5e8426f1..378b6a50f7f77 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -18,6 +18,7 @@ use rustc_data_structures::temp_dir::MaybeTempDir;
 use rustc_errors::DiagCtxtHandle;
 use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize};
 use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
+use rustc_macros::Diagnostic;
 use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file};
 use rustc_metadata::{find_native_static_library, walk_native_lib_search_dirs};
 use rustc_middle::bug;
@@ -749,6 +750,14 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out
     }
 }
 
+#[derive(Diagnostic)]
+#[diag(codegen_ssa_linker_output)]
+/// Translating this is kind of useless. We don't pass translation flags to the linker, so we'd just
+/// end up with inconsistent languages within the same diagnostic.
+struct LinkerOutput {
+    inner: String,
+}
+
 /// Create a dynamic library or executable.
 ///
 /// This will invoke the system linker/cc to create the resulting file. This links to all upstream
@@ -1028,8 +1037,22 @@ fn link_natively(
 
                 sess.dcx().abort_if_errors();
             }
-            info!("linker stderr:\n{}", escape_string(&prog.stderr));
-            info!("linker stdout:\n{}", escape_string(&prog.stdout));
+
+            if !prog.stderr.is_empty() {
+                // We already print `warning:` at the start of the diagnostic. Remove it from the linker output if present.
+                let stderr = escape_string(&prog.stderr);
+                debug!("original stderr: {stderr}");
+                let stderr = stderr
+                    .strip_prefix("warning: ")
+                    .unwrap_or(&stderr)
+                    .replace(": warning: ", ": ");
+                sess.dcx().emit_warn(LinkerOutput { inner: format!("linker stderr: {stderr}") });
+            }
+            if !prog.stdout.is_empty() && sess.opts.verbose {
+                sess.dcx().emit_warn(LinkerOutput {
+                    inner: format!("linker stdout: {}", escape_string(&prog.stdout)),
+                });
+            }
         }
         Err(e) => {
             let linker_not_found = e.kind() == io::ErrorKind::NotFound;
diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs
index 544578b29f107..f14396c517c12 100644
--- a/compiler/rustc_codegen_ssa/src/base.rs
+++ b/compiler/rustc_codegen_ssa/src/base.rs
@@ -44,7 +44,8 @@ use crate::mir::operand::OperandValue;
 use crate::mir::place::PlaceRef;
 use crate::traits::*;
 use crate::{
-    CachedModuleCodegen, CompiledModule, CrateInfo, ModuleCodegen, ModuleKind, errors, meth, mir,
+    CachedModuleCodegen, CodegenLintLevels, CompiledModule, CrateInfo, ModuleCodegen, ModuleKind,
+    errors, meth, mir,
 };
 
 pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate {
@@ -927,6 +928,7 @@ impl CrateInfo {
             dependency_formats: Lrc::clone(tcx.dependency_formats(())),
             windows_subsystem,
             natvis_debugger_visualizers: Default::default(),
+            lint_levels: CodegenLintLevels::from_tcx(tcx),
         };
 
         info.native_libraries.reserve(n_crates);
diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs
index 65c6067c74066..332f2ffbc88ca 100644
--- a/compiler/rustc_codegen_ssa/src/lib.rs
+++ b/compiler/rustc_codegen_ssa/src/lib.rs
@@ -200,6 +200,7 @@ pub struct CrateInfo {
     pub dependency_formats: Lrc<Dependencies>,
     pub windows_subsystem: Option<String>,
     pub natvis_debugger_visualizers: BTreeSet<DebuggerVisualizerFile>,
+    pub lint_levels: CodegenLintLevels,
 }
 
 #[derive(Encodable, Decodable)]
diff --git a/tests/run-make/linker-warning/fake-linker.sh b/tests/run-make/linker-warning/fake-linker.sh
new file mode 100755
index 0000000000000..ed4d472c3bfbd
--- /dev/null
+++ b/tests/run-make/linker-warning/fake-linker.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+code=0
+while ! [ $# = 0 ]; do
+    case "$1" in
+        run_make_info) echo "foo"
+            ;;
+        run_make_warn) echo "warning: bar" >&2
+            ;;
+        run_make_error) echo "error: baz" >&2; code=1
+            ;;
+        *) ;;   # rustc passes lots of args we don't care about
+    esac
+    shift
+done
+
+exit $code
diff --git a/tests/run-make/linker-warning/rmake.rs b/tests/run-make/linker-warning/rmake.rs
index 4d21c5ea5690a..d2bb12aafcba9 100644
--- a/tests/run-make/linker-warning/rmake.rs
+++ b/tests/run-make/linker-warning/rmake.rs
@@ -10,6 +10,25 @@ fn main() {
     // first, compile our linker
     rustc().arg("fake-linker.rs").output("fake-linker").run();
 
+    // Run rustc with our fake linker, and make sure it shows warnings
+    let warnings = run_rustc().link_arg("run_make_warn").run();
+    warnings.assert_stderr_contains("warning: linker stderr: bar");
+
+    // Make sure it shows stdout, but only when --verbose is passed
+    run_rustc()
+        .link_arg("run_make_info")
+        .verbose()
+        .run()
+        .assert_stderr_contains("warning: linker stdout: foo");
+    run_rustc()
+        .link_arg("run_make_info")
+        .run()
+        .assert_stderr_not_contains("warning: linker stdout: foo");
+
+    // Make sure we short-circuit this new path if the linker exits with an error
+    // (so the diagnostic is less verbose)
+    run_rustc().link_arg("run_make_error").run_fail().assert_stderr_contains("note: error: baz");
+
     // Make sure we don't show the linker args unless `--verbose` is passed
     run_rustc()
         .link_arg("run_make_error")
diff --git a/tests/run-make/rust-lld-by-default-beta-stable/rmake.rs b/tests/run-make/rust-lld-by-default-beta-stable/rmake.rs
index 2417a4274e4a0..85860fc95b829 100644
--- a/tests/run-make/rust-lld-by-default-beta-stable/rmake.rs
+++ b/tests/run-make/rust-lld-by-default-beta-stable/rmake.rs
@@ -12,11 +12,7 @@ use run_make_support::rustc;
 fn main() {
     // A regular compilation should not use rust-lld by default. We'll check that by asking the
     // linker to display its version number with a link-arg.
-    let output = rustc()
-        .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
-        .link_arg("-Wl,-v")
-        .input("main.rs")
-        .run();
+    let output = rustc().verbose().link_arg("-Wl,-v").input("main.rs").run();
     assert!(
         !find_lld_version_in_logs(output.stderr_utf8()),
         "the LLD version string should not be present in the output logs:\n{}",
diff --git a/tests/run-make/rust-lld-by-default-nightly/rmake.rs b/tests/run-make/rust-lld-by-default-nightly/rmake.rs
index 02bbe8227f032..4026af8306450 100644
--- a/tests/run-make/rust-lld-by-default-nightly/rmake.rs
+++ b/tests/run-make/rust-lld-by-default-nightly/rmake.rs
@@ -12,11 +12,7 @@ use run_make_support::rustc;
 fn main() {
     // A regular compilation should use rust-lld by default. We'll check that by asking the linker
     // to display its version number with a link-arg.
-    let output = rustc()
-        .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
-        .link_arg("-Wl,-v")
-        .input("main.rs")
-        .run();
+    let output = rustc().verbose().link_arg("-Wl,-v").input("main.rs").run();
     assert!(
         find_lld_version_in_logs(output.stderr_utf8()),
         "the LLD version string should be present in the output logs:\n{}",
@@ -24,12 +20,8 @@ fn main() {
     );
 
     // But it can still be disabled by turning the linker feature off.
-    let output = rustc()
-        .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
-        .link_arg("-Wl,-v")
-        .arg("-Zlinker-features=-lld")
-        .input("main.rs")
-        .run();
+    let output =
+        rustc().verbose().link_arg("-Wl,-v").arg("-Zlinker-features=-lld").input("main.rs").run();
     assert!(
         !find_lld_version_in_logs(output.stderr_utf8()),
         "the LLD version string should not be present in the output logs:\n{}",
@@ -38,6 +30,7 @@ fn main() {
 }
 
 fn find_lld_version_in_logs(stderr: String) -> bool {
-    let lld_version_re = Regex::new(r"^LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
+    let lld_version_re =
+        Regex::new(r"^warning: linker stdout: LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
     stderr.lines().any(|line| lld_version_re.is_match(line.trim()))
 }
diff --git a/tests/run-make/rust-lld-custom-target/rmake.rs b/tests/run-make/rust-lld-custom-target/rmake.rs
index a6f7c33793afe..17468223306da 100644
--- a/tests/run-make/rust-lld-custom-target/rmake.rs
+++ b/tests/run-make/rust-lld-custom-target/rmake.rs
@@ -15,7 +15,7 @@ fn main() {
     // Compile to a custom target spec with rust-lld enabled by default. We'll check that by asking
     // the linker to display its version number with a link-arg.
     let output = rustc()
-        .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
+        .verbose()
         .crate_type("cdylib")
         .target("custom-target.json")
         .link_arg("-Wl,-v")
@@ -29,7 +29,7 @@ fn main() {
 
     // But it can also be disabled via linker features.
     let output = rustc()
-        .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
+        .verbose()
         .crate_type("cdylib")
         .target("custom-target.json")
         .arg("-Zlinker-features=-lld")
@@ -44,6 +44,7 @@ fn main() {
 }
 
 fn find_lld_version_in_logs(stderr: String) -> bool {
-    let lld_version_re = Regex::new(r"^LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
+    let lld_version_re =
+        Regex::new(r"^warning: linker stdout: LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
     stderr.lines().any(|line| lld_version_re.is_match(line.trim()))
 }
diff --git a/tests/run-make/rust-lld/rmake.rs b/tests/run-make/rust-lld/rmake.rs
index 1f311af1ed591..732bc6f6ba2f9 100644
--- a/tests/run-make/rust-lld/rmake.rs
+++ b/tests/run-make/rust-lld/rmake.rs
@@ -14,10 +14,10 @@ fn main() {
     // Opt-in to lld and the self-contained linker, to link with rust-lld. We'll check that by
     // asking the linker to display its version number with a link-arg.
     let output = rustc()
-        .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
         .arg("-Zlinker-features=+lld")
         .arg("-Clink-self-contained=+linker")
         .arg("-Zunstable-options")
+        .verbose()
         .link_arg(linker_version_flag)
         .input("main.rs")
         .run();
@@ -29,8 +29,8 @@ fn main() {
 
     // It should not be used when we explicitly opt-out of lld.
     let output = rustc()
-        .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
         .link_arg(linker_version_flag)
+        .verbose()
         .arg("-Zlinker-features=-lld")
         .input("main.rs")
         .run();
@@ -43,8 +43,8 @@ fn main() {
     // While we're here, also check that the last linker feature flag "wins" when passed multiple
     // times to rustc.
     let output = rustc()
-        .env("RUSTC_LOG", "rustc_codegen_ssa::back::link=info")
         .link_arg(linker_version_flag)
+        .verbose()
         .arg("-Clink-self-contained=+linker")
         .arg("-Zunstable-options")
         .arg("-Zlinker-features=-lld")
@@ -60,6 +60,7 @@ fn main() {
 }
 
 fn find_lld_version_in_logs(stderr: String) -> bool {
-    let lld_version_re = Regex::new(r"^LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
+    let lld_version_re =
+        Regex::new(r"^warning: linker stdout: LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
     stderr.lines().any(|line| lld_version_re.is_match(line.trim()))
 }

From 537218afb2b7f6c8ef9793a52b45c73d1dcb4d4c Mon Sep 17 00:00:00 2001
From: jyn <github@jyn.dev>
Date: Wed, 16 Oct 2024 01:14:10 -0400
Subject: [PATCH 2/9] make it possible to silence linker warnings with a
 crate-level attribute

this was slightly complicated because codegen_ssa doesn't have access to a tcx.
---
 compiler/rustc_codegen_ssa/src/back/link.rs | 21 ++++++++-----
 compiler/rustc_codegen_ssa/src/lib.rs       | 21 +++++++++++++
 compiler/rustc_lint_defs/src/builtin.rs     | 34 +++++++++++++++++++++
 compiler/rustc_lint_defs/src/lib.rs         | 14 ++++++++-
 compiler/rustc_middle/src/lint.rs           |  6 ++--
 5 files changed, 85 insertions(+), 11 deletions(-)

diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 378b6a50f7f77..80758aac13460 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -15,13 +15,14 @@ use rustc_ast::CRATE_NODE_ID;
 use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
 use rustc_data_structures::memmap::Mmap;
 use rustc_data_structures::temp_dir::MaybeTempDir;
-use rustc_errors::DiagCtxtHandle;
+use rustc_errors::{DiagCtxtHandle, LintDiagnostic};
 use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize};
 use rustc_hir::def_id::{CrateNum, LOCAL_CRATE};
-use rustc_macros::Diagnostic;
+use rustc_macros::LintDiagnostic;
 use rustc_metadata::fs::{METADATA_FILENAME, copy_to_stdout, emit_wrapper_file};
 use rustc_metadata::{find_native_static_library, walk_native_lib_search_dirs};
 use rustc_middle::bug;
+use rustc_middle::lint::lint_level;
 use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
 use rustc_middle::middle::dependency_format::Linkage;
 use rustc_middle::middle::exported_symbols::SymbolExportKind;
@@ -30,6 +31,7 @@ use rustc_session::config::{
     OutputType, PrintKind, SplitDwarfKind, Strip,
 };
 use rustc_session::cstore::DllImport;
+use rustc_session::lint::builtin::LINKER_MESSAGES;
 use rustc_session::output::{check_file_is_writeable, invalid_output_for_target, out_filename};
 use rustc_session::search_paths::PathKind;
 use rustc_session::utils::NativeLibKind;
@@ -750,7 +752,7 @@ fn link_dwarf_object(sess: &Session, cg_results: &CodegenResults, executable_out
     }
 }
 
-#[derive(Diagnostic)]
+#[derive(LintDiagnostic)]
 #[diag(codegen_ssa_linker_output)]
 /// Translating this is kind of useless. We don't pass translation flags to the linker, so we'd just
 /// end up with inconsistent languages within the same diagnostic.
@@ -1038,6 +1040,13 @@ fn link_natively(
                 sess.dcx().abort_if_errors();
             }
 
+            let (level, src) = codegen_results.crate_info.lint_levels.linker_messages;
+            let lint = |msg| {
+                lint_level(sess, LINKER_MESSAGES, level, src, None, |diag| {
+                    LinkerOutput { inner: msg }.decorate_lint(diag)
+                })
+            };
+
             if !prog.stderr.is_empty() {
                 // We already print `warning:` at the start of the diagnostic. Remove it from the linker output if present.
                 let stderr = escape_string(&prog.stderr);
@@ -1046,12 +1055,10 @@ fn link_natively(
                     .strip_prefix("warning: ")
                     .unwrap_or(&stderr)
                     .replace(": warning: ", ": ");
-                sess.dcx().emit_warn(LinkerOutput { inner: format!("linker stderr: {stderr}") });
+                lint(format!("linker stderr: {stderr}"));
             }
             if !prog.stdout.is_empty() && sess.opts.verbose {
-                sess.dcx().emit_warn(LinkerOutput {
-                    inner: format!("linker stdout: {}", escape_string(&prog.stdout)),
-                });
+                lint(format!("linker stdout: {}", escape_string(&prog.stdout)))
             }
         }
         Err(e) => {
diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs
index 332f2ffbc88ca..40299ae263085 100644
--- a/compiler/rustc_codegen_ssa/src/lib.rs
+++ b/compiler/rustc_codegen_ssa/src/lib.rs
@@ -29,18 +29,23 @@ use rustc_ast as ast;
 use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
 use rustc_data_structures::sync::Lrc;
 use rustc_data_structures::unord::UnordMap;
+use rustc_hir::CRATE_HIR_ID;
 use rustc_hir::def_id::CrateNum;
 use rustc_macros::{Decodable, Encodable, HashStable};
 use rustc_middle::dep_graph::WorkProduct;
+use rustc_middle::lint::LintLevelSource;
 use rustc_middle::middle::debugger_visualizer::DebuggerVisualizerFile;
 use rustc_middle::middle::dependency_format::Dependencies;
 use rustc_middle::middle::exported_symbols::SymbolExportKind;
+use rustc_middle::ty::TyCtxt;
 use rustc_middle::util::Providers;
 use rustc_serialize::opaque::{FileEncoder, MemDecoder};
 use rustc_serialize::{Decodable, Decoder, Encodable, Encoder};
 use rustc_session::Session;
 use rustc_session::config::{CrateType, OutputFilenames, OutputType, RUST_CGU_EXT};
 use rustc_session::cstore::{self, CrateSource};
+use rustc_session::lint::Level;
+use rustc_session::lint::builtin::LINKER_MESSAGES;
 use rustc_session::utils::NativeLibKind;
 use rustc_span::Symbol;
 
@@ -303,3 +308,19 @@ impl CodegenResults {
         Ok((codegen_results, outputs))
     }
 }
+
+/// A list of lint levels used in codegen.
+///
+/// When using `-Z link-only`, we don't have access to the tcx and must work
+/// solely from the `.rlink` file. `Lint`s are defined too early to be encodeable.
+/// Instead, encode exactly the information we need.
+#[derive(Copy, Clone, Debug, Encodable, Decodable)]
+pub struct CodegenLintLevels {
+    linker_messages: (Level, LintLevelSource),
+}
+
+impl CodegenLintLevels {
+    pub fn from_tcx(tcx: TyCtxt<'_>) -> Self {
+        Self { linker_messages: tcx.lint_level_at_node(LINKER_MESSAGES, CRATE_HIR_ID) }
+    }
+}
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 9fc527a6a3ab3..5cdd18f5ea903 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -60,6 +60,7 @@ declare_lint_pass! {
         LARGE_ASSIGNMENTS,
         LATE_BOUND_LIFETIME_ARGUMENTS,
         LEGACY_DERIVE_HELPERS,
+        LINKER_MESSAGES,
         LONG_RUNNING_CONST_EVAL,
         LOSSY_PROVENANCE_CASTS,
         MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS,
@@ -4085,6 +4086,39 @@ declare_lint! {
     "call to foreign functions or function pointers with FFI-unwind ABI"
 }
 
+declare_lint! {
+    /// The `linker_messages` lint forwards warnings from the linker.
+    ///
+    /// ### Example
+    ///
+    /// ```rust,ignore (needs CLI args, platform-specific)
+    /// extern "C" {
+    ///   fn foo();
+    /// }
+    /// fn main () { unsafe { foo(); } }
+    /// ```
+    ///
+    /// On Linux, using `gcc -Wl,--warn-unresolved-symbols` as a linker, this will produce
+    ///
+    /// ```text
+    /// warning: linker stderr: rust-lld: undefined symbol: foo
+    ///          >>> referenced by rust_out.69edbd30df4ae57d-cgu.0
+    ///          >>>               rust_out.rust_out.69edbd30df4ae57d-cgu.0.rcgu.o:(rust_out::main::h3a90094b06757803)
+    ///   |
+    ///   = note: `#[warn(linker_messages)]` on by default
+    ///
+    /// warning: 1 warning emitted
+    /// ```
+    ///
+    /// ### Explanation
+    ///
+    /// Linkers emit platform-specific and program-specific warnings that cannot be predicted in advance by the rust compiler.
+    /// They are forwarded by default, but can be disabled by adding `#![allow(linker_messages)]` at the crate root.
+    pub LINKER_MESSAGES,
+    Warn,
+    "warnings emitted at runtime by the target-specific linker program"
+}
+
 declare_lint! {
     /// The `named_arguments_used_positionally` lint detects cases where named arguments are only
     /// used positionally in format strings. This usage is valid but potentially very confusing.
diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs
index 7786d3eb59af8..9f4c5d89d0e92 100644
--- a/compiler/rustc_lint_defs/src/lib.rs
+++ b/compiler/rustc_lint_defs/src/lib.rs
@@ -161,7 +161,19 @@ impl<HCX: rustc_hir::HashStableContext> ToStableHashKey<HCX> for LintExpectation
 /// Setting for how to handle a lint.
 ///
 /// See: <https://doc.rust-lang.org/rustc/lints/levels.html>
-#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash, HashStable_Generic)]
+#[derive(
+    Clone,
+    Copy,
+    PartialEq,
+    PartialOrd,
+    Eq,
+    Ord,
+    Debug,
+    Hash,
+    Encodable,
+    Decodable,
+    HashStable_Generic
+)]
 pub enum Level {
     /// The `allow` level will not issue any message.
     Allow,
diff --git a/compiler/rustc_middle/src/lint.rs b/compiler/rustc_middle/src/lint.rs
index 620d9f1c35790..cae980cde613f 100644
--- a/compiler/rustc_middle/src/lint.rs
+++ b/compiler/rustc_middle/src/lint.rs
@@ -4,7 +4,7 @@ use rustc_data_structures::fx::FxIndexMap;
 use rustc_data_structures::sorted_map::SortedMap;
 use rustc_errors::{Diag, MultiSpan};
 use rustc_hir::{HirId, ItemLocalId};
-use rustc_macros::HashStable;
+use rustc_macros::{Decodable, Encodable, HashStable};
 use rustc_session::Session;
 use rustc_session::lint::builtin::{self, FORBIDDEN_LINT_GROUPS};
 use rustc_session::lint::{FutureIncompatibilityReason, Level, Lint, LintExpectationId, LintId};
@@ -15,7 +15,7 @@ use tracing::instrument;
 use crate::ty::TyCtxt;
 
 /// How a lint level was set.
-#[derive(Clone, Copy, PartialEq, Eq, HashStable, Debug)]
+#[derive(Clone, Copy, PartialEq, Eq, Encodable, Decodable, HashStable, Debug)]
 pub enum LintLevelSource {
     /// Lint is at the default level as declared in rustc.
     Default,
@@ -173,7 +173,7 @@ impl TyCtxt<'_> {
 /// This struct represents a lint expectation and holds all required information
 /// to emit the `unfulfilled_lint_expectations` lint if it is unfulfilled after
 /// the `LateLintPass` has completed.
-#[derive(Clone, Debug, HashStable)]
+#[derive(Clone, Debug, Encodable, Decodable, HashStable)]
 pub struct LintExpectation {
     /// The reason for this expectation that can optionally be added as part of
     /// the attribute. It will be displayed as part of the lint message.

From fda23843337472d03d9d789ce2b90f0efdb06ae0 Mon Sep 17 00:00:00 2001
From: jyn <github@jyn.dev>
Date: Wed, 16 Oct 2024 01:35:01 -0400
Subject: [PATCH 3/9] warn on unused linker_messages warning attributes

---
 compiler/rustc_passes/messages.ftl      |  3 +++
 compiler/rustc_passes/src/check_attr.rs | 28 +++++++++++++++++++++++++
 compiler/rustc_passes/src/errors.rs     |  2 ++
 compiler/rustc_span/src/symbol.rs       |  1 +
 tests/ui/lint/linker-warning-bin.rs     |  6 ++++++
 tests/ui/lint/linker-warning.rs         |  9 ++++++++
 tests/ui/lint/linker-warning.stderr     | 22 +++++++++++++++++++
 7 files changed, 71 insertions(+)
 create mode 100644 tests/ui/lint/linker-warning-bin.rs
 create mode 100644 tests/ui/lint/linker-warning.rs
 create mode 100644 tests/ui/lint/linker-warning.stderr

diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index 3ed600a717f53..5d9718258b9bc 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -817,6 +817,9 @@ passes_unused_duplicate =
 passes_unused_empty_lints_note =
     attribute `{$name}` with an empty list has no effect
 
+passes_unused_linker_warnings_note =
+    the `linker_warnings` lint can only be controlled at the root of a crate that needs to be linked
+
 passes_unused_multiple =
     multiple `{$name}` attributes
     .suggestion = remove this attribute
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 1b2b8ac5dd9e7..7df792c0f7963 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -25,6 +25,7 @@ use rustc_middle::traits::ObligationCause;
 use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::{self, TyCtxt, TypingMode};
 use rustc_middle::{bug, span_bug};
+use rustc_session::config::CrateType;
 use rustc_session::lint::builtin::{
     CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, INVALID_MACRO_EXPORT_ARGUMENTS,
     UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
@@ -2328,6 +2329,33 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             && item.path == sym::reason
         {
             errors::UnusedNote::NoLints { name: attr.name_or_empty() }
+        } else if matches!(
+            attr.name_or_empty(),
+            sym::allow | sym::warn | sym::deny | sym::forbid | sym::expect
+        ) && let Some(meta) = attr.meta_item_list()
+            && meta.iter().any(|meta| {
+                meta.meta_item().map_or(false, |item| item.path == sym::linker_messages)
+            })
+        {
+            if hir_id != CRATE_HIR_ID {
+                let err = match attr.style {
+                    ast::AttrStyle::Outer => errors::OuterCrateLevelAttr,
+                    ast::AttrStyle::Inner => errors::OuterCrateLevelAttr,
+                };
+                self.tcx.emit_node_span_lint(UNUSED_ATTRIBUTES, hir_id, attr.span, err);
+                return;
+            } else {
+                let never_needs_link = self
+                    .tcx
+                    .crate_types()
+                    .iter()
+                    .all(|kind| matches!(kind, CrateType::Rlib | CrateType::Staticlib));
+                if never_needs_link {
+                    errors::UnusedNote::LinkerWarningsBinaryCrateOnly
+                } else {
+                    return;
+                }
+            }
         } else if attr.name_or_empty() == sym::default_method_body_is_const {
             errors::UnusedNote::DefaultMethodBodyConst
         } else {
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index c3043ac60aa69..89dd10b3e77d7 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -802,6 +802,8 @@ pub(crate) enum UnusedNote {
     NoLints { name: Symbol },
     #[note(passes_unused_default_method_body_const_note)]
     DefaultMethodBodyConst,
+    #[note(passes_unused_linker_warnings_note)]
+    LinkerWarningsBinaryCrateOnly,
 }
 
 #[derive(LintDiagnostic)]
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index f5ce5dbc9d66a..aa22d0f706ddc 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -1189,6 +1189,7 @@ symbols! {
         link_section,
         linkage,
         linker,
+        linker_messages,
         lint_reasons,
         literal,
         load,
diff --git a/tests/ui/lint/linker-warning-bin.rs b/tests/ui/lint/linker-warning-bin.rs
new file mode 100644
index 0000000000000..ead0aa002bf28
--- /dev/null
+++ b/tests/ui/lint/linker-warning-bin.rs
@@ -0,0 +1,6 @@
+//@ build-pass
+#![crate_type = "bin"]
+#![warn(unused_attributes)]
+#![allow(linker_messages)]
+
+fn main() {}
diff --git a/tests/ui/lint/linker-warning.rs b/tests/ui/lint/linker-warning.rs
new file mode 100644
index 0000000000000..10e3f56ab9590
--- /dev/null
+++ b/tests/ui/lint/linker-warning.rs
@@ -0,0 +1,9 @@
+//@ check-pass
+#![crate_type = "lib"]
+#![warn(unused_attributes)]
+#![allow(linker_messages)]
+//~^ WARNING unused attribute
+
+#[allow(linker_messages)]
+//~^ WARNING should be an inner attribute
+fn foo() {}
diff --git a/tests/ui/lint/linker-warning.stderr b/tests/ui/lint/linker-warning.stderr
new file mode 100644
index 0000000000000..3a2c392fd0312
--- /dev/null
+++ b/tests/ui/lint/linker-warning.stderr
@@ -0,0 +1,22 @@
+warning: crate-level attribute should be an inner attribute: add an exclamation mark: `#![foo]`
+  --> $DIR/linker-warning.rs:7:1
+   |
+LL | #[allow(linker_messages)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: the lint level is defined here
+  --> $DIR/linker-warning.rs:3:9
+   |
+LL | #![warn(unused_attributes)]
+   |         ^^^^^^^^^^^^^^^^^
+
+warning: unused attribute
+  --> $DIR/linker-warning.rs:4:1
+   |
+LL | #![allow(linker_messages)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute
+   |
+   = note: the `linker_warnings` lint can only be controlled at the root of a crate that needs to be linked
+
+warning: 2 warnings emitted
+

From cedd4cad21298ca8b825ab3c56a84753176dc8e8 Mon Sep 17 00:00:00 2001
From: jyn <github@jyn.dev>
Date: Fri, 29 Nov 2024 10:28:54 -0500
Subject: [PATCH 4/9] ignore linker errors on all platforms

---
 src/bootstrap/src/core/build_steps/test.rs | 2 ++
 src/bootstrap/src/core/builder/cargo.rs    | 7 +++++++
 2 files changed, 9 insertions(+)

diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index 9f3e4d9cc8995..38e056cbc14ea 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -3577,6 +3577,8 @@ impl Step for CodegenGCC {
         let mut cargo = build_cargo();
 
         cargo
+            // cg_gcc's build system ignores RUSTFLAGS. pass some flags through CG_RUSTFLAGS instead.
+            .env("CG_RUSTFLAGS", "-Alinker-messages")
             .arg("--")
             .arg("test")
             .arg("--use-system-gcc")
diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs
index f9fb19ddb0952..031cbb7dcdbc8 100644
--- a/src/bootstrap/src/core/builder/cargo.rs
+++ b/src/bootstrap/src/core/builder/cargo.rs
@@ -269,6 +269,13 @@ impl Cargo {
             self.rustflags.arg("-Clink-arg=-gz");
         }
 
+        // Ignore linker warnings for now. These are complicated to fix and don't affect the build.
+        // FIXME: we should really investigate these...
+        // cfg(bootstrap)
+        if compiler.stage != 0 {
+            self.rustflags.arg("-Alinker-messages");
+        }
+
         // Throughout the build Cargo can execute a number of build scripts
         // compiling C/C++ code and we need to pass compilers, archivers, flags, etc
         // obtained previously to those build scripts.

From b757663a00260e22799a1bbdc5d6ba603e4bb30d Mon Sep 17 00:00:00 2001
From: jyn <github@jyn.dev>
Date: Sat, 21 Dec 2024 10:06:07 -0500
Subject: [PATCH 5/9] don't ICE when emitting linker errors during `-Z
 link-only`

note that this still ICEs when passed `-Z link-only --error-format json` because i can't be bothered to fix it right now
---
 compiler/rustc_errors/src/json.rs       | 47 ++++++++++++++++---------
 compiler/rustc_errors/src/json/tests.rs |  2 +-
 compiler/rustc_session/src/session.rs   |  9 +++--
 src/librustdoc/core.rs                  |  2 +-
 tests/run-make/linker-warning/rmake.rs  | 29 +++++++++++++++
 5 files changed, 67 insertions(+), 22 deletions(-)

diff --git a/compiler/rustc_errors/src/json.rs b/compiler/rustc_errors/src/json.rs
index c1188665a05f4..95c81fc5f4482 100644
--- a/compiler/rustc_errors/src/json.rs
+++ b/compiler/rustc_errors/src/json.rs
@@ -21,7 +21,7 @@ use rustc_error_messages::FluentArgs;
 use rustc_lint_defs::Applicability;
 use rustc_span::Span;
 use rustc_span::hygiene::ExpnData;
-use rustc_span::source_map::SourceMap;
+use rustc_span::source_map::{FilePathMapping, SourceMap};
 use serde::Serialize;
 use termcolor::{ColorSpec, WriteColor};
 
@@ -45,7 +45,7 @@ pub struct JsonEmitter {
     #[setters(skip)]
     dst: IntoDynSyncSend<Box<dyn Write + Send>>,
     #[setters(skip)]
-    sm: Lrc<SourceMap>,
+    sm: Option<Lrc<SourceMap>>,
     fluent_bundle: Option<Lrc<FluentBundle>>,
     #[setters(skip)]
     fallback_bundle: LazyFallbackBundle,
@@ -65,7 +65,7 @@ pub struct JsonEmitter {
 impl JsonEmitter {
     pub fn new(
         dst: Box<dyn Write + Send>,
-        sm: Lrc<SourceMap>,
+        sm: Option<Lrc<SourceMap>>,
         fallback_bundle: LazyFallbackBundle,
         pretty: bool,
         json_rendered: HumanReadableErrorType,
@@ -171,7 +171,7 @@ impl Emitter for JsonEmitter {
     }
 
     fn source_map(&self) -> Option<&SourceMap> {
-        Some(&self.sm)
+        self.sm.as_deref()
     }
 
     fn should_show_explain(&self) -> bool {
@@ -371,7 +371,7 @@ impl Diagnostic {
         }
         HumanEmitter::new(dst, Lrc::clone(&je.fallback_bundle))
             .short_message(short)
-            .sm(Some(Lrc::clone(&je.sm)))
+            .sm(je.sm.clone())
             .fluent_bundle(je.fluent_bundle.clone())
             .diagnostic_width(je.diagnostic_width)
             .macro_backtrace(je.macro_backtrace)
@@ -458,23 +458,34 @@ impl DiagnosticSpan {
         mut backtrace: impl Iterator<Item = ExpnData>,
         je: &JsonEmitter,
     ) -> DiagnosticSpan {
-        let start = je.sm.lookup_char_pos(span.lo());
+        let empty_source_map;
+        let sm = match &je.sm {
+            Some(s) => s,
+            None => {
+                span = rustc_span::DUMMY_SP;
+                empty_source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
+                empty_source_map
+                    .new_source_file(std::path::PathBuf::from("empty.rs").into(), String::new());
+                &empty_source_map
+            }
+        };
+        let start = sm.lookup_char_pos(span.lo());
         // If this goes from the start of a line to the end and the replacement
         // is an empty string, increase the length to include the newline so we don't
         // leave an empty line
         if start.col.0 == 0
             && let Some((suggestion, _)) = suggestion
             && suggestion.is_empty()
-            && let Ok(after) = je.sm.span_to_next_source(span)
+            && let Ok(after) = sm.span_to_next_source(span)
             && after.starts_with('\n')
         {
             span = span.with_hi(span.hi() + rustc_span::BytePos(1));
         }
-        let end = je.sm.lookup_char_pos(span.hi());
+        let end = sm.lookup_char_pos(span.hi());
         let backtrace_step = backtrace.next().map(|bt| {
             let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je);
             let def_site_span = Self::from_span_full(
-                je.sm.guess_head_span(bt.def_site),
+                sm.guess_head_span(bt.def_site),
                 false,
                 None,
                 None,
@@ -489,7 +500,7 @@ impl DiagnosticSpan {
         });
 
         DiagnosticSpan {
-            file_name: je.sm.filename_for_diagnostics(&start.file.name).to_string(),
+            file_name: sm.filename_for_diagnostics(&start.file.name).to_string(),
             byte_start: start.file.original_relative_byte_pos(span.lo()).0,
             byte_end: start.file.original_relative_byte_pos(span.hi()).0,
             line_start: start.line,
@@ -559,19 +570,20 @@ impl DiagnosticSpanLine {
     /// `span` within the line.
     fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
         je.sm
-            .span_to_lines(span)
-            .map(|lines| {
+            .as_ref()
+            .and_then(|sm| {
+                let lines = sm.span_to_lines(span).ok()?;
                 // We can't get any lines if the source is unavailable.
                 if !should_show_source_code(
                     &je.ignored_directories_in_source_blocks,
-                    &je.sm,
+                    &sm,
                     &lines.file,
                 ) {
-                    return vec![];
+                    return None;
                 }
 
                 let sf = &*lines.file;
-                lines
+                let span_lines = lines
                     .lines
                     .iter()
                     .map(|line| {
@@ -582,8 +594,9 @@ impl DiagnosticSpanLine {
                             line.end_col.0 + 1,
                         )
                     })
-                    .collect()
+                    .collect();
+                Some(span_lines)
             })
-            .unwrap_or_else(|_| vec![])
+            .unwrap_or_default()
     }
 }
diff --git a/compiler/rustc_errors/src/json/tests.rs b/compiler/rustc_errors/src/json/tests.rs
index 0de555b83d3ab..cebaf7c1cfe45 100644
--- a/compiler/rustc_errors/src/json/tests.rs
+++ b/compiler/rustc_errors/src/json/tests.rs
@@ -47,7 +47,7 @@ fn test_positions(code: &str, span: (u32, u32), expected_output: SpanTestData) {
         let output = Arc::new(Mutex::new(Vec::new()));
         let je = JsonEmitter::new(
             Box::new(Shared { data: output.clone() }),
-            sm,
+            Some(sm),
             fallback_bundle,
             true, // pretty
             HumanReadableErrorType::Short,
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index 60f1154dc6d02..022c2aa880e25 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -895,13 +895,16 @@ fn default_emitter(
         }
         t => t,
     };
+
+    let source_map = if sopts.unstable_opts.link_only { None } else { Some(source_map) };
+
     match sopts.error_format {
         config::ErrorOutputType::HumanReadable(kind, color_config) => {
             let short = kind.short();
 
             if let HumanReadableErrorType::AnnotateSnippet = kind {
                 let emitter = AnnotateSnippetEmitter::new(
-                    Some(source_map),
+                    source_map,
                     bundle,
                     fallback_bundle,
                     short,
@@ -911,7 +914,7 @@ fn default_emitter(
             } else {
                 let emitter = HumanEmitter::new(stderr_destination(color_config), fallback_bundle)
                     .fluent_bundle(bundle)
-                    .sm(Some(source_map))
+                    .sm(source_map)
                     .short_message(short)
                     .teach(sopts.unstable_opts.teach)
                     .diagnostic_width(sopts.diagnostic_width)
@@ -1442,7 +1445,7 @@ fn mk_emitter(output: ErrorOutputType) -> Box<DynEmitter> {
         config::ErrorOutputType::Json { pretty, json_rendered, color_config } => {
             Box::new(JsonEmitter::new(
                 Box::new(io::BufWriter::new(io::stderr())),
-                Lrc::new(SourceMap::new(FilePathMapping::empty())),
+                Some(Lrc::new(SourceMap::new(FilePathMapping::empty()))),
                 fallback_bundle,
                 pretty,
                 json_rendered,
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 0dda3466a7173..78fee20aee36a 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -178,7 +178,7 @@ pub(crate) fn new_dcx(
             Box::new(
                 JsonEmitter::new(
                     Box::new(io::BufWriter::new(io::stderr())),
-                    source_map,
+                    Some(source_map),
                     fallback_bundle,
                     pretty,
                     json_rendered,
diff --git a/tests/run-make/linker-warning/rmake.rs b/tests/run-make/linker-warning/rmake.rs
index d2bb12aafcba9..5967f5b32e9aa 100644
--- a/tests/run-make/linker-warning/rmake.rs
+++ b/tests/run-make/linker-warning/rmake.rs
@@ -44,4 +44,33 @@ fn main() {
         .assert_stderr_contains("object files omitted")
         .assert_stderr_contains_regex(r"\{")
         .assert_stderr_not_contains_regex(r"lib(/|\\\\)libstd");
+
+    // Make sure we show linker warnings even across `-Z no-link`
+    rustc()
+        .arg("-Zno-link")
+        .input("-")
+        .stdin_buf("#![deny(linker_messages)] \n fn main() {}")
+        .run()
+        .assert_stderr_equals("");
+    rustc()
+        .arg("-Zlink-only")
+        .arg("rust_out.rlink")
+        .linker("./fake-linker")
+        .link_arg("run_make_warn")
+        .run_fail()
+        // NOTE: the error message here is quite bad (we don't have a source
+        // span, but still try to print the lint source). But `-Z link-only` is
+        // unstable and this still shows the linker warning itself so this is
+        // probably good enough.
+        .assert_stderr_contains("linker stderr: bar");
+
+    // Same thing, but with json output.
+    rustc()
+        .error_format("json")
+        .arg("-Zlink-only")
+        .arg("rust_out.rlink")
+        .linker("./fake-linker")
+        .link_arg("run_make_warn")
+        .run_fail()
+        .assert_stderr_contains(r#""$message_type":"diagnostic""#);
 }

From 26708aa941d64242e2cd8a8e29e0d16c875bcc2a Mon Sep 17 00:00:00 2001
From: jyn <github@jyn.dev>
Date: Fri, 17 Jan 2025 08:00:28 -0500
Subject: [PATCH 6/9] Don't require `--verbose` to show linker stdout

---
 compiler/rustc_codegen_ssa/src/back/link.rs     | 11 +++++++----
 compiler/rustc_passes/src/check_attr.rs         | 17 +++++++++++++----
 tests/run-make/linker-warning/rmake.rs          |  7 +------
 .../rust-lld-by-default-beta-stable/rmake.rs    |  5 +++--
 .../rust-lld-by-default-nightly/rmake.rs        |  5 ++---
 tests/run-make/rust-lld-custom-target/rmake.rs  |  2 --
 tests/run-make/rust-lld/rmake.rs                | 10 ++--------
 7 files changed, 28 insertions(+), 29 deletions(-)

diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index 80758aac13460..fae59a3b9dc86 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -1040,6 +1040,11 @@ fn link_natively(
                 sess.dcx().abort_if_errors();
             }
 
+            let stderr = escape_string(&prog.stderr);
+            let stdout = escape_string(&prog.stdout);
+            info!("linker stderr:\n{}", &stderr);
+            info!("linker stdout:\n{}", &stdout);
+
             let (level, src) = codegen_results.crate_info.lint_levels.linker_messages;
             let lint = |msg| {
                 lint_level(sess, LINKER_MESSAGES, level, src, None, |diag| {
@@ -1049,16 +1054,14 @@ fn link_natively(
 
             if !prog.stderr.is_empty() {
                 // We already print `warning:` at the start of the diagnostic. Remove it from the linker output if present.
-                let stderr = escape_string(&prog.stderr);
-                debug!("original stderr: {stderr}");
                 let stderr = stderr
                     .strip_prefix("warning: ")
                     .unwrap_or(&stderr)
                     .replace(": warning: ", ": ");
                 lint(format!("linker stderr: {stderr}"));
             }
-            if !prog.stdout.is_empty() && sess.opts.verbose {
-                lint(format!("linker stdout: {}", escape_string(&prog.stdout)))
+            if !prog.stdout.is_empty() {
+                lint(format!("linker stdout: {}", stdout))
             }
         }
         Err(e) => {
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 7df792c0f7963..3d88fe1a4d39c 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -2338,11 +2338,20 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
             })
         {
             if hir_id != CRATE_HIR_ID {
-                let err = match attr.style {
-                    ast::AttrStyle::Outer => errors::OuterCrateLevelAttr,
-                    ast::AttrStyle::Inner => errors::OuterCrateLevelAttr,
+                match attr.style {
+                    ast::AttrStyle::Outer => self.tcx.emit_node_span_lint(
+                        UNUSED_ATTRIBUTES,
+                        hir_id,
+                        attr.span,
+                        errors::OuterCrateLevelAttr,
+                    ),
+                    ast::AttrStyle::Inner => self.tcx.emit_node_span_lint(
+                        UNUSED_ATTRIBUTES,
+                        hir_id,
+                        attr.span,
+                        errors::InnerCrateLevelAttr,
+                    ),
                 };
-                self.tcx.emit_node_span_lint(UNUSED_ATTRIBUTES, hir_id, attr.span, err);
                 return;
             } else {
                 let never_needs_link = self
diff --git a/tests/run-make/linker-warning/rmake.rs b/tests/run-make/linker-warning/rmake.rs
index 5967f5b32e9aa..0a4c13c5f019e 100644
--- a/tests/run-make/linker-warning/rmake.rs
+++ b/tests/run-make/linker-warning/rmake.rs
@@ -14,16 +14,11 @@ fn main() {
     let warnings = run_rustc().link_arg("run_make_warn").run();
     warnings.assert_stderr_contains("warning: linker stderr: bar");
 
-    // Make sure it shows stdout, but only when --verbose is passed
+    // Make sure it shows stdout
     run_rustc()
         .link_arg("run_make_info")
-        .verbose()
         .run()
         .assert_stderr_contains("warning: linker stdout: foo");
-    run_rustc()
-        .link_arg("run_make_info")
-        .run()
-        .assert_stderr_not_contains("warning: linker stdout: foo");
 
     // Make sure we short-circuit this new path if the linker exits with an error
     // (so the diagnostic is less verbose)
diff --git a/tests/run-make/rust-lld-by-default-beta-stable/rmake.rs b/tests/run-make/rust-lld-by-default-beta-stable/rmake.rs
index 85860fc95b829..d2f1e8b253a79 100644
--- a/tests/run-make/rust-lld-by-default-beta-stable/rmake.rs
+++ b/tests/run-make/rust-lld-by-default-beta-stable/rmake.rs
@@ -12,7 +12,7 @@ use run_make_support::rustc;
 fn main() {
     // A regular compilation should not use rust-lld by default. We'll check that by asking the
     // linker to display its version number with a link-arg.
-    let output = rustc().verbose().link_arg("-Wl,-v").input("main.rs").run();
+    let output = rustc().link_arg("-Wl,-v").input("main.rs").run();
     assert!(
         !find_lld_version_in_logs(output.stderr_utf8()),
         "the LLD version string should not be present in the output logs:\n{}",
@@ -21,6 +21,7 @@ fn main() {
 }
 
 fn find_lld_version_in_logs(stderr: String) -> bool {
-    let lld_version_re = Regex::new(r"^LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
+    let lld_version_re =
+        Regex::new(r"^warning: linker stdout: LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
     stderr.lines().any(|line| lld_version_re.is_match(line.trim()))
 }
diff --git a/tests/run-make/rust-lld-by-default-nightly/rmake.rs b/tests/run-make/rust-lld-by-default-nightly/rmake.rs
index 4026af8306450..a25a69b859bab 100644
--- a/tests/run-make/rust-lld-by-default-nightly/rmake.rs
+++ b/tests/run-make/rust-lld-by-default-nightly/rmake.rs
@@ -12,7 +12,7 @@ use run_make_support::rustc;
 fn main() {
     // A regular compilation should use rust-lld by default. We'll check that by asking the linker
     // to display its version number with a link-arg.
-    let output = rustc().verbose().link_arg("-Wl,-v").input("main.rs").run();
+    let output = rustc().link_arg("-Wl,-v").input("main.rs").run();
     assert!(
         find_lld_version_in_logs(output.stderr_utf8()),
         "the LLD version string should be present in the output logs:\n{}",
@@ -20,8 +20,7 @@ fn main() {
     );
 
     // But it can still be disabled by turning the linker feature off.
-    let output =
-        rustc().verbose().link_arg("-Wl,-v").arg("-Zlinker-features=-lld").input("main.rs").run();
+    let output = rustc().link_arg("-Wl,-v").arg("-Zlinker-features=-lld").input("main.rs").run();
     assert!(
         !find_lld_version_in_logs(output.stderr_utf8()),
         "the LLD version string should not be present in the output logs:\n{}",
diff --git a/tests/run-make/rust-lld-custom-target/rmake.rs b/tests/run-make/rust-lld-custom-target/rmake.rs
index 17468223306da..a6f936ba6e5c5 100644
--- a/tests/run-make/rust-lld-custom-target/rmake.rs
+++ b/tests/run-make/rust-lld-custom-target/rmake.rs
@@ -15,7 +15,6 @@ fn main() {
     // Compile to a custom target spec with rust-lld enabled by default. We'll check that by asking
     // the linker to display its version number with a link-arg.
     let output = rustc()
-        .verbose()
         .crate_type("cdylib")
         .target("custom-target.json")
         .link_arg("-Wl,-v")
@@ -29,7 +28,6 @@ fn main() {
 
     // But it can also be disabled via linker features.
     let output = rustc()
-        .verbose()
         .crate_type("cdylib")
         .target("custom-target.json")
         .arg("-Zlinker-features=-lld")
diff --git a/tests/run-make/rust-lld/rmake.rs b/tests/run-make/rust-lld/rmake.rs
index 732bc6f6ba2f9..1f098fdb1d910 100644
--- a/tests/run-make/rust-lld/rmake.rs
+++ b/tests/run-make/rust-lld/rmake.rs
@@ -17,7 +17,6 @@ fn main() {
         .arg("-Zlinker-features=+lld")
         .arg("-Clink-self-contained=+linker")
         .arg("-Zunstable-options")
-        .verbose()
         .link_arg(linker_version_flag)
         .input("main.rs")
         .run();
@@ -28,12 +27,8 @@ fn main() {
     );
 
     // It should not be used when we explicitly opt-out of lld.
-    let output = rustc()
-        .link_arg(linker_version_flag)
-        .verbose()
-        .arg("-Zlinker-features=-lld")
-        .input("main.rs")
-        .run();
+    let output =
+        rustc().link_arg(linker_version_flag).arg("-Zlinker-features=-lld").input("main.rs").run();
     assert!(
         !find_lld_version_in_logs(output.stderr_utf8()),
         "the LLD version string should not be present in the output logs:\n{}",
@@ -44,7 +39,6 @@ fn main() {
     // times to rustc.
     let output = rustc()
         .link_arg(linker_version_flag)
-        .verbose()
         .arg("-Clink-self-contained=+linker")
         .arg("-Zunstable-options")
         .arg("-Zlinker-features=-lld")

From 0ff369c5a6359a586638254594cad328f8ce25d5 Mon Sep 17 00:00:00 2001
From: jyn <github@jyn.dev>
Date: Sun, 19 Jan 2025 09:26:08 -0500
Subject: [PATCH 7/9] Silence progress messages from MSVC link.exe

These cannot be silenced with a CLI flag, and are not useful to warn
about. They can still be viewed for debugging purposes using
`RUSTC_LOG=rustc_codegen_ssa::link::back`.
---
 compiler/rustc_codegen_ssa/src/back/link.rs | 41 +++++++++++++++------
 1 file changed, 29 insertions(+), 12 deletions(-)

diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs
index fae59a3b9dc86..9688a5d47f2d9 100644
--- a/compiler/rustc_codegen_ssa/src/back/link.rs
+++ b/compiler/rustc_codegen_ssa/src/back/link.rs
@@ -992,6 +992,11 @@ fn link_natively(
 
     match prog {
         Ok(prog) => {
+            let is_msvc_link_exe = sess.target.is_like_msvc
+                && flavor == LinkerFlavor::Msvc(Lld::No)
+                // Match exactly "link.exe"
+                && linker_path.to_str() == Some("link.exe");
+
             if !prog.status.success() {
                 let mut output = prog.stderr.clone();
                 output.extend_from_slice(&prog.stdout);
@@ -1008,16 +1013,9 @@ fn link_natively(
                 // is not a Microsoft LNK error then suggest a way to fix or
                 // install the Visual Studio build tools.
                 if let Some(code) = prog.status.code() {
-                    if sess.target.is_like_msvc
-                        && flavor == LinkerFlavor::Msvc(Lld::No)
-                        // Respect the command line override
-                        && sess.opts.cg.linker.is_none()
-                        // Match exactly "link.exe"
-                        && linker_path.to_str() == Some("link.exe")
-                        // All Microsoft `link.exe` linking error codes are
-                        // four digit numbers in the range 1000 to 9999 inclusive
-                        && (code < 1000 || code > 9999)
-                    {
+                    // All Microsoft `link.exe` linking ror codes are
+                    // four digit numbers in the range 1000 to 9999 inclusive
+                    if is_msvc_link_exe && (code < 1000 || code > 9999) {
                         let is_vs_installed = windows_registry::find_vs_version().is_ok();
                         let has_linker =
                             windows_registry::find_tool(&sess.target.arch, "link.exe").is_some();
@@ -1041,10 +1039,29 @@ fn link_natively(
             }
 
             let stderr = escape_string(&prog.stderr);
-            let stdout = escape_string(&prog.stdout);
+            let mut stdout = escape_string(&prog.stdout);
             info!("linker stderr:\n{}", &stderr);
             info!("linker stdout:\n{}", &stdout);
 
+            // Hide some progress messages from link.exe that we don't care about.
+            // See https://github.com/chromium/chromium/blob/bfa41e41145ffc85f041384280caf2949bb7bd72/build/toolchain/win/tool_wrapper.py#L144-L146
+            if is_msvc_link_exe {
+                if let Ok(str) = str::from_utf8(&prog.stdout) {
+                    let mut output = String::with_capacity(str.len());
+                    for line in stdout.lines() {
+                        if line.starts_with("   Creating library")
+                            || line.starts_with("Generating code")
+                            || line.starts_with("Finished generating code")
+                        {
+                            continue;
+                        }
+                        output += line;
+                        output += "\r\n"
+                    }
+                    stdout = escape_string(output.trim().as_bytes())
+                }
+            }
+
             let (level, src) = codegen_results.crate_info.lint_levels.linker_messages;
             let lint = |msg| {
                 lint_level(sess, LINKER_MESSAGES, level, src, None, |diag| {
@@ -1060,7 +1077,7 @@ fn link_natively(
                     .replace(": warning: ", ": ");
                 lint(format!("linker stderr: {stderr}"));
             }
-            if !prog.stdout.is_empty() {
+            if !stdout.is_empty() {
                 lint(format!("linker stdout: {}", stdout))
             }
         }

From 7407c1268334a01142d1770dd5080ffeaa8eaa1a Mon Sep 17 00:00:00 2001
From: jyn <github@jyn.dev>
Date: Mon, 20 Jan 2025 16:44:24 -0500
Subject: [PATCH 8/9] Ignore linker warnings on macOS for ui-fulldeps

ld is showing things like this:
```
ld: ignoring duplicate libraries: '-lm'
```

I don't have time or a macbook that lets me investigate these. Just silence them for now.
---
 src/bootstrap/src/core/build_steps/test.rs | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index 38e056cbc14ea..95fbf76b79b7c 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -1862,12 +1862,19 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
         let mut hostflags = flags.clone();
         hostflags.push(format!("-Lnative={}", builder.test_helpers_out(compiler.host).display()));
         hostflags.extend(linker_flags(builder, compiler.host, LldThreads::No));
-        for flag in hostflags {
-            cmd.arg("--host-rustcflags").arg(flag);
-        }
 
         let mut targetflags = flags;
         targetflags.push(format!("-Lnative={}", builder.test_helpers_out(target).display()));
+
+        // FIXME: on macOS, we get linker warnings about duplicate `-lm` flags. We should investigate why this happens.
+        if suite == "ui-fulldeps" && target.ends_with("darwin") {
+            hostflags.push("-Alinker_messages".into());
+            targetflags.push("-Alinker_messages".into());
+        }
+
+        for flag in hostflags {
+            cmd.arg("--host-rustcflags").arg(flag);
+        }
         for flag in targetflags {
             cmd.arg("--target-rustcflags").arg(flag);
         }

From 8b92619ce6b1c0ac41cb7fa97d6c550ee0b6785d Mon Sep 17 00:00:00 2001
From: jyn <github@jyn.dev>
Date: Mon, 20 Jan 2025 19:09:20 -0500
Subject: [PATCH 9/9] Allow LLD version info to be either stderr or stdout

---
 tests/run-make/rust-lld/rmake.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/run-make/rust-lld/rmake.rs b/tests/run-make/rust-lld/rmake.rs
index 1f098fdb1d910..76b15ab1799b9 100644
--- a/tests/run-make/rust-lld/rmake.rs
+++ b/tests/run-make/rust-lld/rmake.rs
@@ -55,6 +55,6 @@ fn main() {
 
 fn find_lld_version_in_logs(stderr: String) -> bool {
     let lld_version_re =
-        Regex::new(r"^warning: linker stdout: LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
+        Regex::new(r"^warning: linker std(out|err): LLD [0-9]+\.[0-9]+\.[0-9]+").unwrap();
     stderr.lines().any(|line| lld_version_re.is_match(line.trim()))
 }