diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index c527dd95e2db4..564dc9300cef4 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -1,5 +1,6 @@ mod raw_dylib; +use std::borrow::Cow; use std::collections::BTreeSet; use std::ffi::OsString; use std::fs::{File, OpenOptions, read}; @@ -47,7 +48,7 @@ use rustc_session::{Session, filesearch}; use rustc_span::Symbol; use rustc_target::spec::crt_objects::CrtObjects; use rustc_target::spec::{ - BinaryFormat, Cc, CfgAbi, Env, LinkOutputKind, LinkSelfContainedComponents, + Arch, BinaryFormat, Cc, CfgAbi, Env, LinkOutputKind, LinkSelfContainedComponents, LinkSelfContainedDefault, LinkerFeatures, LinkerFlavor, LinkerFlavorCli, Lld, Os, RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, }; @@ -2346,6 +2347,79 @@ fn add_rpath_args( } } +fn strip_numeric_suffix<'a>( + base: &'a str, + suffix: impl AsRef, + fallback: &'a str, +) -> Cow<'a, str> { + if suffix.as_ref().parse::().is_ok() { + Cow::Owned(base.to_string()) + } else { + Cow::Borrowed(fallback) + } +} + +fn undecorate_c_symbol<'a>( + name: &'a str, + sess: &Session, + kind: SymbolExportKind, +) -> Option> { + match sess.target.binary_format { + BinaryFormat::MachO => { + // Mach-O: strip the leading underscore that all external symbols have. + // The Darwin linker's export_symbols will add it back. + name.strip_prefix('_').map(|s| Cow::Owned(s.to_string())) + } + BinaryFormat::Coff => { + // MSVC C++ mangled names start with '?' and use a completely different + // decorating scheme that includes '@@' as structural delimiters. + // They must not be subjected to C calling-convention undecoration. + if name.starts_with('?') { + return Some(Cow::Borrowed(name)); + } + Some(match sess.target.arch { + Arch::X86 => { + // COFF 32-bit: strip calling-convention decorations. + if let Some(rest) = name.strip_prefix('@') { + // fastcall: @foo@N -> foo + rest.rsplit_once('@') + .map(|(base, suffix)| strip_numeric_suffix(base, suffix, name)) + .unwrap_or(Cow::Borrowed(name)) + } else if let Some(stripped) = name.strip_prefix('_') { + if let Some((base, suffix)) = stripped.rsplit_once('@') { + // stdcall: _foo@N -> foo + strip_numeric_suffix(base, suffix, stripped) + } else { + // cdecl: _foo -> foo + Cow::Owned(stripped.to_string()) + } + } else { + // vectorcall: foo@@N -> foo + name.rsplit_once("@@") + .map(|(base, suffix)| strip_numeric_suffix(base, suffix, name)) + .unwrap_or(Cow::Borrowed(name)) + } + } + Arch::X86_64 => { + // COFF 64-bit: vectorcall mangling (foo@@N -> foo) also applies on x86_64. + name.rsplit_once("@@") + .map(|(base, suffix)| strip_numeric_suffix(base, suffix, name)) + .unwrap_or(Cow::Borrowed(name)) + } + Arch::Arm64EC if kind == SymbolExportKind::Text => { + // Arm64EC: `#` prefix distinguishes ARM64EC text symbols from x64 thunks. + name.strip_prefix('#') + .map(|s| Cow::Owned(s.to_string())) + .unwrap_or(Cow::Borrowed(name)) + } + _ => Cow::Borrowed(name), + }) + } + // ELF: no decoration + _ => Some(Cow::Borrowed(name)), + } +} + fn add_c_staticlib_symbols( sess: &Session, lib: &NativeLib, @@ -2387,7 +2461,14 @@ fn add_c_staticlib_symbols( } for symbol in object.symbols() { - if symbol.scope() != object::SymbolScope::Dynamic { + // The `object` crate returns `Dynamic` for ELF/Mach-O global symbols, + // but always returns `Linkage` for COFF external symbols. + // Accept both for COFF (Windows and UEFI). + let scope = symbol.scope(); + if scope != object::SymbolScope::Dynamic + && !(sess.target.binary_format == BinaryFormat::Coff + && scope == object::SymbolScope::Linkage) + { continue; } @@ -2402,9 +2483,10 @@ fn add_c_staticlib_symbols( _ => continue, }; - // FIXME:The symbol mangle rules are slightly different in Windows(32-bit) and Apple. - // Need to be resolved. - out.push((name.to_string(), export_kind)); + let Some(undecorated) = undecorate_c_symbol(name, sess, export_kind) else { + continue; + }; + out.push((undecorated.into_owned(), export_kind)); } } diff --git a/tests/run-make/cdylib-export-c-library-symbols/rmake.rs b/tests/run-make/cdylib-export-c-library-symbols/rmake.rs index cb237eceedadf..a2c0796da582e 100644 --- a/tests/run-make/cdylib-export-c-library-symbols/rmake.rs +++ b/tests/run-make/cdylib-export-c-library-symbols/rmake.rs @@ -1,34 +1,48 @@ //@ ignore-nvptx64 //@ ignore-wasm //@ ignore-cross-compile -// FIXME:The symbol mangle rules are slightly different in Windows(32-bit) and Apple. -// Need to be resolved. -//@ ignore-windows -//@ ignore-apple // Reason: the compiled binary is executed -use run_make_support::{build_native_static_lib, cc, dynamic_lib_name, is_darwin, llvm_nm, rustc}; +use run_make_support::{ + cc, dynamic_lib_name, is_darwin, is_windows, llvm_ar, llvm_nm, llvm_readobj, rfs, rustc, + static_lib_name, +}; fn main() { - cc().input("foo.c").arg("-c").out_exe("foo.o").run(); - build_native_static_lib("foo"); + cc().input("foo.c").arg("-c").arg("-fno-lto").out_exe("foo.o").run(); + llvm_ar().obj_to_ar().output_input(&static_lib_name("foo"), "foo.o").run(); rustc().input("foo.rs").arg("-lstatic=foo").crate_type("cdylib").run(); - let out = llvm_nm() - .input(dynamic_lib_name("foo")) - .run() - .assert_stdout_not_contains_regex("T *my_function"); + if is_darwin() { + llvm_nm().input(dynamic_lib_name("foo")).run().assert_stdout_not_contains("T _my_function"); + } else if is_windows() { + llvm_readobj() + .arg("--coff-exports") + .input(dynamic_lib_name("foo")) + .run() + .assert_stdout_not_contains("my_function"); + } else { + llvm_nm().input(dynamic_lib_name("foo")).run().assert_stdout_not_contains("T my_function"); + } + + rfs::remove_file(dynamic_lib_name("foo")); rustc().input("foo_export.rs").arg("-lstatic:+export-symbols=foo").crate_type("cdylib").run(); if is_darwin() { - let out = llvm_nm() + llvm_nm() .input(dynamic_lib_name("foo_export")) .run() .assert_stdout_contains("T _my_function"); + } else if is_windows() { + llvm_readobj() + .arg("--coff-exports") + .input(dynamic_lib_name("foo_export")) + .run() + .assert_stdout_contains("my_function"); } else { - let out = llvm_nm() + llvm_nm() .input(dynamic_lib_name("foo_export")) .run() .assert_stdout_contains("T my_function");