Skip to content

Commit 749740f

Browse files
Rollup merge of #155535 - cezarbbb:cstyle-export-symbols, r=bjorn3
export symbols: support macos/windows(32/64) Previously, in the pr #150992 , export symbols only supported Linux. The special prefix and suffix rules for some symbols in macOS and Windows were not fully supported. This pull request attempts to clarify these rules and add corresponding support. Currently, the `undecorate_c_symbol()` function has been added to handle macOS `_` prefixes, Windows x86 calling convention modifiers (cdecl/stdcall/fastcall/vectorcall), and Arm64EC `#` prefixes. *Update: Handling of Windows C++ mangled symbols has now been added(Linux/macOS don't need). r? @bjorn3 @petrochenkov
2 parents fa36a47 + ca10995 commit 749740f

2 files changed

Lines changed: 103 additions & 19 deletions

File tree

  • compiler/rustc_codegen_ssa/src/back
  • tests/run-make/cdylib-export-c-library-symbols

compiler/rustc_codegen_ssa/src/back/link.rs

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ use rustc_session::{Session, filesearch};
4747
use rustc_span::Symbol;
4848
use rustc_target::spec::crt_objects::CrtObjects;
4949
use rustc_target::spec::{
50-
BinaryFormat, Cc, CfgAbi, Env, LinkOutputKind, LinkSelfContainedComponents,
50+
Arch, BinaryFormat, Cc, CfgAbi, Env, LinkOutputKind, LinkSelfContainedComponents,
5151
LinkSelfContainedDefault, LinkerFeatures, LinkerFlavor, LinkerFlavorCli, Lld, Os, RelocModel,
5252
RelroLevel, SanitizerSet, SplitDebuginfo,
5353
};
@@ -2413,6 +2413,69 @@ fn add_rpath_args(
24132413
}
24142414
}
24152415

