diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index cb22aac4e952d..71a3a2c973bbe 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -47,7 +47,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, }; @@ -2290,6 +2290,81 @@ fn add_rpath_args( } } +fn undecorate_c_symbol(name: &str, sess: &Session, kind: SymbolExportKind) -> String { + if sess.target.is_like_darwin { + // 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| s.to_string()).unwrap_or(name.to_string()) + } else if sess.target.is_like_windows { + // 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 name.to_string(); + } + 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.split_once('@') + .map(|(base, suffix)| { + if suffix.parse::().is_ok() { + base.to_string() + } else { + name.to_string() + } + }) + .unwrap_or(name.to_string()) + } else if let Some(stripped) = name.strip_prefix('_') { + if let Some((base, suffix)) = stripped.split_once('@') { + // stdcall: _foo@N -> foo + if suffix.parse::().is_ok() { + base.to_string() + } else { + stripped.to_string() + } + } else { + // cdecl: _foo -> foo + stripped.to_string() + } + } else { + // vectorcall: foo@@N -> foo + name.split_once("@@") + .map(|(base, suffix)| { + if suffix.parse::().is_ok() { + base.to_string() + } else { + name.to_string() + } + }) + .unwrap_or(name.to_string()) + } + } + Arch::X86_64 => { + // COFF 64-bit: vectorcall mangling (foo@@N -> foo) also applies on x86_64. + name.split_once("@@") + .map(|(base, suffix)| { + if suffix.parse::().is_ok() { + base.to_string() + } else { + name.to_string() + } + }) + .unwrap_or(name.to_string()) + } + Arch::Arm64EC if kind == SymbolExportKind::Text => { + // Arm64EC: only text symbols have '#' prefix decoration + name.strip_prefix('#').map(|s| s.to_string()).unwrap_or(name.to_string()) + } + _ => name.to_string(), + } + } else { + // ELF: no decoration + name.to_string() + } +} + fn add_c_staticlib_symbols( sess: &Session, lib: &NativeLib, @@ -2331,7 +2406,13 @@ 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 on Windows. + let scope = symbol.scope(); + if scope != object::SymbolScope::Dynamic + && !(sess.target.is_like_windows && scope == object::SymbolScope::Linkage) + { continue; } @@ -2346,9 +2427,8 @@ 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 undecorated = undecorate_c_symbol(name, sess, export_kind); + out.push((undecorated, 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..602f1c0da5ff1 100644 --- a/tests/run-make/cdylib-export-c-library-symbols/rmake.rs +++ b/tests/run-make/cdylib-export-c-library-symbols/rmake.rs @@ -1,13 +1,12 @@ //@ 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::{ + build_native_static_lib, cc, dynamic_lib_name, is_darwin, is_windows, llvm_nm, llvm_readobj, + rfs, rustc, +}; fn main() { cc().input("foo.c").arg("-c").out_exe("foo.o").run(); @@ -15,20 +14,35 @@ fn main() { 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");