From 689bb118ce3f24da8fa2ce20a0c34e4d76097a26 Mon Sep 17 00:00:00 2001
From: Oneirical <manchot@videotron.ca>
Date: Thu, 27 Jun 2024 16:32:54 -0400
Subject: [PATCH 1/3] rewrite symbol-visibility to rmake

---
 src/tools/compiletest/src/command-list.rs     |   1 +
 src/tools/run-make-support/src/command.rs     |   2 +
 .../tidy/src/allowed_run_make_makefiles.txt   |   1 -
 tests/run-make/symbol-visibility/Makefile     | 123 -------------
 tests/run-make/symbol-visibility/rmake.rs     | 168 ++++++++++++++++++
 5 files changed, 171 insertions(+), 124 deletions(-)
 delete mode 100644 tests/run-make/symbol-visibility/Makefile
 create mode 100644 tests/run-make/symbol-visibility/rmake.rs

diff --git a/src/tools/compiletest/src/command-list.rs b/src/tools/compiletest/src/command-list.rs
index c356f4266f016..288f90ea12399 100644
--- a/src/tools/compiletest/src/command-list.rs
+++ b/src/tools/compiletest/src/command-list.rs
@@ -117,6 +117,7 @@ const KNOWN_DIRECTIVE_NAMES: &[&str] = &[
     "ignore-watchos",
     "ignore-windows",
     "ignore-windows-gnu",
+    "ignore-windows-msvc",
     "ignore-x32",
     "ignore-x86",
     "ignore-x86_64",
diff --git a/src/tools/run-make-support/src/command.rs b/src/tools/run-make-support/src/command.rs
index fb94ff996f0d6..86d2e4a852a30 100644
--- a/src/tools/run-make-support/src/command.rs
+++ b/src/tools/run-make-support/src/command.rs
@@ -163,11 +163,13 @@ pub struct CompletedProcess {
 
 impl CompletedProcess {
     #[must_use]
+    #[track_caller]
     pub fn stdout_utf8(&self) -> String {
         String::from_utf8(self.output.stdout.clone()).expect("stdout is not valid UTF-8")
     }
 
     #[must_use]
+    #[track_caller]
     pub fn stderr_utf8(&self) -> String {
         String::from_utf8(self.output.stderr.clone()).expect("stderr is not valid UTF-8")
     }
diff --git a/src/tools/tidy/src/allowed_run_make_makefiles.txt b/src/tools/tidy/src/allowed_run_make_makefiles.txt
index a84b89ff4a113..6bb9ba0428eca 100644
--- a/src/tools/tidy/src/allowed_run_make_makefiles.txt
+++ b/src/tools/tidy/src/allowed_run_make_makefiles.txt
@@ -52,7 +52,6 @@ run-make/split-debuginfo/Makefile
 run-make/stable-symbol-names/Makefile
 run-make/staticlib-dylib-linkage/Makefile
 run-make/symbol-mangling-hashed/Makefile
-run-make/symbol-visibility/Makefile
 run-make/sysroot-crates-are-unstable/Makefile
 run-make/thumb-none-cortex-m/Makefile
 run-make/thumb-none-qemu/Makefile
diff --git a/tests/run-make/symbol-visibility/Makefile b/tests/run-make/symbol-visibility/Makefile
deleted file mode 100644
index 9159af214ca7b..0000000000000
--- a/tests/run-make/symbol-visibility/Makefile
+++ /dev/null
@@ -1,123 +0,0 @@
-# ignore-cross-compile
-include ../tools.mk
-
-# ignore-windows-msvc
-
-NM=nm -D
-CDYLIB_NAME=liba_cdylib.so
-RDYLIB_NAME=liba_rust_dylib.so
-PROC_MACRO_NAME=liba_proc_macro.so
-EXE_NAME=an_executable
-COMBINED_CDYLIB_NAME=libcombined_rlib_dylib.so
-
-ifeq ($(UNAME),Darwin)
-NM=nm -gU
-CDYLIB_NAME=liba_cdylib.dylib
-RDYLIB_NAME=liba_rust_dylib.dylib
-PROC_MACRO_NAME=liba_proc_macro.dylib
-EXE_NAME=an_executable
-COMBINED_CDYLIB_NAME=libcombined_rlib_dylib.dylib
-endif
-
-ifdef IS_WINDOWS
-NM=nm -g
-CDYLIB_NAME=liba_cdylib.dll.a
-RDYLIB_NAME=liba_rust_dylib.dll.a
-PROC_MACRO_NAME=liba_proc_macro.dll
-EXE_NAME=an_executable.exe
-COMBINED_CDYLIB_NAME=libcombined_rlib_dylib.dll.a
-endif
-
-# `grep` regex for symbols produced by either `legacy` or `v0` mangling
-RE_ANY_RUST_SYMBOL="_ZN.*h.*E\|_R[a-zA-Z0-9_]+"
-
-all:
-	$(RUSTC) -Zshare-generics=no an_rlib.rs
-	$(RUSTC) -Zshare-generics=no a_cdylib.rs
-	$(RUSTC) -Zshare-generics=no a_rust_dylib.rs
-	$(RUSTC) -Zshare-generics=no a_proc_macro.rs
-	$(RUSTC) -Zshare-generics=no an_executable.rs
-	$(RUSTC) -Zshare-generics=no a_cdylib.rs --crate-name combined_rlib_dylib --crate-type=rlib,cdylib
-
-	# Check that a cdylib exports its public #[no_mangle] functions
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_cdylib)" -eq "1" ]
-	# Check that a cdylib exports the public #[no_mangle] functions of dependencies
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_rlib)" -eq "1" ]
-	# Check that a cdylib DOES NOT export any public Rust functions
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c $(RE_ANY_RUST_SYMBOL))" -eq "0" ]
-
-	# Check that a Rust dylib exports its monomorphic functions
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_rust_dylib)" -eq "1" ]
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_rust_function_from_rust_dylib)" -eq "1" ]
-	# Check that a Rust dylib does not export generics if -Zshare-generics=no
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_generic_function_from_rust_dylib)" -eq "0" ]
-
-
-	# Check that a Rust dylib exports the monomorphic functions from its dependencies
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_rlib)" -eq "1" ]
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_rust_function_from_rlib)" -eq "1" ]
-	# Check that a Rust dylib does not export generics if -Zshare-generics=no
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_generic_function_from_rlib)" -eq "0" ]
-
-	# Check that a proc macro exports its public #[no_mangle] functions
-	# FIXME(#99978) avoid exporting #[no_mangle] symbols for proc macros
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_cdylib)" -eq "1" ]
-	# Check that a proc macro exports the public #[no_mangle] functions of dependencies
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_rlib)" -eq "1" ]
-	# Check that a proc macro DOES NOT export any public Rust functions
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c $(RE_ANY_RUST_SYMBOL))" -eq "0" ]
-
-# FIXME(nbdd0121): This is broken in MinGW, see https://github.com/rust-lang/rust/pull/95604#issuecomment-1101564032
-ifndef IS_WINDOWS
-	# Check that an executable does not export any dynamic symbols
-	[ "$$($(NM) $(TMPDIR)/$(EXE_NAME) | grep -v __imp_ | grep -c public_c_function_from_rlib)" -eq "0" ]
-	[ "$$($(NM) $(TMPDIR)/$(EXE_NAME) | grep -v __imp_ | grep -c public_rust_function_from_exe)" -eq "0" ]
-endif
-
-
-	# Check the combined case, where we generate a cdylib and an rlib in the same
-	# compilation session:
-	# Check that a cdylib exports its public #[no_mangle] functions
-	[ "$$($(NM) $(TMPDIR)/$(COMBINED_CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_cdylib)" -eq "1" ]
-	# Check that a cdylib exports the public #[no_mangle] functions of dependencies
-	[ "$$($(NM) $(TMPDIR)/$(COMBINED_CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_rlib)" -eq "1" ]
-	# Check that a cdylib DOES NOT export any public Rust functions
-	[ "$$($(NM) $(TMPDIR)/$(COMBINED_CDYLIB_NAME) | grep -v __imp_ | grep -c $(RE_ANY_RUST_SYMBOL))" -eq "0" ]
-
-
-	$(RUSTC) -Zshare-generics=yes an_rlib.rs
-	$(RUSTC) -Zshare-generics=yes a_cdylib.rs
-	$(RUSTC) -Zshare-generics=yes a_rust_dylib.rs
-	$(RUSTC) -Zshare-generics=yes a_proc_macro.rs
-	$(RUSTC) -Zshare-generics=yes an_executable.rs
-
-	# Check that a cdylib exports its public #[no_mangle] functions
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_cdylib)" -eq "1" ]
-	# Check that a cdylib exports the public #[no_mangle] functions of dependencies
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_rlib)" -eq "1" ]
-	# Check that a cdylib DOES NOT export any public Rust functions
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c $(RE_ANY_RUST_SYMBOL))" -eq "0" ]
-
-	# Check that a Rust dylib exports its monomorphic functions, including generics this time
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_rust_dylib)" -eq "1" ]
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_rust_function_from_rust_dylib)" -eq "1" ]
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_generic_function_from_rust_dylib)" -eq "1" ]
-
-	# Check that a Rust dylib exports the monomorphic functions from its dependencies
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_rlib)" -eq "1" ]
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_rust_function_from_rlib)" -eq "1" ]
-	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -v __imp_ | grep -c public_generic_function_from_rlib)" -eq "1" ]
-
-	# Check that a proc macro exports its public #[no_mangle] functions
-	# FIXME(#99978) avoid exporting #[no_mangle] symbols for proc macros
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_cdylib)" -eq "1" ]
-	# Check that a proc macro exports the public #[no_mangle] functions of dependencies
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c public_c_function_from_rlib)" -eq "1" ]
-	# Check that a proc macro DOES NOT export any public Rust functions
-	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -v __imp_ | grep -c $(RE_ANY_RUST_SYMBOL))" -eq "0" ]
-
-ifndef IS_WINDOWS
-	# Check that an executable does not export any dynamic symbols
-	[ "$$($(NM) $(TMPDIR)/$(EXE_NAME) | grep -v __imp_ | grep -c public_c_function_from_rlib)" -eq "0" ]
-	[ "$$($(NM) $(TMPDIR)/$(EXE_NAME) | grep -v __imp_ | grep -c public_rust_function_from_exe)" -eq "0" ]
-endif
diff --git a/tests/run-make/symbol-visibility/rmake.rs b/tests/run-make/symbol-visibility/rmake.rs
new file mode 100644
index 0000000000000..5fff89e071e11
--- /dev/null
+++ b/tests/run-make/symbol-visibility/rmake.rs
@@ -0,0 +1,168 @@
+// Dynamic libraries on Rust used to export a very high amount of symbols,
+// going as far as filling the output with mangled names and generic function
+// names. After the rework of #38117, this test checks that no mangled Rust symbols
+// are exported, and that generics are only shown if explicitely requested.
+// See https://github.com/rust-lang/rust/issues/37530
+
+//@ ignore-windows-msvc
+
+use run_make_support::{bin_name, dynamic_lib_name, is_windows, llvm_readobj, regex, rustc};
+
+fn main() {
+    let mut cdylib_name = dynamic_lib_name("a_cdylib");
+    let mut rdylib_name = dynamic_lib_name("a_rust_dylib");
+    let exe_name = bin_name("an_executable");
+    let mut combined_cdylib_name = dynamic_lib_name("combined_rlib_dylib");
+    rustc().arg("-Zshare-generics=no").input("an_rlib.rs").run();
+    rustc().arg("-Zshare-generics=no").input("a_cdylib.rs").run();
+    rustc().arg("-Zshare-generics=no").input("a_rust_dylib.rs").run();
+    rustc().arg("-Zshare-generics=no").input("a_proc_macro.rs").run();
+    rustc().arg("-Zshare-generics=no").input("an_executable.rs").run();
+    rustc()
+        .arg("-Zshare-generics=no")
+        .input("a_cdylib.rs")
+        .crate_name("combined_rlib_dylib")
+        .crate_type("rlib,cdylib")
+        .run();
+
+    // Check that a cdylib exports its public #[no_mangle] functions
+    symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_cdylib"), true);
+    // Check that a cdylib exports the public #[no_mangle] functions of dependencies
+    symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib"), true);
+    // Check that a cdylib DOES NOT export any public Rust functions
+    symbols_check(&cdylib_name, SymbolCheckType::AnyRustSymbol, false);
+
+    // Check that a Rust dylib exports its monomorphic functions
+    symbols_check(
+        &rdylib_name,
+        SymbolCheckType::StrSymbol("public_c_function_from_rust_dylib"),
+        true,
+    );
+    symbols_check(
+        &rdylib_name,
+        SymbolCheckType::StrSymbol("public_rust_function_from_rust_dylib"),
+        true,
+    );
+    // Check that a Rust dylib does not export generics if -Zshare-generics=no
+    symbols_check(
+        &rdylib_name,
+        SymbolCheckType::StrSymbol("public_generic_function_from_rust_dylib"),
+        false,
+    );
+
+    // Check that a Rust dylib exports the monomorphic functions from its dependencies
+    symbols_check(&rdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib"), true);
+    symbols_check(&rdylib_name, SymbolCheckType::StrSymbol("public_rust_function_from_rlib"), true);
+    // Check that a Rust dylib does not export generics if -Zshare-generics=no
+    symbols_check(
+        &rdylib_name,
+        SymbolCheckType::StrSymbol("public_generic_function_from_rlib"),
+        false,
+    );
+
+    // FIXME(nbdd0121): This is broken in MinGW, see https://github.com/rust-lang/rust/pull/95604#issuecomment-1101564032
+    // if is_windows() {
+    //     // Check that an executable does not export any dynamic symbols
+    //     symbols_check(&exe_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib")
+    //, false);
+    //     symbols_check(
+    //         &exe_name,
+    //         SymbolCheckType::StrSymbol("public_rust_function_from_exe"),
+    //         false,
+    //     );
+    // }
+
+    // Check the combined case, where we generate a cdylib and an rlib in the same
+    // compilation session:
+    // Check that a cdylib exports its public //[no_mangle] functions
+    symbols_check(
+        &combined_cdylib_name,
+        SymbolCheckType::StrSymbol("public_c_function_from_cdylib"),
+        true,
+    );
+    // Check that a cdylib exports the public //[no_mangle] functions of dependencies
+    symbols_check(
+        &combined_cdylib_name,
+        SymbolCheckType::StrSymbol("public_c_function_from_rlib"),
+        true,
+    );
+    // Check that a cdylib DOES NOT export any public Rust functions
+    symbols_check(&combined_cdylib_name, SymbolCheckType::AnyRustSymbol, false);
+
+    rustc().arg("-Zshare-generics=yes").input("an_rlib.rs").run();
+    rustc().arg("-Zshare-generics=yes").input("a_cdylib.rs").run();
+    rustc().arg("-Zshare-generics=yes").input("a_rust_dylib.rs").run();
+    rustc().arg("-Zshare-generics=yes").input("an_executable.rs").run();
+
+    // Check that a cdylib exports its public //[no_mangle] functions
+    symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_cdylib"), true);
+    // Check that a cdylib exports the public //[no_mangle] functions of dependencies
+    symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib"), true);
+    // Check that a cdylib DOES NOT export any public Rust functions
+    symbols_check(&cdylib_name, SymbolCheckType::AnyRustSymbol, false);
+
+    // Check that a Rust dylib exports its monomorphic functions, including generics this time
+    symbols_check(
+        &rdylib_name,
+        SymbolCheckType::StrSymbol("public_c_function_from_rust_dylib"),
+        true,
+    );
+    symbols_check(
+        &rdylib_name,
+        SymbolCheckType::StrSymbol("public_rust_function_from_rust_dylib"),
+        true,
+    );
+    symbols_check(
+        &rdylib_name,
+        SymbolCheckType::StrSymbol("public_generic_function_from_rust_dylib"),
+        true,
+    );
+
+    // Check that a Rust dylib exports the monomorphic functions from its dependencies
+    symbols_check(&rdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib"), true);
+    symbols_check(&rdylib_name, SymbolCheckType::StrSymbol("public_rust_function_from_rlib"), true);
+    symbols_check(
+        &rdylib_name,
+        SymbolCheckType::StrSymbol("public_generic_function_from_rlib"),
+        true,
+    );
+
+    // FIXME(nbdd0121): This is broken in MinGW, see https://github.com/rust-lang/rust/pull/95604#issuecomment-1101564032
+    // if is_windows() {
+    //     // Check that an executable does not export any dynamic symbols
+    //     symbols_check(&exe_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib")
+    //, false);
+    //     symbols_check(
+    //         &exe_name,
+    //         SymbolCheckType::StrSymbol("public_rust_function_from_exe"),
+    //         false,
+    //     );
+    // }
+}
+
+#[track_caller]
+fn symbols_check(path: &str, symbol_check_type: SymbolCheckType, exists_once: bool) {
+    let out = llvm_readobj().arg("--dyn-symbols").input(path).run().stdout_utf8();
+    assert_eq!(
+        out.lines()
+            .filter(|&line| !line.contains("__imp_") && has_symbol(line, symbol_check_type))
+            .count()
+            == 1,
+        exists_once
+    );
+}
+
+fn has_symbol(line: &str, symbol_check_type: SymbolCheckType) -> bool {
+    if let SymbolCheckType::StrSymbol(expected) = symbol_check_type {
+        line.contains(expected)
+    } else {
+        let regex = regex::Regex::new(r#"_ZN.*h.*E\|_R[a-zA-Z0-9_]+"#).unwrap();
+        regex.is_match(line)
+    }
+}
+
+#[derive(Clone, Copy)]
+enum SymbolCheckType {
+    StrSymbol(&'static str),
+    AnyRustSymbol,
+}

From dcaa17a661d5101c7ee79703f0c4a14e8ddcb5f5 Mon Sep 17 00:00:00 2001
From: Oneirical <manchot@videotron.ca>
Date: Mon, 15 Jul 2024 11:52:37 -0400
Subject: [PATCH 2/3] invalid stdout_utf8 handling in run_make_support

---
 src/tools/run-make-support/src/command.rs | 6 ++++++
 tests/run-make/symbol-visibility/rmake.rs | 2 +-
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/tools/run-make-support/src/command.rs b/src/tools/run-make-support/src/command.rs
index 86d2e4a852a30..532e3b8e048a0 100644
--- a/src/tools/run-make-support/src/command.rs
+++ b/src/tools/run-make-support/src/command.rs
@@ -168,6 +168,12 @@ impl CompletedProcess {
         String::from_utf8(self.output.stdout.clone()).expect("stdout is not valid UTF-8")
     }
 
+    #[must_use]
+    #[track_caller]
+    pub fn invalid_stdout_utf8(&self) -> String {
+        String::from_utf8_lossy(&self.output.stdout.clone()).to_string()
+    }
+
     #[must_use]
     #[track_caller]
     pub fn stderr_utf8(&self) -> String {
diff --git a/tests/run-make/symbol-visibility/rmake.rs b/tests/run-make/symbol-visibility/rmake.rs
index 5fff89e071e11..cd085cf05d0ed 100644
--- a/tests/run-make/symbol-visibility/rmake.rs
+++ b/tests/run-make/symbol-visibility/rmake.rs
@@ -142,7 +142,7 @@ fn main() {
 
 #[track_caller]
 fn symbols_check(path: &str, symbol_check_type: SymbolCheckType, exists_once: bool) {
-    let out = llvm_readobj().arg("--dyn-symbols").input(path).run().stdout_utf8();
+    let out = llvm_readobj().arg("--dyn-symbols").input(path).run().invalid_stdout_utf8();
     assert_eq!(
         out.lines()
             .filter(|&line| !line.contains("__imp_") && has_symbol(line, symbol_check_type))

From ea04b0afbf3e5346c2df015b9c59ff1b0b3bc270 Mon Sep 17 00:00:00 2001
From: Oneirical <manchot@videotron.ca>
Date: Tue, 30 Jul 2024 13:07:49 -0400
Subject: [PATCH 3/3] use llvm-nm in symbol-visibility rmake test

---
 .../src/external_deps/llvm.rs                 | 30 +++++++++++++++++++
 src/tools/run-make-support/src/lib.rs         |  4 +--
 tests/run-make/symbol-visibility/rmake.rs     | 29 ++++++++++++------
 3 files changed, 52 insertions(+), 11 deletions(-)

diff --git a/src/tools/run-make-support/src/external_deps/llvm.rs b/src/tools/run-make-support/src/external_deps/llvm.rs
index b116bd08e3a6c..259bb6159461b 100644
--- a/src/tools/run-make-support/src/external_deps/llvm.rs
+++ b/src/tools/run-make-support/src/external_deps/llvm.rs
@@ -36,6 +36,12 @@ pub fn llvm_ar() -> LlvmAr {
     LlvmAr::new()
 }
 
+/// Construct a new `llvm-nm` invocation. This assumes that `llvm-nm` is available
+/// at `$LLVM_BIN_DIR/llvm-nm`.
+pub fn llvm_nm() -> LlvmNm {
+    LlvmNm::new()
+}
+
 /// A `llvm-readobj` invocation builder.
 #[derive(Debug)]
 #[must_use]
@@ -71,11 +77,19 @@ pub struct LlvmAr {
     cmd: Command,
 }
 
+/// A `llvm-nm` invocation builder.
+#[derive(Debug)]
+#[must_use]
+pub struct LlvmNm {
+    cmd: Command,
+}
+
 crate::macros::impl_common_helpers!(LlvmReadobj);
 crate::macros::impl_common_helpers!(LlvmProfdata);
 crate::macros::impl_common_helpers!(LlvmFilecheck);
 crate::macros::impl_common_helpers!(LlvmObjdump);
 crate::macros::impl_common_helpers!(LlvmAr);
+crate::macros::impl_common_helpers!(LlvmNm);
 
 /// Generate the path to the bin directory of LLVM.
 #[must_use]
@@ -244,3 +258,19 @@ impl LlvmAr {
         self
     }
 }
+
+impl LlvmNm {
+    /// Construct a new `llvm-nm` invocation. This assumes that `llvm-nm` is available
+    /// at `$LLVM_BIN_DIR/llvm-nm`.
+    pub fn new() -> Self {
+        let llvm_nm = llvm_bin_dir().join("llvm-nm");
+        let cmd = Command::new(llvm_nm);
+        Self { cmd }
+    }
+
+    /// Provide an input file.
+    pub fn input<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
+        self.cmd.arg(path.as_ref());
+        self
+    }
+}
diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs
index 085120764b463..9ced61932585c 100644
--- a/src/tools/run-make-support/src/lib.rs
+++ b/src/tools/run-make-support/src/lib.rs
@@ -48,8 +48,8 @@ pub use cc::{cc, cxx, extra_c_flags, extra_cxx_flags, Cc};
 pub use clang::{clang, Clang};
 pub use htmldocck::htmldocck;
 pub use llvm::{
-    llvm_ar, llvm_filecheck, llvm_objdump, llvm_profdata, llvm_readobj, LlvmAr, LlvmFilecheck,
-    LlvmObjdump, LlvmProfdata, LlvmReadobj,
+    llvm_ar, llvm_filecheck, llvm_nm, llvm_objdump, llvm_profdata, llvm_readobj, LlvmAr,
+    LlvmFilecheck, LlvmNm, LlvmObjdump, LlvmProfdata, LlvmReadobj,
 };
 pub use python::python_command;
 pub use rustc::{aux_build, bare_rustc, rustc, Rustc};
diff --git a/tests/run-make/symbol-visibility/rmake.rs b/tests/run-make/symbol-visibility/rmake.rs
index cd085cf05d0ed..b37ff44f4ead6 100644
--- a/tests/run-make/symbol-visibility/rmake.rs
+++ b/tests/run-make/symbol-visibility/rmake.rs
@@ -6,13 +6,16 @@
 
 //@ ignore-windows-msvc
 
-use run_make_support::{bin_name, dynamic_lib_name, is_windows, llvm_readobj, regex, rustc};
+//FIXME(Oneirical): This currently uses llvm-nm for symbol detection. However,
+// the custom Rust-based solution of #128314 may prove to be an interesting alternative.
+
+use run_make_support::{bin_name, dynamic_lib_name, is_darwin, is_windows, llvm_nm, regex, rustc};
 
 fn main() {
-    let mut cdylib_name = dynamic_lib_name("a_cdylib");
-    let mut rdylib_name = dynamic_lib_name("a_rust_dylib");
+    let cdylib_name = dynamic_lib_name("a_cdylib");
+    let rdylib_name = dynamic_lib_name("a_rust_dylib");
     let exe_name = bin_name("an_executable");
-    let mut combined_cdylib_name = dynamic_lib_name("combined_rlib_dylib");
+    let combined_cdylib_name = dynamic_lib_name("combined_rlib_dylib");
     rustc().arg("-Zshare-generics=no").input("an_rlib.rs").run();
     rustc().arg("-Zshare-generics=no").input("a_cdylib.rs").run();
     rustc().arg("-Zshare-generics=no").input("a_rust_dylib.rs").run();
@@ -74,13 +77,13 @@ fn main() {
 
     // Check the combined case, where we generate a cdylib and an rlib in the same
     // compilation session:
-    // Check that a cdylib exports its public //[no_mangle] functions
+    // Check that a cdylib exports its public #[no_mangle] functions
     symbols_check(
         &combined_cdylib_name,
         SymbolCheckType::StrSymbol("public_c_function_from_cdylib"),
         true,
     );
-    // Check that a cdylib exports the public //[no_mangle] functions of dependencies
+    // Check that a cdylib exports the public #[no_mangle] functions of dependencies
     symbols_check(
         &combined_cdylib_name,
         SymbolCheckType::StrSymbol("public_c_function_from_rlib"),
@@ -94,9 +97,9 @@ fn main() {
     rustc().arg("-Zshare-generics=yes").input("a_rust_dylib.rs").run();
     rustc().arg("-Zshare-generics=yes").input("an_executable.rs").run();
 
-    // Check that a cdylib exports its public //[no_mangle] functions
+    // Check that a cdylib exports its public #[no_mangle] functions
     symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_cdylib"), true);
-    // Check that a cdylib exports the public //[no_mangle] functions of dependencies
+    // Check that a cdylib exports the public #[no_mangle] functions of dependencies
     symbols_check(&cdylib_name, SymbolCheckType::StrSymbol("public_c_function_from_rlib"), true);
     // Check that a cdylib DOES NOT export any public Rust functions
     symbols_check(&cdylib_name, SymbolCheckType::AnyRustSymbol, false);
@@ -142,7 +145,15 @@ fn main() {
 
 #[track_caller]
 fn symbols_check(path: &str, symbol_check_type: SymbolCheckType, exists_once: bool) {
-    let out = llvm_readobj().arg("--dyn-symbols").input(path).run().invalid_stdout_utf8();
+    let mut nm = llvm_nm();
+    if is_windows() {
+        nm.arg("--extern-only");
+    } else if is_darwin() {
+        nm.arg("--extern-only").arg("--defined-only");
+    } else {
+        nm.arg("--dynamic");
+    }
+    let out = nm.input(path).run().stdout_utf8();
     assert_eq!(
         out.lines()
             .filter(|&line| !line.contains("__imp_") && has_symbol(line, symbol_check_type))