MinGW: emit dllexport/dllimport by rustc

This fixes various cases where LD could not guess dllexport correctly and greatly improves compatibility with LLD which is not going to support linker scripts anytime soon
This commit is contained in:
Mateusz Mikuła 2020-05-07 11:52:21 +02:00
parent 6ee1b62c81
commit db9a84a1af
6 changed files with 47 additions and 14 deletions

View File

@ -168,7 +168,12 @@ pub fn get_fn(cx: &CodegenCx<'ll, 'tcx>, instance: Instance<'tcx>) -> &'ll Value
}
}
if cx.use_dll_storage_attrs && tcx.is_dllimport_foreign_item(instance_def_id) {
// MinGW: For backward compatibility we rely on the linker to decide whether it
// should use dllimport for functions.
if cx.use_dll_storage_attrs
&& tcx.is_dllimport_foreign_item(instance_def_id)
&& tcx.sess.target.target.target_env != "gnu"
{
unsafe {
llvm::LLVMSetDLLStorageClass(llfn, llvm::DLLStorageClass::DllImport);
}

View File

@ -281,7 +281,7 @@ impl CodegenCx<'ll, 'tcx> {
// argument validation.
debug_assert!(
!(self.tcx.sess.opts.cg.linker_plugin_lto.enabled()
&& self.tcx.sess.target.target.options.is_like_msvc
&& self.tcx.sess.target.target.options.is_like_windows
&& self.tcx.sess.opts.cg.prefer_dynamic)
);

View File

@ -217,7 +217,16 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
// attributes in LLVM IR as well as native dependencies (in C these
// correspond to `__declspec(dllimport)`).
//
// Whenever a dynamic library is built by MSVC it must have its public
// LD (BFD) in MinGW mode can often correctly guess `dllexport` but
// relying on that can result in issues like #50176.
// LLD won't support that and expects symbols with proper attributes.
// Because of that we make MinGW target emit dllexport just like MSVC.
// When it comes to dllimport we use it for constants but for functions
// rely on the linker to do the right thing. Opposed to dllexport this
// task is easy for them (both LD and LLD) and allows us to easily use
// symbols from static libraries in shared libraries.
//
// Whenever a dynamic library is built on Windows it must have its public
// interface specified by functions tagged with `dllexport` or otherwise
// they're not available to be linked against. This poses a few problems
// for the compiler, some of which are somewhat fundamental, but we use
@ -254,8 +263,8 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> {
// this effect) by marking very little as `dllimport` and praying the
// linker will take care of everything. Fixing this problem will likely
// require adding a few attributes to Rust itself (feature gated at the
// start) and then strongly recommending static linkage on MSVC!
let use_dll_storage_attrs = tcx.sess.target.target.options.is_like_msvc;
// start) and then strongly recommending static linkage on Windows!
let use_dll_storage_attrs = tcx.sess.target.target.options.is_like_windows;
let check_overflow = tcx.sess.overflow_checks();

View File

@ -523,8 +523,9 @@ fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType) {
return;
}
let is_windows = self.sess.target.target.options.is_like_windows;
let mut arg = OsString::new();
let path = tmpdir.join("list");
let path = tmpdir.join(if is_windows { "list.def" } else { "list" });
debug!("EXPORTED SYMBOLS:");
@ -540,6 +541,21 @@ fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType) {
if let Err(e) = res {
self.sess.fatal(&format!("failed to write lib.def file: {}", e));
}
} else if is_windows {
let res: io::Result<()> = try {
let mut f = BufWriter::new(File::create(&path)?);
// .def file similar to MSVC one but without LIBRARY section
// because LD doesn't like when it's empty
writeln!(f, "EXPORTS")?;
for symbol in self.info.exports[&crate_type].iter() {
debug!(" _{}", symbol);
writeln!(f, " {}", symbol)?;
}
};
if let Err(e) = res {
self.sess.fatal(&format!("failed to write list.def file: {}", e));
}
} else {
// Write an LD version script
let res: io::Result<()> = try {
@ -573,7 +589,10 @@ fn export_symbols(&mut self, tmpdir: &Path, crate_type: CrateType) {
if !self.is_ld {
arg.push("-Wl,")
}
arg.push("--version-script=");
// Both LD and LLD accept export list in *.def file form, there are no flags required
if !is_windows {
arg.push("--version-script=")
}
}
arg.push(&path);

View File

@ -1846,11 +1846,11 @@ fn msvc_imps_needed(tcx: TyCtxt<'_>) -> bool {
// something is wrong with commandline arg validation.
assert!(
!(tcx.sess.opts.cg.linker_plugin_lto.enabled()
&& tcx.sess.target.target.options.is_like_msvc
&& tcx.sess.target.target.options.is_like_windows
&& tcx.sess.opts.cg.prefer_dynamic)
);
tcx.sess.target.target.options.is_like_msvc &&
tcx.sess.target.target.options.is_like_windows &&
tcx.sess.crate_types().iter().any(|ct| *ct == CrateType::Rlib) &&
// ThinLTO can't handle this workaround in all cases, so we don't
// emit the `__imp_` symbols. Instead we make them unnecessary by disallowing

View File

@ -1294,19 +1294,19 @@ pub fn build_session(
// commandline argument, you can do so here.
fn validate_commandline_args_with_session_available(sess: &Session) {
// Since we don't know if code in an rlib will be linked to statically or
// dynamically downstream, rustc generates `__imp_` symbols that help the
// MSVC linker deal with this lack of knowledge (#27438). Unfortunately,
// dynamically downstream, rustc generates `__imp_` symbols that help linkers
// on Windows deal with this lack of knowledge (#27438). Unfortunately,
// these manually generated symbols confuse LLD when it tries to merge
// bitcode during ThinLTO. Therefore we disallow dynamic linking on MSVC
// bitcode during ThinLTO. Therefore we disallow dynamic linking on Windows
// when compiling for LLD ThinLTO. This way we can validly just not generate
// the `dllimport` attributes and `__imp_` symbols in that case.
if sess.opts.cg.linker_plugin_lto.enabled()
&& sess.opts.cg.prefer_dynamic
&& sess.target.target.options.is_like_msvc
&& sess.target.target.options.is_like_windows
{
sess.err(
"Linker plugin based LTO is not supported together with \
`-C prefer-dynamic` when targeting MSVC",
`-C prefer-dynamic` when targeting Windows-like targets",
);
}