2416+
fn strip_numeric_suffix<'a>(base: &'a str, suffix: impl AsRef<str>, fallback: &'a str) -> &'a str {
2417+
if suffix.as_ref().parse::<u32>().is_ok() { base } else { fallback }
2418+
}
2419+
2420+
fn undecorate_c_symbol<'a>(
2421+
name: &'a str,
2422+
sess: &Session,
2423+
kind: SymbolExportKind,
2424+
) -> Option<&'a str> {
2425+
match sess.target.binary_format {
2426+
BinaryFormat::MachO => {
2427+
// Mach-O: strip the leading underscore that all external symbols have.
2428+
// The Darwin linker's export_symbols will add it back.
2429+
name.strip_prefix('_')
2430+
}
2431+
BinaryFormat::Coff => {
2432+
// MSVC C++ mangled names start with '?' and use a completely different
2433+
// decorating scheme that includes '@@' as structural delimiters.
2434+
// They must not be subjected to C calling-convention undecoration.
2435+
if name.starts_with('?') {
2436+
return Some(name);
2437+
}
2438+
Some(match sess.target.arch {
2439+
Arch::X86 => {
2440+
// COFF 32-bit: strip calling-convention decorations.
2441+
if let Some(rest) = name.strip_prefix('@') {
2442+
// fastcall: @foo@N -> foo
2443+
rest.rsplit_once('@')
2444+
.map(|(base, suffix)| strip_numeric_suffix(base, suffix, name))
2445+
.unwrap_or(name)
2446+
} else if let Some(stripped) = name.strip_prefix('_') {
2447+
if let Some((base, suffix)) = stripped.rsplit_once('@') {
2448+
// stdcall: _foo@N -> foo
2449+
strip_numeric_suffix(base, suffix, stripped)
2450+
} else {
2451+
// cdecl: _foo -> foo
2452+
stripped
2453+
}
2454+
} else {
2455+
// vectorcall: foo@@N -> foo
2456+
name.rsplit_once("@@")
2457+
.map(|(base, suffix)| strip_numeric_suffix(base, suffix, name))
2458+
.unwrap_or(name)
2459+
}
2460+
}
2461+
Arch::X86_64 => {
2462+
// COFF 64-bit: vectorcall mangling (foo@@N -> foo) also applies on x86_64.
2463+
name.rsplit_once("@@")
2464+
.map(|(base, suffix)| strip_numeric_suffix(base, suffix, name))
2465+
.unwrap_or(name)
2466+
}
2467+
Arch::Arm64EC if kind == SymbolExportKind::Text => {
2468+
// Arm64EC: `#` prefix distinguishes ARM64EC text symbols from x64 thunks.
2469+
name.strip_prefix('#').unwrap_or(name)
2470+
}
2471+
_ => name,
2472+
})
2473+
}
2474+
// ELF: no decoration
2475+
_ => Some(name),
2476+
}
2477+
}
2478+
24162479
fn add_c_staticlib_symbols(
24172480
sess: &Session,
24182481
lib: &NativeLib,
@@ -2454,7 +2517,14 @@ fn add_c_staticlib_symbols(
24542517
}
24552518

24562519
for symbol in object.symbols() {
2457-
if symbol.scope() != object::SymbolScope::Dynamic {
2520+
// The `object` crate returns `Dynamic` for ELF/Mach-O global symbols,
2521+
// but always returns `Linkage` for COFF external symbols.
2522+
// Accept both for COFF (Windows and UEFI).
2523+
let scope = symbol.scope();
2524+
if scope != object::SymbolScope::Dynamic
2525+
&& !(sess.target.binary_format == BinaryFormat::Coff
2526+
&& scope == object::SymbolScope::Linkage)
2527+
{
24582528
continue;
24592529
}
24602530

@@ -2469,9 +2539,10 @@ fn add_c_staticlib_symbols(
24692539
_ => continue,
24702540
};
24712541

2472-
// FIXME:The symbol mangle rules are slightly different in Windows(32-bit) and Apple.
2473-
// Need to be resolved.
2474-
out.push((name.to_string(), export_kind));
2542+
let Some(undecorated) = undecorate_c_symbol(name, sess, export_kind) else {
2543+
continue;
2544+
};
2545+
out.push((undecorated.to_string(), export_kind));
24752546
}
24762547
}
24772548

tests/run-make/cdylib-export-c-library-symbols/rmake.rs

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,54 @@
11
//@ ignore-nvptx64
22
//@ ignore-wasm
33
//@ ignore-cross-compile
4-
// FIXME:The symbol mangle rules are slightly different in Windows(32-bit) and Apple.
5-
// Need to be resolved.
6-
//@ ignore-windows
7-
//@ ignore-apple
84
// Reason: the compiled binary is executed
95

106
use run_make_support::{
11-
build_native_static_lib, cc, dynamic_lib_name, is_aix, is_darwin, llvm_nm, rustc,
7+
build_native_static_lib, cc, dynamic_lib_name, is_aix, is_darwin, is_windows, is_windows_msvc,
8+
llvm_ar, llvm_nm, llvm_readobj, rfs, rustc, static_lib_name,
129
};
1310

1411
fn main() {
15-
cc().input("foo.c").arg("-c").out_exe("foo.o").run();
16-
build_native_static_lib("foo");
12+
let obj_file = if is_windows_msvc() { "foo.obj" } else { "foo.o" };
13+
cc().input("foo.c").arg("-c").arg("-fno-lto").out_exe(obj_file).run();
14+
llvm_ar().obj_to_ar().output_input(&static_lib_name("foo"), obj_file).run();
1715

1816
rustc().input("foo.rs").arg("-lstatic=foo").crate_type("cdylib").run();
1917

20-
let out = llvm_nm()
21-
.input(dynamic_lib_name("foo"))
22-
.run()
23-
.assert_stdout_not_contains_regex("T *my_function");
18+
if is_darwin() {
19+
llvm_nm().input(dynamic_lib_name("foo")).run().assert_stdout_not_contains("T _my_function");
20+
} else if is_windows() {
21+
llvm_readobj()
22+
.arg("--coff-exports")
23+
.input(dynamic_lib_name("foo"))
24+
.run()
25+
.assert_stdout_not_contains("my_function");
26+
} else {
27+
llvm_nm().input(dynamic_lib_name("foo")).run().assert_stdout_not_contains("T my_function");
28+
}
29+
30+
rfs::remove_file(dynamic_lib_name("foo"));
2431

2532
rustc().input("foo_export.rs").arg("-lstatic:+export-symbols=foo").crate_type("cdylib").run();
2633

2734
if is_darwin() {
28-
let out = llvm_nm()
35+
llvm_nm()
2936
.input(dynamic_lib_name("foo_export"))
3037
.run()
3138
.assert_stdout_contains("T _my_function");
39+
} else if is_windows() {
40+
llvm_readobj()
41+
.arg("--coff-exports")
42+
.input(dynamic_lib_name("foo_export"))
43+
.run()
44+
.assert_stdout_contains("my_function");
3245
} else if is_aix() {
33-
let out = llvm_nm()
46+
llvm_nm()
3447
.input(dynamic_lib_name("foo_export"))
3548
.run()
3649
.assert_stdout_contains("T .my_function");
3750
} else {
38-
let out = llvm_nm()
51+
llvm_nm()
3952
.input(dynamic_lib_name("foo_export"))
4053
.run()
4154
.assert_stdout_contains("T my_function");

0 commit comments

Comments
 (0)