From 6ce845caf40cfc34685383590fd2fadc37546412 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Mon, 16 Jun 2025 09:20:57 +0000 Subject: [PATCH 1/5] Use the .drectve section for exporting symbols from dlls on Windows While it would be reasonable to expect the Windows linker to handle linker args in the .drectve section identical to cli arguments, as it turns out exporting weak symbols only works when the /EXPORT is in the .drectve section, not when it is a linker argument or when a .DEF file is used. --- compiler/rustc_codegen_ssa/src/back/link.rs | 52 +++++++++++++++++-- compiler/rustc_codegen_ssa/src/back/linker.rs | 48 ++--------------- tests/ui/linking/dll-weak-definition.rs | 20 +++++++ 3 files changed, 72 insertions(+), 48 deletions(-) create mode 100644 tests/ui/linking/dll-weak-definition.rs diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index 07c713a006994..b88742de8acfd 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -2196,9 +2196,11 @@ fn add_linked_symbol_object( cmd: &mut dyn Linker, sess: &Session, tmpdir: &Path, - symbols: &[(String, SymbolExportKind)], + crate_type: CrateType, + linked_symbols: &[(String, SymbolExportKind)], + exported_symbols: &[(String, SymbolExportKind)], ) { - if symbols.is_empty() { + if linked_symbols.is_empty() && exported_symbols.is_empty() { return; } @@ -2235,7 +2237,7 @@ fn add_linked_symbol_object( None }; - for (sym, kind) in symbols.iter() { + for (sym, kind) in linked_symbols.iter() { let symbol = file.add_symbol(object::write::Symbol { name: sym.clone().into(), value: 0, @@ -2293,6 +2295,41 @@ fn add_linked_symbol_object( } } + if sess.target.is_like_msvc { + // Symbol visibility takes care of this for executables typically + let should_filter_symbols = if crate_type == CrateType::Executable { + sess.opts.unstable_opts.export_executable_symbols + } else { + true + }; + if should_filter_symbols { + // Currently the compiler doesn't use `dllexport` (an LLVM attribute) to + // export symbols from a dynamic library. When building a dynamic library, + // however, we're going to want some symbols exported, so this adds a + // `.drectve` section which lists all the symbols using /EXPORT arguments. + // + // The linker will read these arguments from the `.drectve` section and + // export all the symbols from the dynamic library. Note that this is not + // as simple as just exporting all the symbols in the current crate (as + // specified by `codegen.reachable`) but rather we also need to possibly + // export the symbols of upstream crates. Upstream rlibs may be linked + // statically to this dynamic library, in which case they may continue to + // transitively be used and hence need their symbols exported. + let drectve = exported_symbols + .into_iter() + .map(|(sym, kind)| match kind { + SymbolExportKind::Text | SymbolExportKind::Tls => format!(" /EXPORT:\"{sym}\""), + SymbolExportKind::Data => format!(" /EXPORT:\"{sym}\",DATA"), + }) + .collect::>() + .join(""); + + let section = + file.add_section(vec![], b".drectve".to_vec(), object::SectionKind::Linker); + file.append_section_data(section, drectve.as_bytes(), 1); + } + } + let path = tmpdir.join("symbols.o"); let result = std::fs::write(&path, file.write().unwrap()); if let Err(error) = result { @@ -2629,7 +2666,14 @@ fn linker_with_args( // Pre-link CRT objects. add_pre_link_objects(cmd, sess, flavor, link_output_kind, self_contained_crt_objects); - add_linked_symbol_object(cmd, sess, tmpdir, &crate_info.linked_symbols[&crate_type]); + add_linked_symbol_object( + cmd, + sess, + tmpdir, + crate_type, + &crate_info.linked_symbols[&crate_type], + &export_symbols, + ); // Sanitizer libraries. add_sanitizer_libraries(sess, flavor, crate_type, cmd); diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 4041bfaa24cf0..0622fba408a12 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -1126,53 +1126,13 @@ impl<'a> Linker for MsvcLinker<'a> { } } - // Currently the compiler doesn't use `dllexport` (an LLVM attribute) to - // export symbols from a dynamic library. When building a dynamic library, - // however, we're going to want some symbols exported, so this function - // generates a DEF file which lists all the symbols. - // - // The linker will read this `*.def` file and export all the symbols from - // the dynamic library. Note that this is not as simple as just exporting - // all the symbols in the current crate (as specified by `codegen.reachable`) - // but rather we also need to possibly export the symbols of upstream - // crates. Upstream rlibs may be linked statically to this dynamic library, - // in which case they may continue to transitively be used and hence need - // their symbols exported. fn export_symbols( &mut self, - tmpdir: &Path, - crate_type: CrateType, - symbols: &[(String, SymbolExportKind)], + _tmpdir: &Path, + _crate_type: CrateType, + _symbols: &[(String, SymbolExportKind)], ) { - // Symbol visibility takes care of this typically - if crate_type == CrateType::Executable { - let should_export_executable_symbols = - self.sess.opts.unstable_opts.export_executable_symbols; - if !should_export_executable_symbols { - return; - } - } - - let path = tmpdir.join("lib.def"); - let res = try { - let mut f = File::create_buffered(&path)?; - - // Start off with the standard module name header and then go - // straight to exports. - writeln!(f, "LIBRARY")?; - writeln!(f, "EXPORTS")?; - for (symbol, kind) in symbols { - let kind_marker = if *kind == SymbolExportKind::Data { " DATA" } else { "" }; - debug!(" _{symbol}"); - writeln!(f, " {symbol}{kind_marker}")?; - } - }; - if let Err(error) = res { - self.sess.dcx().emit_fatal(errors::LibDefWriteFailure { error }); - } - let mut arg = OsString::from("/DEF:"); - arg.push(path); - self.link_arg(&arg); + // We already add /EXPORT arguments to the .drectve section of symbols.o. } fn windows_subsystem(&mut self, subsystem: WindowsSubsystemKind) { diff --git a/tests/ui/linking/dll-weak-definition.rs b/tests/ui/linking/dll-weak-definition.rs new file mode 100644 index 0000000000000..198a94ccee97e --- /dev/null +++ b/tests/ui/linking/dll-weak-definition.rs @@ -0,0 +1,20 @@ +// Regression test for MSVC link.exe failing to export weak definitions from dlls. +// See https://github.com/rust-lang/rust/pull/142568 + +//@ build-pass +//@ only-msvc +//@ revisions: link_exe lld +//@[lld] needs-rust-lld +//@[link_exe] compile-flags: -Zunstable-options -Clink-self-contained=-linker -Clinker-features=-lld +//@[lld] compile-flags: -Zunstable-options -Clink-self-contained=+linker -Clinker-features=+lld + +#![feature(linkage)] +#![crate_type = "cdylib"] + +#[linkage = "weak"] +#[no_mangle] +pub fn weak_function() {} + +#[linkage = "weak"] +#[no_mangle] +pub static WEAK_STATIC: u8 = 42; From 729ca3fb4b21642bc4ff223598f51b1e2284ca06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Tue, 23 Jun 2026 15:15:44 +0800 Subject: [PATCH 2/5] Fix MSVC drectve exports for decorated symbols --- compiler/rustc_codegen_ssa/src/back/link.rs | 83 ++++----- compiler/rustc_codegen_ssa/src/back/linker.rs | 159 +++++++++--------- .../src/back/symbol_export.rs | 5 +- compiler/rustc_codegen_ssa/src/lib.rs | 24 ++- typos.toml | 1 + 5 files changed, 147 insertions(+), 125 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index b88742de8acfd..c41f6f30a1da3 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -63,7 +63,10 @@ use super::rmeta_link::RmetaLinkCache; use super::rpath::{self, RPathConfig}; use super::{apple, rmeta_link, versioned_llvm_target}; use crate::base::needs_allocator_shim_for_linking; -use crate::{CodegenLintLevelSpecs, CompiledModule, CompiledModules, CrateInfo, NativeLib, errors}; +use crate::{ + CodegenLintLevelSpecs, CompiledModule, CompiledModules, CrateInfo, NativeLib, SymbolExport, + errors, +}; pub fn ensure_removed(dcx: DiagCtxtHandle<'_>, path: &Path) { if let Err(e) = fs::remove_file(path) { @@ -593,7 +596,7 @@ fn link_staticlib( crate_info .exported_symbols .get(&CrateType::StaticLib) - .map(|symbols| symbols.iter().map(|(s, _)| s.clone()).collect()) + .map(|symbols| symbols.iter().map(|symbol| symbol.name.clone()).collect()) } } else { None @@ -2198,9 +2201,13 @@ fn add_linked_symbol_object( tmpdir: &Path, crate_type: CrateType, linked_symbols: &[(String, SymbolExportKind)], - exported_symbols: &[(String, SymbolExportKind)], + exported_symbols: &[SymbolExport], ) { - if linked_symbols.is_empty() && exported_symbols.is_empty() { + let should_export_symbols = sess.target.is_like_msvc + && !exported_symbols.is_empty() + && (crate_type != CrateType::Executable + || sess.opts.unstable_opts.export_executable_symbols); + if linked_symbols.is_empty() && !should_export_symbols { return; } @@ -2295,39 +2302,35 @@ fn add_linked_symbol_object( } } - if sess.target.is_like_msvc { - // Symbol visibility takes care of this for executables typically - let should_filter_symbols = if crate_type == CrateType::Executable { - sess.opts.unstable_opts.export_executable_symbols - } else { - true - }; - if should_filter_symbols { - // Currently the compiler doesn't use `dllexport` (an LLVM attribute) to - // export symbols from a dynamic library. When building a dynamic library, - // however, we're going to want some symbols exported, so this adds a - // `.drectve` section which lists all the symbols using /EXPORT arguments. - // - // The linker will read these arguments from the `.drectve` section and - // export all the symbols from the dynamic library. Note that this is not - // as simple as just exporting all the symbols in the current crate (as - // specified by `codegen.reachable`) but rather we also need to possibly - // export the symbols of upstream crates. Upstream rlibs may be linked - // statically to this dynamic library, in which case they may continue to - // transitively be used and hence need their symbols exported. - let drectve = exported_symbols - .into_iter() - .map(|(sym, kind)| match kind { - SymbolExportKind::Text | SymbolExportKind::Tls => format!(" /EXPORT:\"{sym}\""), - SymbolExportKind::Data => format!(" /EXPORT:\"{sym}\",DATA"), - }) - .collect::>() - .join(""); - - let section = - file.add_section(vec![], b".drectve".to_vec(), object::SectionKind::Linker); - file.append_section_data(section, drectve.as_bytes(), 1); + if should_export_symbols { + // Currently the compiler doesn't use `dllexport` (an LLVM attribute) to + // export symbols from a dynamic library. When building a dynamic library, + // however, we're going to want some symbols exported, so this adds a + // `.drectve` section which lists all the symbols using /EXPORT arguments. + // + // The linker will read these arguments from the `.drectve` section and + // export all the symbols from the dynamic library. Note that this is not + // as simple as just exporting all the symbols in the current crate (as + // specified by `codegen.reachable`) but rather we also need to possibly + // export the symbols of upstream crates. Upstream rlibs may be linked + // statically to this dynamic library, in which case they may continue to + // transitively be used and hence need their symbols exported. + fn msvc_drectve_export(symbol: &SymbolExport) -> String { + let data = if symbol.kind == SymbolExportKind::Data { ",DATA" } else { "" }; + + if let Some(link_name) = symbol.link_name.as_deref() { + // The first name is the decorated symbol used by the import library, while + // EXPORTAS gives the public name written to the DLL export table. + format!(" /EXPORT:\"{link_name}\"{data},EXPORTAS,\"{}\"", symbol.name) + } else { + format!(" /EXPORT:\"{}\"{data}", symbol.name) + } } + + let drectve = exported_symbols.iter().map(msvc_drectve_export).collect::(); + + let section = file.add_section(vec![], b".drectve".to_vec(), object::SectionKind::Linker); + file.append_section_data(section, drectve.as_bytes(), 1); } let path = tmpdir.join("symbols.o"); @@ -2523,7 +2526,7 @@ fn undecorate_c_symbol<'a>( fn add_c_staticlib_symbols( sess: &Session, lib: &NativeLib, - out: &mut Vec<(String, SymbolExportKind)>, + out: &mut Vec, ) -> io::Result<()> { let file_path = find_native_static_library(lib.name.as_str(), lib.verbatim, sess); @@ -2586,7 +2589,11 @@ fn add_c_staticlib_symbols( let Some(undecorated) = undecorate_c_symbol(name, sess, export_kind) else { continue; }; - out.push((undecorated.to_string(), export_kind)); + out.push(SymbolExport::with_link_name( + undecorated.to_string(), + export_kind, + name.to_string(), + )); } } diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 0622fba408a12..9f981016c18af 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -15,7 +15,7 @@ use rustc_middle::middle::dependency_format::Linkage; use rustc_middle::middle::exported_symbols::{ self, ExportedSymbol, SymbolExportInfo, SymbolExportKind, SymbolExportLevel, }; -use rustc_middle::ty::TyCtxt; +use rustc_middle::ty::{SymbolName, TyCtxt}; use rustc_session::Session; use rustc_session::config::{self, CrateType, DebugInfo, LinkerPluginLto, Lto, OptLevel, Strip}; use rustc_target::spec::{Arch, Cc, CfgAbi, LinkOutputKind, LinkerFlavor, Lld, Os}; @@ -25,7 +25,7 @@ use super::command::Command; use super::symbol_export; use crate::back::symbol_export::allocator_shim_symbols; use crate::base::needs_allocator_shim_for_linking; -use crate::errors; +use crate::{SymbolExport, errors}; #[cfg(test)] mod tests; @@ -338,12 +338,7 @@ pub(crate) trait Linker { fn debuginfo(&mut self, strip: Strip, natvis_debugger_visualizers: &[PathBuf]); fn no_crt_objects(&mut self); fn no_default_libraries(&mut self); - fn export_symbols( - &mut self, - tmpdir: &Path, - crate_type: CrateType, - symbols: &[(String, SymbolExportKind)], - ); + fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType, symbols: &[SymbolExport]); fn windows_subsystem(&mut self, subsystem: WindowsSubsystemKind); fn linker_plugin_lto(&mut self); fn add_eh_frame_header(&mut self) {} @@ -794,12 +789,7 @@ impl<'a> Linker for GccLinker<'a> { } } - fn export_symbols( - &mut self, - tmpdir: &Path, - crate_type: CrateType, - symbols: &[(String, SymbolExportKind)], - ) { + fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType, symbols: &[SymbolExport]) { // Symbol visibility in object files typically takes care of this. if crate_type == CrateType::Executable { let should_export_executable_symbols = @@ -826,9 +816,9 @@ impl<'a> Linker for GccLinker<'a> { // Write a plain, newline-separated list of symbols let res = try { let mut f = File::create_buffered(&path)?; - for (sym, _) in symbols { - debug!(" _{sym}"); - writeln!(f, "_{sym}")?; + for sym in symbols { + debug!(" _{}", sym.name); + writeln!(f, "_{}", sym.name)?; } }; if let Err(error) = res { @@ -842,12 +832,13 @@ impl<'a> Linker for GccLinker<'a> { // .def file similar to MSVC one but without LIBRARY section // because LD doesn't like when it's empty writeln!(f, "EXPORTS")?; - for (symbol, kind) in symbols { - let kind_marker = if *kind == SymbolExportKind::Data { " DATA" } else { "" }; - debug!(" _{symbol}"); + for symbol in symbols { + let kind_marker = + if symbol.kind == SymbolExportKind::Data { " DATA" } else { "" }; + debug!(" _{}", symbol.name); // Quote the name in case it's reserved by linker in some way // (this accounts for names with dots in particular). - writeln!(f, " \"{symbol}\"{kind_marker}")?; + writeln!(f, " \"{}\"{kind_marker}", symbol.name)?; } }; if let Err(error) = res { @@ -856,16 +847,16 @@ impl<'a> Linker for GccLinker<'a> { self.link_arg(path); } else if self.sess.target.is_like_wasm { self.link_arg("--no-export-dynamic"); - for (sym, _) in symbols { - self.link_arg("--export").link_arg(sym); + for sym in symbols { + self.link_arg("--export").link_arg(&sym.name); } } else if crate_type == CrateType::Executable && !self.sess.target.is_like_solaris { let res = try { let mut f = File::create_buffered(&path)?; writeln!(f, "{{")?; - for (sym, _) in symbols { - debug!(sym); - writeln!(f, " {sym};")?; + for sym in symbols { + debug!("{}", sym.name); + writeln!(f, " {};", sym.name)?; } writeln!(f, "}};")?; }; @@ -880,9 +871,9 @@ impl<'a> Linker for GccLinker<'a> { writeln!(f, "{{")?; if !symbols.is_empty() { writeln!(f, " global:")?; - for (sym, _) in symbols { - debug!(" {sym};"); - writeln!(f, " {sym};")?; + for sym in symbols { + debug!(" {};", sym.name); + writeln!(f, " {};", sym.name)?; } } writeln!(f, "\n local:\n *;\n}};")?; @@ -1130,7 +1121,7 @@ impl<'a> Linker for MsvcLinker<'a> { &mut self, _tmpdir: &Path, _crate_type: CrateType, - _symbols: &[(String, SymbolExportKind)], + _symbols: &[SymbolExport], ) { // We already add /EXPORT arguments to the .drectve section of symbols.o. } @@ -1273,19 +1264,14 @@ impl<'a> Linker for EmLinker<'a> { self.cc_arg("-nodefaultlibs"); } - fn export_symbols( - &mut self, - _tmpdir: &Path, - _crate_type: CrateType, - symbols: &[(String, SymbolExportKind)], - ) { + fn export_symbols(&mut self, _tmpdir: &Path, _crate_type: CrateType, symbols: &[SymbolExport]) { debug!("EXPORTED SYMBOLS:"); self.cc_arg("-s"); let mut arg = OsString::from("EXPORTED_FUNCTIONS="); let encoded = serde_json::to_string( - &symbols.iter().map(|(sym, _)| "_".to_owned() + sym).collect::>(), + &symbols.iter().map(|sym| "_".to_owned() + &sym.name).collect::>(), ) .unwrap(); debug!("{encoded}"); @@ -1413,14 +1399,9 @@ impl<'a> Linker for WasmLd<'a> { fn no_default_libraries(&mut self) {} - fn export_symbols( - &mut self, - _tmpdir: &Path, - _crate_type: CrateType, - symbols: &[(String, SymbolExportKind)], - ) { - for (sym, _) in symbols { - self.link_args(&["--export", sym]); + fn export_symbols(&mut self, _tmpdir: &Path, _crate_type: CrateType, symbols: &[SymbolExport]) { + for sym in symbols { + self.link_args(&["--export", &sym.name]); } } @@ -1541,7 +1522,7 @@ impl<'a> Linker for L4Bender<'a> { self.cc_arg("-nostdlib"); } - fn export_symbols(&mut self, _: &Path, _: CrateType, _: &[(String, SymbolExportKind)]) { + fn export_symbols(&mut self, _: &Path, _: CrateType, _: &[SymbolExport]) { // ToDo, not implemented, copy from GCC self.sess.dcx().emit_warn(errors::L4BenderExportingSymbolsUnimplemented); } @@ -1695,19 +1676,14 @@ impl<'a> Linker for AixLinker<'a> { fn no_default_libraries(&mut self) {} - fn export_symbols( - &mut self, - tmpdir: &Path, - _crate_type: CrateType, - symbols: &[(String, SymbolExportKind)], - ) { + fn export_symbols(&mut self, tmpdir: &Path, _crate_type: CrateType, symbols: &[SymbolExport]) { let path = tmpdir.join("list.exp"); let res = try { let mut f = File::create_buffered(&path)?; // FIXME: use llvm-nm to generate export list. - for (symbol, _) in symbols { - debug!(" _{symbol}"); - writeln!(f, " {symbol}")?; + for symbol in symbols { + debug!(" _{}", symbol.name); + writeln!(f, " {}", symbol.name)?; } }; if let Err(e) = res { @@ -1752,15 +1728,36 @@ fn for_each_exported_symbols_include_dep<'tcx>( } } -pub(crate) fn exported_symbols( +fn symbol_export_from_exported_symbol<'tcx>( + tcx: TyCtxt<'tcx>, + symbol: ExportedSymbol<'tcx>, + kind: SymbolExportKind, + cnum: CrateNum, +) -> SymbolExport { + let name = symbol_export::exporting_symbol_name_for_instance_in_crate(tcx, symbol, cnum); + let link_name = + symbol_export::linking_symbol_name_for_instance_in_crate(tcx, symbol, kind, cnum); + SymbolExport::with_link_name(name, kind, link_name) +} + +fn symbol_export_from_raw_name( tcx: TyCtxt<'_>, - crate_type: CrateType, -) -> Vec<(String, SymbolExportKind)> { + name: String, + kind: SymbolExportKind, +) -> SymbolExport { + let symbol = ExportedSymbol::NoDefId(SymbolName::new(tcx, &name)); + let link_name = + symbol_export::linking_symbol_name_for_instance_in_crate(tcx, symbol, kind, LOCAL_CRATE); + SymbolExport::with_link_name(name, kind, link_name) +} + +pub(crate) fn exported_symbols(tcx: TyCtxt<'_>, crate_type: CrateType) -> Vec { if let Some(ref exports) = tcx.sess.target.override_export_symbols { return exports .iter() .map(|name| { - ( + symbol_export_from_raw_name( + tcx, name.to_string(), // FIXME use the correct export kind for this symbol. override_export_symbols // can't directly specify the SymbolExportKind as it is defined in rustc_middle @@ -1785,7 +1782,11 @@ pub(crate) fn exported_symbols( && !tcx.sess.target.is_like_wasm { let metadata_symbol_name = exported_symbols::metadata_symbol_name(tcx); - symbols.push((metadata_symbol_name, SymbolExportKind::Data)); + symbols.push(symbol_export_from_raw_name( + tcx, + metadata_symbol_name, + SymbolExportKind::Data, + )); } symbols @@ -1794,7 +1795,7 @@ pub(crate) fn exported_symbols( fn exported_symbols_for_non_proc_macro( tcx: TyCtxt<'_>, crate_type: CrateType, -) -> Vec<(String, SymbolExportKind)> { +) -> Vec { let mut symbols = Vec::new(); let export_threshold = symbol_export::crates_export_threshold(&[crate_type]); for_each_exported_symbols_include_dep(tcx, crate_type, |symbol, info, cnum| { @@ -1802,10 +1803,7 @@ fn exported_symbols_for_non_proc_macro( // from any dylib. The latter doesn't work anyway as we use hidden visibility for // compiler-builtins. Most linkers silently ignore it, but ld64 gives a warning. if info.level.is_below_threshold(export_threshold) && !tcx.is_compiler_builtins(cnum) { - symbols.push(( - symbol_export::exporting_symbol_name_for_instance_in_crate(tcx, symbol, cnum), - info.kind, - )); + symbols.push(symbol_export_from_exported_symbol(tcx, symbol, info.kind, cnum)); symbol_export::extend_exported_symbols(&mut symbols, tcx, symbol, cnum); } }); @@ -1815,13 +1813,16 @@ fn exported_symbols_for_non_proc_macro( && needs_allocator_shim_for_linking(tcx.dependency_formats(()), crate_type) && let Some(kind) = tcx.allocator_kind(()) { - symbols.extend(allocator_shim_symbols(tcx, kind)); + symbols.extend( + allocator_shim_symbols(tcx, kind) + .map(|(name, kind)| symbol_export_from_raw_name(tcx, name, kind)), + ); } symbols } -fn exported_symbols_for_proc_macro_crate(tcx: TyCtxt<'_>) -> Vec<(String, SymbolExportKind)> { +fn exported_symbols_for_proc_macro_crate(tcx: TyCtxt<'_>) -> Vec { // `exported_symbols` will be empty when !should_codegen. if !tcx.sess.opts.output_types.should_codegen() { return Vec::new(); @@ -1830,7 +1831,7 @@ fn exported_symbols_for_proc_macro_crate(tcx: TyCtxt<'_>) -> Vec<(String, Symbol let stable_crate_id = tcx.stable_crate_id(LOCAL_CRATE); let proc_macro_decls_name = rustc_session::generate_proc_macro_decls_symbol(stable_crate_id); - vec![(proc_macro_decls_name, SymbolExportKind::Data)] + vec![symbol_export_from_raw_name(tcx, proc_macro_decls_name, SymbolExportKind::Data)] } pub(crate) fn linked_symbols( @@ -1944,16 +1945,11 @@ impl<'a> Linker for LlbcLinker<'a> { fn ehcont_guard(&mut self) {} - fn export_symbols( - &mut self, - _tmpdir: &Path, - _crate_type: CrateType, - symbols: &[(String, SymbolExportKind)], - ) { + fn export_symbols(&mut self, _tmpdir: &Path, _crate_type: CrateType, symbols: &[SymbolExport]) { match _crate_type { CrateType::Cdylib => { - for (sym, _) in symbols { - self.link_args(&["--export-symbol", sym]); + for sym in symbols { + self.link_args(&["--export-symbol", &sym.name]); } } _ => (), @@ -2024,17 +2020,12 @@ impl<'a> Linker for BpfLinker<'a> { fn ehcont_guard(&mut self) {} - fn export_symbols( - &mut self, - tmpdir: &Path, - _crate_type: CrateType, - symbols: &[(String, SymbolExportKind)], - ) { + fn export_symbols(&mut self, tmpdir: &Path, _crate_type: CrateType, symbols: &[SymbolExport]) { let path = tmpdir.join("symbols"); let res = try { let mut f = File::create_buffered(&path)?; - for (sym, _) in symbols { - writeln!(f, "{sym}")?; + for sym in symbols { + writeln!(f, "{}", sym.name)?; } }; if let Err(error) = res { diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index dfc8c8be5c03f..42e2b646e4793 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -21,6 +21,7 @@ use rustc_symbol_mangling::mangle_internal_symbol; use rustc_target::spec::{Arch, Os, TlsModel}; use tracing::debug; +use crate::SymbolExport; use crate::back::symbol_export; use crate::base::allocator_shim_contents; @@ -721,7 +722,7 @@ pub(crate) fn exporting_symbol_name_for_instance_in_crate<'tcx>( /// Add it to the symbols list for all kernel functions, so that it is exported in the linked /// object. pub(crate) fn extend_exported_symbols<'tcx>( - symbols: &mut Vec<(String, SymbolExportKind)>, + symbols: &mut Vec, tcx: TyCtxt<'tcx>, symbol: ExportedSymbol<'tcx>, instantiating_crate: CrateNum, @@ -737,7 +738,7 @@ pub(crate) fn extend_exported_symbols<'tcx>( // Add the symbol for the kernel descriptor (with .kd suffix) // Per https://llvm.org/docs/AMDGPUUsage.html#symbols these will always be `STT_OBJECT` so // export as data. - symbols.push((format!("{undecorated}.kd"), SymbolExportKind::Data)); + symbols.push(SymbolExport::new(format!("{undecorated}.kd"), SymbolExportKind::Data)); } fn maybe_emutls_symbol_name<'tcx>( diff --git a/compiler/rustc_codegen_ssa/src/lib.rs b/compiler/rustc_codegen_ssa/src/lib.rs index 10ae8a9ee0b38..f3f19f9e90d83 100644 --- a/compiler/rustc_codegen_ssa/src/lib.rs +++ b/compiler/rustc_codegen_ssa/src/lib.rs @@ -233,6 +233,28 @@ impl From<&cstore::NativeLib> for NativeLib { } } +/// A symbol to make visible from a linked artifact. +#[derive(Clone, Debug, Encodable, Decodable)] +pub struct SymbolExport { + /// Name to make visible from the linked artifact. + pub name: String, + /// Kind of symbol, used for target-specific export directives and name decoration. + pub kind: SymbolExportKind, + /// Name of the symbol as seen by the linker, when it differs from `name`. + pub link_name: Option, +} + +impl SymbolExport { + pub fn new(name: String, kind: SymbolExportKind) -> SymbolExport { + SymbolExport { name, kind, link_name: None } + } + + pub fn with_link_name(name: String, kind: SymbolExportKind, link_name: String) -> SymbolExport { + let link_name = if link_name == name { None } else { Some(link_name) }; + SymbolExport { name, kind, link_name } + } +} + /// Misc info we load from metadata to persist beyond the tcx. /// /// Note: though `CrateNum` is only meaningful within the same tcx, information within `CrateInfo` @@ -247,7 +269,7 @@ pub struct CrateInfo { pub target_cpu: String, pub target_features: Vec, pub crate_types: Vec, - pub exported_symbols: UnordMap>, + pub exported_symbols: UnordMap>, pub linked_symbols: FxIndexMap>, pub local_crate_name: Symbol, pub compiler_builtins: Option, diff --git a/typos.toml b/typos.toml index f680f5b0e8abf..1e3d5c5e89d09 100644 --- a/typos.toml +++ b/typos.toml @@ -21,6 +21,7 @@ extend-exclude = [ # right now. Entries should look like `mipsel = "mipsel"`. # # tidy-alphabetical-start +EXPORTAS = "EXPORTAS" # MSVC linker keyword used with /EXPORT directives anser = "anser" # an ANSI parsing package used by rust-analyzer arange = "arange" # short for A-range childs = "childs" From c84bcbe972ae59876db601e9a84d7381ff6ad50f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Mon, 29 Jun 2026 14:18:48 +0800 Subject: [PATCH 3/5] Use rust-lld for dll-weak-definition test --- tests/ui/linking/dll-weak-definition.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/linking/dll-weak-definition.rs b/tests/ui/linking/dll-weak-definition.rs index 198a94ccee97e..47ae6aeb43366 100644 --- a/tests/ui/linking/dll-weak-definition.rs +++ b/tests/ui/linking/dll-weak-definition.rs @@ -4,9 +4,9 @@ //@ build-pass //@ only-msvc //@ revisions: link_exe lld +//@[link_exe] compile-flags: -Clinker=link.exe //@[lld] needs-rust-lld -//@[link_exe] compile-flags: -Zunstable-options -Clink-self-contained=-linker -Clinker-features=-lld -//@[lld] compile-flags: -Zunstable-options -Clink-self-contained=+linker -Clinker-features=+lld +//@[lld] compile-flags: -Clinker=rust-lld #![feature(linkage)] #![crate_type = "cdylib"] From 2f749bdc65b91a74b2175839d43b3b68773504d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Tue, 30 Jun 2026 12:31:28 +0800 Subject: [PATCH 4/5] Preserve MSVC import libs for empty cdylibs --- compiler/rustc_codegen_ssa/src/back/linker.rs | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index 9f981016c18af..8c20db5791774 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -1117,13 +1117,30 @@ impl<'a> Linker for MsvcLinker<'a> { } } - fn export_symbols( - &mut self, - _tmpdir: &Path, - _crate_type: CrateType, - _symbols: &[SymbolExport], - ) { + fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType, _symbols: &[SymbolExport]) { // We already add /EXPORT arguments to the .drectve section of symbols.o. + // Keep passing an empty .def file: link.exe otherwise skips the import + // library for DLLs with no exports. + if crate_type == CrateType::Executable { + let should_export_executable_symbols = + self.sess.opts.unstable_opts.export_executable_symbols; + if !should_export_executable_symbols { + return; + } + } + + let path = tmpdir.join("lib.def"); + let res = try { + let mut f = File::create_buffered(&path)?; + writeln!(f, "LIBRARY")?; + writeln!(f, "EXPORTS")?; + }; + if let Err(error) = res { + self.sess.dcx().emit_fatal(errors::LibDefWriteFailure { error }); + } + let mut arg = OsString::from("/DEF:"); + arg.push(path); + self.link_arg(&arg); } fn windows_subsystem(&mut self, subsystem: WindowsSubsystemKind) { From 296ba6a90cf5304063d53a6963a227d8fef71ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9D=E5=80=89=E6=B0=B4=E5=B8=8C?= Date: Tue, 30 Jun 2026 23:56:44 +0800 Subject: [PATCH 5/5] Rewrite dll-weak-definition test to run-make --- tests/run-make/dll-weak-definition/rmake.rs | 23 +++++++++++++++++++++ tests/run-make/dll-weak-definition/weak.rs | 10 +++++++++ tests/ui/linking/dll-weak-definition.rs | 20 ------------------ 3 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 tests/run-make/dll-weak-definition/rmake.rs create mode 100644 tests/run-make/dll-weak-definition/weak.rs delete mode 100644 tests/ui/linking/dll-weak-definition.rs diff --git a/tests/run-make/dll-weak-definition/rmake.rs b/tests/run-make/dll-weak-definition/rmake.rs new file mode 100644 index 0000000000000..1b05bdecc2cdf --- /dev/null +++ b/tests/run-make/dll-weak-definition/rmake.rs @@ -0,0 +1,23 @@ +// Regression test for MSVC link.exe failing to export weak definitions from dlls. +// See https://github.com/rust-lang/rust/pull/158294 + +//@ only-msvc +//@ needs-rust-lld + +use run_make_support::{dynamic_lib_name, llvm_readobj, rustc}; + +fn test_with_linker(linker: &str) { + rustc().input("weak.rs").linker(linker).run(); + + llvm_readobj() + .arg("--coff-exports") + .input(dynamic_lib_name("weak")) + .run() + .assert_stdout_contains("Name: weak_function") + .assert_stdout_contains("Name: WEAK_STATIC"); +} + +fn main() { + test_with_linker("link"); + test_with_linker("rust-lld"); +} diff --git a/tests/run-make/dll-weak-definition/weak.rs b/tests/run-make/dll-weak-definition/weak.rs new file mode 100644 index 0000000000000..93b19ad627910 --- /dev/null +++ b/tests/run-make/dll-weak-definition/weak.rs @@ -0,0 +1,10 @@ +#![feature(linkage)] +#![crate_type = "cdylib"] + +#[linkage = "weak"] +#[no_mangle] +pub fn weak_function() {} + +#[linkage = "weak"] +#[no_mangle] +pub static WEAK_STATIC: u8 = 42; diff --git a/tests/ui/linking/dll-weak-definition.rs b/tests/ui/linking/dll-weak-definition.rs deleted file mode 100644 index 47ae6aeb43366..0000000000000 --- a/tests/ui/linking/dll-weak-definition.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Regression test for MSVC link.exe failing to export weak definitions from dlls. -// See https://github.com/rust-lang/rust/pull/142568 - -//@ build-pass -//@ only-msvc -//@ revisions: link_exe lld -//@[link_exe] compile-flags: -Clinker=link.exe -//@[lld] needs-rust-lld -//@[lld] compile-flags: -Clinker=rust-lld - -#![feature(linkage)] -#![crate_type = "cdylib"] - -#[linkage = "weak"] -#[no_mangle] -pub fn weak_function() {} - -#[linkage = "weak"] -#[no_mangle] -pub static WEAK_STATIC: u8 = 42;