From 18607149fbb0836059a96981c78e10ca52d23cd5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 30 Jul 2015 14:20:36 -0700 Subject: [PATCH 1/2] syntax: Add a new unstable #[linked_from] attribute To correctly reexport statically included libraries from a DLL on Windows, the compiler will soon need to have knowledge about what symbols are statically included and which are not. To solve this problem a new unstable `#[linked_from]` attribute is being added and recognized on `extern` blocks to indicate which native library the symbols are coming from. The compiler then keeps track of what the set of FFI symbols are that are included statically. This information will be used in a future commit to configure how we invoke the linker on Windows. --- src/doc/reference.md | 10 +- src/librustc/metadata/creader.rs | 154 ++++++++++++++++--------------- src/librustc/metadata/cstore.rs | 15 ++- src/libsyntax/feature_gate.rs | 5 + 4 files changed, 105 insertions(+), 79 deletions(-) diff --git a/src/doc/reference.md b/src/doc/reference.md index e905ed917d7..4d5564d9faf 100644 --- a/src/doc/reference.md +++ b/src/doc/reference.md @@ -1924,10 +1924,16 @@ On an `extern` block, the following attributes are interpreted: name and type. This is feature gated and the exact behavior is implementation-defined (due to variety of linker invocation syntax). - `link` - indicate that a native library should be linked to for the - declarations in this block to be linked correctly. `link` supports an optional `kind` - key with three possible values: `dylib`, `static`, and `framework`. See [external blocks](#external-blocks) for more about external blocks. Two + declarations in this block to be linked correctly. `link` supports an optional + `kind` key with three possible values: `dylib`, `static`, and `framework`. See + [external blocks](#external-blocks) for more about external blocks. Two examples: `#[link(name = "readline")]` and `#[link(name = "CoreFoundation", kind = "framework")]`. +- `linked_from` - indicates what native library this block of FFI items is + coming from. This attribute is of the form `#[linked_from = "foo"]` where + `foo` is the name of a library in either `#[link]` or a `-l` flag. This + attribute is currently required to export symbols from a Rust dynamic library + on Windows, and it is feature gated behind the `linked_from` feature. On declarations inside an `extern` block, the following attributes are interpreted: diff --git a/src/librustc/metadata/creader.rs b/src/librustc/metadata/creader.rs index bbb2d4fade1..3226a99c6b3 100644 --- a/src/librustc/metadata/creader.rs +++ b/src/librustc/metadata/creader.rs @@ -20,6 +20,7 @@ use metadata::cstore::{CStore, CrateSource, MetadataBlob}; use metadata::decoder; use metadata::loader; use metadata::loader::CratePaths; +use util::nodemap::FnvHashMap; use std::cell::RefCell; use std::path::PathBuf; @@ -47,6 +48,7 @@ pub struct LocalCrateReader<'a, 'b:'a> { pub struct CrateReader<'a> { sess: &'a Session, next_crate_num: ast::CrateNum, + foreign_item_map: FnvHashMap>, } impl<'a, 'b, 'v> visit::Visitor<'v> for LocalCrateReader<'a, 'b> { @@ -157,6 +159,7 @@ impl<'a> CrateReader<'a> { CrateReader { sess: sess, next_crate_num: sess.cstore.next_crate_num(), + foreign_item_map: FnvHashMap(), } } @@ -490,6 +493,20 @@ impl<'a> CrateReader<'a> { _ => None, } } + + fn register_statically_included_foreign_items(&mut self) { + let libs = self.sess.cstore.get_used_libraries(); + for (lib, list) in self.foreign_item_map.iter() { + let is_static = libs.borrow().iter().any(|&(ref name, kind)| { + lib == name && kind == cstore::NativeStatic + }); + if is_static { + for id in list { + self.sess.cstore.add_statically_included_foreign_item(*id); + } + } + } + } } impl<'a, 'b> LocalCrateReader<'a, 'b> { @@ -515,6 +532,7 @@ impl<'a, 'b> LocalCrateReader<'a, 'b> { for &(ref name, kind) in &self.sess.opts.libs { register_native_lib(self.sess, None, name.clone(), kind); } + self.creader.register_statically_included_foreign_items(); } fn process_crate(&self, c: &ast::Crate) { @@ -541,89 +559,75 @@ impl<'a, 'b> LocalCrateReader<'a, 'b> { None, i.span, PathKind::Crate); - self.ast_map.with_path(i.id, |path| - cmeta.update_local_path(path)); + self.ast_map.with_path(i.id, |path| { + cmeta.update_local_path(path) + }); self.sess.cstore.add_extern_mod_stmt_cnum(info.id, cnum); } None => () } } - ast::ItemForeignMod(ref fm) => { - if fm.abi == abi::Rust || fm.abi == abi::RustIntrinsic { - return; - } - - // First, add all of the custom link_args attributes - let link_args = i.attrs.iter() - .filter_map(|at| if at.name() == "link_args" { - Some(at) - } else { - None - }) - .collect::>(); - for m in &link_args { - match m.value_str() { - Some(linkarg) => self.sess.cstore.add_used_link_args(&linkarg), - None => { /* fallthrough */ } - } - } - - // Next, process all of the #[link(..)]-style arguments - let link_args = i.attrs.iter() - .filter_map(|at| if at.name() == "link" { - Some(at) - } else { - None - }) - .collect::>(); - for m in &link_args { - match m.meta_item_list() { - Some(items) => { - let kind = items.iter().find(|k| { - k.name() == "kind" - }).and_then(|a| a.value_str()); - let kind = match kind { - Some(k) => { - if k == "static" { - cstore::NativeStatic - } else if self.sess.target.target.options.is_like_osx - && k == "framework" { - cstore::NativeFramework - } else if k == "framework" { - cstore::NativeFramework - } else if k == "dylib" { - cstore::NativeUnknown - } else { - self.sess.span_err(m.span, - &format!("unknown kind: `{}`", - k)); - cstore::NativeUnknown - } - } - None => cstore::NativeUnknown - }; - let n = items.iter().find(|n| { - n.name() == "name" - }).and_then(|a| a.value_str()); - let n = match n { - Some(n) => n, - None => { - self.sess.span_err(m.span, - "#[link(...)] specified without \ - `name = \"foo\"`"); - InternedString::new("foo") - } - }; - register_native_lib(self.sess, Some(m.span), - n.to_string(), kind); - } - None => {} - } - } - } + ast::ItemForeignMod(ref fm) => self.process_foreign_mod(i, fm), _ => { } } } + + fn process_foreign_mod(&mut self, i: &ast::Item, fm: &ast::ForeignMod) { + if fm.abi == abi::Rust || fm.abi == abi::RustIntrinsic { + return; + } + + // First, add all of the custom #[link_args] attributes + for m in i.attrs.iter().filter(|a| a.check_name("link_args")) { + if let Some(linkarg) = m.value_str() { + self.sess.cstore.add_used_link_args(&linkarg); + } + } + + // Next, process all of the #[link(..)]-style arguments + for m in i.attrs.iter().filter(|a| a.check_name("link")) { + let items = match m.meta_item_list() { + Some(item) => item, + None => continue, + }; + let kind = items.iter().find(|k| { + k.check_name("kind") + }).and_then(|a| a.value_str()); + let kind = match kind.as_ref().map(|s| &s[..]) { + Some("static") => cstore::NativeStatic, + Some("dylib") => cstore::NativeUnknown, + Some("framework") => cstore::NativeFramework, + Some(k) => { + self.sess.span_err(m.span, &format!("unknown kind: `{}`", k)); + cstore::NativeUnknown + } + None => cstore::NativeUnknown + }; + let n = items.iter().find(|n| { + n.check_name("name") + }).and_then(|a| a.value_str()); + let n = match n { + Some(n) => n, + None => { + self.sess.span_err(m.span, "#[link(...)] specified without \ + `name = \"foo\"`"); + InternedString::new("foo") + } + }; + register_native_lib(self.sess, Some(m.span), n.to_string(), kind); + } + + // Finally, process the #[linked_from = "..."] attribute + for m in i.attrs.iter().filter(|a| a.check_name("linked_from")) { + let lib_name = match m.value_str() { + Some(name) => name, + None => continue, + }; + let list = self.creader.foreign_item_map.entry(lib_name.to_string()) + .or_insert(Vec::new()); + list.extend(fm.items.iter().map(|it| it.id)); + } + } } /// Imports the codemap from an external crate into the codemap of the crate diff --git a/src/librustc/metadata/cstore.rs b/src/librustc/metadata/cstore.rs index 19d494824cf..ae5e797a029 100644 --- a/src/librustc/metadata/cstore.rs +++ b/src/librustc/metadata/cstore.rs @@ -20,7 +20,7 @@ pub use self::NativeLibraryKind::*; use back::svh::Svh; use metadata::{creader, decoder, loader}; use session::search_paths::PathKind; -use util::nodemap::{FnvHashMap, NodeMap}; +use util::nodemap::{FnvHashMap, NodeMap, NodeSet}; use std::cell::{RefCell, Ref}; use std::rc::Rc; @@ -97,6 +97,7 @@ pub struct CStore { used_crate_sources: RefCell>, used_libraries: RefCell>, used_link_args: RefCell>, + statically_included_foreign_items: RefCell, pub intr: Rc, } @@ -108,7 +109,8 @@ impl CStore { used_crate_sources: RefCell::new(Vec::new()), used_libraries: RefCell::new(Vec::new()), used_link_args: RefCell::new(Vec::new()), - intr: intr + intr: intr, + statically_included_foreign_items: RefCell::new(NodeSet()), } } @@ -167,6 +169,7 @@ impl CStore { self.used_crate_sources.borrow_mut().clear(); self.used_libraries.borrow_mut().clear(); self.used_link_args.borrow_mut().clear(); + self.statically_included_foreign_items.borrow_mut().clear(); } // This method is used when generating the command line to pass through to @@ -240,6 +243,14 @@ impl CStore { -> Option { self.extern_mod_crate_map.borrow().get(&emod_id).cloned() } + + pub fn add_statically_included_foreign_item(&self, id: ast::NodeId) { + self.statically_included_foreign_items.borrow_mut().insert(id); + } + + pub fn is_statically_included_foreign_item(&self, id: ast::NodeId) -> bool { + self.statically_included_foreign_items.borrow().contains(&id) + } } impl crate_metadata { diff --git a/src/libsyntax/feature_gate.rs b/src/libsyntax/feature_gate.rs index 60a5730a3da..87149ff81da 100644 --- a/src/libsyntax/feature_gate.rs +++ b/src/libsyntax/feature_gate.rs @@ -85,6 +85,7 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Status)] = &[ ("on_unimplemented", "1.0.0", Active), ("simd_ffi", "1.0.0", Active), ("allocator", "1.0.0", Active), + ("linked_from", "1.3.0", Active), ("if_let", "1.0.0", Accepted), ("while_let", "1.0.0", Accepted), @@ -269,6 +270,10 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType)] = &[ "the `#[fundamental]` attribute \ is an experimental feature")), + ("linked_from", Gated("linked_from", + "the `#[linked_from]` attribute \ + is an experimental feature")), + // FIXME: #14408 whitelist docs since rustdoc looks at them ("doc", Whitelisted), From e648c96c5f9b69022ae416040cf0558221a11d77 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 28 Jul 2015 17:19:08 -0700 Subject: [PATCH 2/2] trans: Stop informing LLVM about dllexport Rust's current compilation model makes it impossible on Windows to generate one object file with a complete and final set of dllexport annotations. This is because when an object is generated the compiler doesn't actually know if it will later be included in a dynamic library or not. The compiler works around this today by flagging *everything* as dllexport, but this has the drawback of exposing too much. Thankfully there are alternate methods of specifying the exported surface area of a dll on Windows, one of which is passing a `*.def` file to the linker which lists all public symbols of the dynamic library. This commit removes all locations that add `dllexport` to LLVM variables and instead dynamically generates a `*.def` file which is passed to the linker. This file will include all the public symbols of the current object file as well as all upstream libraries, and the crucial aspect is that it's only used when generating a dynamic library. When generating an executable this file isn't generated, so all the symbols aren't exported from an executable. To ensure that statically included native libraries are reexported correctly, the previously added support for the `#[linked_from]` attribute is used to determine the set of FFI symbols that are exported from a dynamic library, and this is required to get the compiler to link correctly. --- mk/platform.mk | 9 +- mk/target.mk | 4 +- src/compiletest/procsrv.rs | 3 +- src/librustc/metadata/common.rs | 4 +- src/librustc/metadata/csearch.rs | 10 +- src/librustc/metadata/decoder.rs | 25 +++- src/librustc/metadata/encoder.rs | 29 ++-- src/librustc_llvm/lib.rs | 2 + src/librustc_trans/back/link.rs | 6 + src/librustc_trans/back/linker.rs | 74 +++++++++- src/librustc_trans/trans/base.rs | 132 ++++++++++-------- src/test/auxiliary/issue-25185-1.rs | 3 + .../compile-fail/feature-gate-linked-from.rs | 16 +++ src/test/run-pass/variadic-ffi.rs | 1 + 14 files changed, 227 insertions(+), 91 deletions(-) create mode 100644 src/test/compile-fail/feature-gate-linked-from.rs diff --git a/mk/platform.mk b/mk/platform.mk index 2802e5ee4a2..082c0d526a0 100644 --- a/mk/platform.mk +++ b/mk/platform.mk @@ -277,10 +277,15 @@ $(foreach target,$(CFG_TARGET), \ # Fun times! # # [1]: https://msdn.microsoft.com/en-us/library/28d6s79h.aspx +# +# FIXME(stage0): remove this macro and the usage below (and the commments above) +# when a new snapshot is available. Also remove the +# RUSTFLAGS$(1)_.._T_ variable in mk/target.mk along with +# CUSTOM_DEPS (as they were only added for this) define ADD_RUSTC_LLVM_DEF_TO_MSVC ifeq ($$(findstring msvc,$(1)),msvc) -RUSTFLAGS_rustc_llvm_T_$(1) += -C link-args="-DEF:$(1)/rt/rustc_llvm.def" -CUSTOM_DEPS_rustc_llvm_T_$(1) += $(1)/rt/rustc_llvm.def +RUSTFLAGS0_rustc_llvm_T_$(1) += -C link-args="-DEF:$(1)/rt/rustc_llvm.def" +CUSTOM_DEPS0_rustc_llvm_T_$(1) += $(1)/rt/rustc_llvm.def $(1)/rt/rustc_llvm.def: $$(S)src/etc/mklldef.py $$(S)src/librustc_llvm/lib.rs $$(CFG_PYTHON) $$^ $$@ rustc_llvm-$$(CFG_FILENAME_EXTRA) diff --git a/mk/target.mk b/mk/target.mk index 1af4a2f4694..cd22a77bd22 100644 --- a/mk/target.mk +++ b/mk/target.mk @@ -40,7 +40,7 @@ CRATE_FULLDEPS_$(1)_T_$(2)_H_$(3)_$(4) := \ $$(RT_OUTPUT_DIR_$(2))/$$(dep)) \ $$(foreach dep,$$(NATIVE_TOOL_DEPS_$(4)_T_$(2)), \ $$(TBIN$(1)_T_$(3)_H_$(3))/$$(dep)) \ - $$(CUSTOM_DEPS_$(4)_T_$(2)) + $$(CUSTOM_DEPS$(1)_$(4)_T_$(2)) endef $(foreach host,$(CFG_HOST), \ @@ -92,7 +92,7 @@ $$(TLIB$(1)_T_$(2)_H_$(3))/stamp.$(4): \ $$(LLVM_LIBDIR_RUSTFLAGS_$(2)) \ $$(LLVM_STDCPP_RUSTFLAGS_$(2)) \ $$(RUSTFLAGS_$(4)) \ - $$(RUSTFLAGS_$(4)_T_$(2)) \ + $$(RUSTFLAGS$(1)_$(4)_T_$(2)) \ --out-dir $$(@D) \ -C extra-filename=-$$(CFG_FILENAME_EXTRA) \ $$< diff --git a/src/compiletest/procsrv.rs b/src/compiletest/procsrv.rs index b30efaa6c29..878dc00e7b4 100644 --- a/src/compiletest/procsrv.rs +++ b/src/compiletest/procsrv.rs @@ -26,8 +26,7 @@ fn add_target_env(cmd: &mut Command, lib_path: &str, aux_path: Option<&str>) { // Add the new dylib search path var let var = DynamicLibrary::envvar(); let newpath = DynamicLibrary::create_path(&path); - let newpath = newpath.to_str().unwrap().to_string(); - cmd.env(var, &newpath); + cmd.env(var, newpath); } pub struct Result {pub status: ExitStatus, pub out: String, pub err: String} diff --git a/src/librustc/metadata/common.rs b/src/librustc/metadata/common.rs index 5f785fefa12..abcff6e78e2 100644 --- a/src/librustc/metadata/common.rs +++ b/src/librustc/metadata/common.rs @@ -205,8 +205,8 @@ pub const tag_plugin_registrar_fn: usize = 0x10b; // top-level only pub const tag_method_argument_names: usize = 0x85; pub const tag_method_argument_name: usize = 0x86; -pub const tag_reachable_extern_fns: usize = 0x10c; // top-level only -pub const tag_reachable_extern_fn_id: usize = 0x87; +pub const tag_reachable_ids: usize = 0x10c; // top-level only +pub const tag_reachable_id: usize = 0x87; pub const tag_items_data_item_stability: usize = 0x88; diff --git a/src/librustc/metadata/csearch.rs b/src/librustc/metadata/csearch.rs index efa17912a32..2ade251018f 100644 --- a/src/librustc/metadata/csearch.rs +++ b/src/librustc/metadata/csearch.rs @@ -352,11 +352,11 @@ pub fn get_method_arg_names(cstore: &cstore::CStore, did: ast::DefId) decoder::get_method_arg_names(&*cdata, did.node) } -pub fn get_reachable_extern_fns(cstore: &cstore::CStore, cnum: ast::CrateNum) +pub fn get_reachable_ids(cstore: &cstore::CStore, cnum: ast::CrateNum) -> Vec { let cdata = cstore.get_crate_data(cnum); - decoder::get_reachable_extern_fns(&*cdata) + decoder::get_reachable_ids(&*cdata) } pub fn is_typedef(cstore: &cstore::CStore, did: ast::DefId) -> bool { @@ -400,3 +400,9 @@ pub fn is_default_impl(cstore: &cstore::CStore, impl_did: ast::DefId) -> bool { let cdata = cstore.get_crate_data(impl_did.krate); decoder::is_default_impl(&*cdata, impl_did.node) } + +pub fn is_extern_fn(cstore: &cstore::CStore, did: ast::DefId, + tcx: &ty::ctxt) -> bool { + let cdata = cstore.get_crate_data(did.krate); + decoder::is_extern_fn(&*cdata, did.node, tcx) +} diff --git a/src/librustc/metadata/decoder.rs b/src/librustc/metadata/decoder.rs index df5f798217f..c6c18fa14a3 100644 --- a/src/librustc/metadata/decoder.rs +++ b/src/librustc/metadata/decoder.rs @@ -45,6 +45,7 @@ use std::str; use rbml::reader; use rbml; use serialize::Decodable; +use syntax::abi; use syntax::attr; use syntax::parse::token::{IdentInterner, special_idents}; use syntax::parse::token; @@ -1418,10 +1419,10 @@ pub fn get_method_arg_names(cdata: Cmd, id: ast::NodeId) -> Vec { } } -pub fn get_reachable_extern_fns(cdata: Cmd) -> Vec { +pub fn get_reachable_ids(cdata: Cmd) -> Vec { let items = reader::get_doc(rbml::Doc::new(cdata.data()), - tag_reachable_extern_fns); - reader::tagged_docs(items, tag_reachable_extern_fn_id).map(|doc| { + tag_reachable_ids); + reader::tagged_docs(items, tag_reachable_id).map(|doc| { ast::DefId { krate: cdata.cnum, node: reader::doc_as_u32(doc), @@ -1543,3 +1544,21 @@ pub fn get_imported_filemaps(metadata: &[u8]) -> Vec { Decodable::decode(&mut decoder).unwrap() }).collect() } + +pub fn is_extern_fn(cdata: Cmd, id: ast::NodeId, tcx: &ty::ctxt) -> bool { + let root_doc = rbml::Doc::new(cdata.data()); + let items = reader::get_doc(root_doc, tag_items); + let item_doc = match maybe_find_item(id, items) { + Some(doc) => doc, + None => return false, + }; + if let Fn = item_family(item_doc) { + let ty::TypeScheme { generics, ty } = get_type(cdata, id, tcx); + generics.types.is_empty() && match ty.sty { + ty::TyBareFn(_, fn_ty) => fn_ty.abi != abi::Rust, + _ => false, + } + } else { + false + } +} diff --git a/src/librustc/metadata/encoder.rs b/src/librustc/metadata/encoder.rs index d5c189ff044..e0f35b6817b 100644 --- a/src/librustc/metadata/encoder.rs +++ b/src/librustc/metadata/encoder.rs @@ -1781,9 +1781,8 @@ fn encode_crate_deps(rbml_w: &mut Encoder, cstore: &cstore::CStore) { // FIXME (#2166): This is not nearly enough to support correct versioning // but is enough to get transitive crate dependencies working. rbml_w.start_tag(tag_crate_deps); - let r = get_ordered_deps(cstore); - for dep in &r { - encode_crate_dep(rbml_w, (*dep).clone()); + for dep in &get_ordered_deps(cstore) { + encode_crate_dep(rbml_w, dep); } rbml_w.end_tag(); } @@ -1971,24 +1970,22 @@ fn encode_misc_info(ecx: &EncodeContext, rbml_w.end_tag(); } -fn encode_reachable_extern_fns(ecx: &EncodeContext, rbml_w: &mut Encoder) { - rbml_w.start_tag(tag_reachable_extern_fns); - +// Encodes all reachable symbols in this crate into the metadata. +// +// This pass is seeded off the reachability list calculated in the +// middle::reachable module but filters out items that either don't have a +// symbol associated with them (they weren't translated) or if they're an FFI +// definition (as that's not defined in this crate). +fn encode_reachable(ecx: &EncodeContext, rbml_w: &mut Encoder) { + rbml_w.start_tag(tag_reachable_ids); for id in ecx.reachable { - if let Some(ast_map::NodeItem(i)) = ecx.tcx.map.find(*id) { - if let ast::ItemFn(_, _, _, abi, ref generics, _) = i.node { - if abi != abi::Rust && !generics.is_type_parameterized() { - rbml_w.wr_tagged_u32(tag_reachable_extern_fn_id, *id); - } - } - } + rbml_w.wr_tagged_u32(tag_reachable_id, *id); } - rbml_w.end_tag(); } fn encode_crate_dep(rbml_w: &mut Encoder, - dep: decoder::CrateDep) { + dep: &decoder::CrateDep) { rbml_w.start_tag(tag_crate_dep); rbml_w.wr_tagged_str(tag_crate_dep_crate_name, &dep.name); rbml_w.wr_tagged_str(tag_crate_dep_hash, dep.hash.as_str()); @@ -2170,7 +2167,7 @@ fn encode_metadata_inner(wr: &mut Cursor>, // Encode miscellaneous info. i = rbml_w.writer.seek(SeekFrom::Current(0)).unwrap(); encode_misc_info(&ecx, krate, &mut rbml_w); - encode_reachable_extern_fns(&ecx, &mut rbml_w); + encode_reachable(&ecx, &mut rbml_w); stats.misc_bytes = rbml_w.writer.seek(SeekFrom::Current(0)).unwrap() - i; // Encode and index the items. diff --git a/src/librustc_llvm/lib.rs b/src/librustc_llvm/lib.rs index 9ee046915da..e0d585d6f5b 100644 --- a/src/librustc_llvm/lib.rs +++ b/src/librustc_llvm/lib.rs @@ -31,6 +31,7 @@ #![feature(link_args)] #![feature(staged_api)] #![feature(vec_push_all)] +#![cfg_attr(not(stage0), feature(linked_from))] extern crate libc; #[macro_use] #[no_link] extern crate rustc_bitflags; @@ -598,6 +599,7 @@ pub mod debuginfo { // automatically updated whenever LLVM is updated to include an up-to-date // set of the libraries we need to link to LLVM for. #[link(name = "rustllvm", kind = "static")] +#[cfg_attr(not(stage0), linked_from = "rustllvm")] // not quite true but good enough extern { /* Create and destroy contexts. */ pub fn LLVMContextCreate() -> ContextRef; diff --git a/src/librustc_trans/back/link.rs b/src/librustc_trans/back/link.rs index 3ab557bc1eb..46c7b80670f 100644 --- a/src/librustc_trans/back/link.rs +++ b/src/librustc_trans/back/link.rs @@ -902,6 +902,12 @@ fn link_args(cmd: &mut Linker, } cmd.output_filename(out_filename); + // If we're building a dynamic library then some platforms need to make sure + // that all symbols are exported correctly from the dynamic library. + if dylib { + cmd.export_symbols(sess, trans, tmpdir); + } + // When linking a dynamic library, we put the metadata into a section of the // executable. This metadata is in a separate object file from the main // object file, so we link that in here. diff --git a/src/librustc_trans/back/linker.rs b/src/librustc_trans/back/linker.rs index 3a709955098..8bd86a3a34a 100644 --- a/src/librustc_trans/back/linker.rs +++ b/src/librustc_trans/back/linker.rs @@ -9,14 +9,21 @@ // except according to those terms. use std::ffi::OsString; +use std::fs::{self, File}; +use std::io::{self, BufWriter}; +use std::io::prelude::*; use std::path::{Path, PathBuf}; use std::process::Command; -use std::fs; use back::archive; +use metadata::csearch; +use metadata::cstore; use session::Session; -use session::config; use session::config::DebugInfoLevel::{NoDebugInfo, LimitedDebugInfo, FullDebugInfo}; +use session::config::CrateTypeDylib; +use session::config; +use syntax::ast; +use trans::CrateTranslation; /// Linker abstraction used by back::link to build up the command to invoke a /// linker. @@ -48,6 +55,8 @@ pub trait Linker { fn hint_dynamic(&mut self); fn whole_archives(&mut self); fn no_whole_archives(&mut self); + fn export_symbols(&mut self, sess: &Session, trans: &CrateTranslation, + tmpdir: &Path); } pub struct GnuLinker<'a> { @@ -192,6 +201,10 @@ impl<'a> Linker for GnuLinker<'a> { if !self.takes_hints() { return } self.cmd.arg("-Wl,-Bdynamic"); } + + fn export_symbols(&mut self, _: &Session, _: &CrateTranslation, _: &Path) { + // noop, visibility in object files takes care of this + } } pub struct MsvcLinker<'a> { @@ -301,4 +314,61 @@ impl<'a> Linker for MsvcLinker<'a> { // we do on Unix platforms. fn hint_static(&mut self) {} fn hint_dynamic(&mut self) {} + + // 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 `trans.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, sess: &Session, trans: &CrateTranslation, + tmpdir: &Path) { + let path = tmpdir.join("lib.def"); + let res = (|| -> io::Result<()> { + let mut f = BufWriter::new(try!(File::create(&path))); + + // Start off with the standard module name header and then go + // straight to exports. + try!(writeln!(f, "LIBRARY")); + try!(writeln!(f, "EXPORTS")); + + // Write out all our local symbols + for sym in trans.reachable.iter() { + try!(writeln!(f, " {}", sym)); + } + + // Take a look at how all upstream crates are linked into this + // dynamic library. For all statically linked libraries we take all + // their reachable symbols and emit them as well. + let cstore = &sess.cstore; + let symbols = trans.crate_formats[&CrateTypeDylib].iter(); + let symbols = symbols.enumerate().filter_map(|(i, f)| { + if let Some(cstore::RequireStatic) = *f { + Some((i + 1) as ast::CrateNum) + } else { + None + } + }).flat_map(|cnum| { + csearch::get_reachable_ids(cstore, cnum) + }).map(|did| { + csearch::get_symbol(cstore, did) + }); + for symbol in symbols { + try!(writeln!(f, " {}", symbol)); + } + Ok(()) + })(); + if let Err(e) = res { + sess.fatal(&format!("failed to write lib.def file: {}", e)); + } + let mut arg = OsString::from("/DEF:"); + arg.push(path); + self.cmd.arg(&arg); + } } diff --git a/src/librustc_trans/trans/base.rs b/src/librustc_trans/trans/base.rs index 583c2c09085..716b1290817 100644 --- a/src/librustc_trans/trans/base.rs +++ b/src/librustc_trans/trans/base.rs @@ -81,7 +81,7 @@ use trans::type_of::*; use trans::value::Value; use util::common::indenter; use util::sha2::Sha256; -use util::nodemap::NodeMap; +use util::nodemap::{NodeMap, NodeSet}; use arena::TypedArena; use libc::c_uint; @@ -2007,17 +2007,11 @@ pub fn update_linkage(ccx: &CrateContext, match id { Some(id) if ccx.reachable().contains(&id) => { llvm::SetLinkage(llval, llvm::ExternalLinkage); - if ccx.use_dll_storage_attrs() { - llvm::SetDLLStorageClass(llval, llvm::DLLExportStorageClass); - } }, _ => { // `id` does not refer to an item in `ccx.reachable`. if ccx.sess().opts.cg.codegen_units > 1 { llvm::SetLinkage(llval, llvm::ExternalLinkage); - if ccx.use_dll_storage_attrs() { - llvm::SetDLLStorageClass(llval, llvm::DLLExportStorageClass); - } } else { llvm::SetLinkage(llval, llvm::InternalLinkage); } @@ -2158,28 +2152,12 @@ pub fn register_fn_llvmty(ccx: &CrateContext, ty::FnConverging(ccx.tcx().mk_nil())).unwrap_or_else(||{ ccx.sess().span_fatal(sp, &format!("symbol `{}` is already defined", sym)); }); - finish_register_fn(ccx, sym, node_id, llfn); + finish_register_fn(ccx, sym, node_id); llfn } -fn finish_register_fn(ccx: &CrateContext, sym: String, node_id: ast::NodeId, - llfn: ValueRef) { +fn finish_register_fn(ccx: &CrateContext, sym: String, node_id: ast::NodeId) { ccx.item_symbols().borrow_mut().insert(node_id, sym); - - // The eh_personality function need to be externally linkable. - let def = ast_util::local_def(node_id); - if ccx.tcx().lang_items.eh_personality() == Some(def) { - llvm::SetLinkage(llfn, llvm::ExternalLinkage); - if ccx.use_dll_storage_attrs() { - llvm::SetDLLStorageClass(llfn, llvm::DLLExportStorageClass); - } - } - if ccx.tcx().lang_items.eh_unwind_resume() == Some(def) { - llvm::SetLinkage(llfn, llvm::ExternalLinkage); - if ccx.use_dll_storage_attrs() { - llvm::SetDLLStorageClass(llfn, llvm::DLLExportStorageClass); - } - } } fn register_fn<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, @@ -2201,7 +2179,7 @@ fn register_fn<'a, 'tcx>(ccx: &CrateContext<'a, 'tcx>, let llfn = declare::define_rust_fn(ccx, &sym[..], node_type).unwrap_or_else(||{ ccx.sess().span_fatal(sp, &format!("symbol `{}` is already defined", sym)); }); - finish_register_fn(ccx, sym, node_id, llfn); + finish_register_fn(ccx, sym, node_id); llfn } @@ -2215,8 +2193,8 @@ pub fn is_entry_fn(sess: &Session, node_id: ast::NodeId) -> bool { /// Create the `main` function which will initialise the rust runtime and call users’ main /// function. pub fn create_entry_wrapper(ccx: &CrateContext, - sp: Span, - main_llfn: ValueRef) { + sp: Span, + main_llfn: ValueRef) { let et = ccx.sess().entry_type.get().unwrap(); match et { config::EntryMain => { @@ -2242,12 +2220,6 @@ pub fn create_entry_wrapper(ccx: &CrateContext, panic!(); }); - // FIXME: #16581: Marking a symbol in the executable with `dllexport` - // linkage forces MinGW's linker to output a `.reloc` section for ASLR - if ccx.sess().target.target.options.is_like_windows { - llvm::SetDLLStorageClass(llfn, llvm::DLLExportStorageClass); - } - let llbb = unsafe { llvm::LLVMAppendBasicBlockInContext(ccx.llcx(), llfn, "top\0".as_ptr() as *const _) @@ -2524,7 +2496,8 @@ fn register_method(ccx: &CrateContext, id: ast::NodeId, } pub fn crate_ctxt_to_encode_parms<'a, 'tcx>(cx: &'a SharedCrateContext<'a, 'tcx>, - ie: encoder::EncodeInlinedItem<'a>) + ie: encoder::EncodeInlinedItem<'a>, + reachable: &'a NodeSet) -> encoder::EncodeParams<'a, 'tcx> { encoder::EncodeParams { diag: cx.sess().diagnostic(), @@ -2534,11 +2507,12 @@ pub fn crate_ctxt_to_encode_parms<'a, 'tcx>(cx: &'a SharedCrateContext<'a, 'tcx> link_meta: cx.link_meta(), cstore: &cx.sess().cstore, encode_inlined_item: ie, - reachable: cx.reachable(), + reachable: reachable, } } -pub fn write_metadata(cx: &SharedCrateContext, krate: &ast::Crate) -> Vec { +pub fn write_metadata(cx: &SharedCrateContext, krate: &ast::Crate, + reachable: &NodeSet) -> Vec { use flate; let any_library = cx.sess().crate_types.borrow().iter().any(|ty| { @@ -2551,7 +2525,8 @@ pub fn write_metadata(cx: &SharedCrateContext, krate: &ast::Crate) -> Vec { let encode_inlined_item: encoder::EncodeInlinedItem = Box::new(|ecx, rbml_w, ii| astencode::encode_inlined_item(ecx, rbml_w, ii)); - let encode_parms = crate_ctxt_to_encode_parms(cx, encode_inlined_item); + let encode_parms = crate_ctxt_to_encode_parms(cx, encode_inlined_item, + reachable); let metadata = encoder::encode_metadata(encode_parms, krate); let mut compressed = encoder::metadata_encoding_version.to_vec(); compressed.push_all(&flate::deflate_bytes(&metadata)); @@ -2576,7 +2551,7 @@ pub fn write_metadata(cx: &SharedCrateContext, krate: &ast::Crate) -> Vec { /// Find any symbols that are defined in one compilation unit, but not declared /// in any other compilation unit. Give these symbols internal linkage. -fn internalize_symbols(cx: &SharedCrateContext, reachable: &HashSet) { +fn internalize_symbols(cx: &SharedCrateContext, reachable: &HashSet<&str>) { unsafe { let mut declared = HashSet::new(); @@ -2659,6 +2634,41 @@ fn internalize_symbols(cx: &SharedCrateContext, reachable: &HashSet) { } } +/// The context provided lists a set of reachable ids as calculated by +/// middle::reachable, but this contains far more ids and symbols than we're +/// actually exposing from the object file. This function will filter the set in +/// the context to the set of ids which correspond to symbols that are exposed +/// from the object file being generated. +/// +/// This list is later used by linkers to determine the set of symbols needed to +/// be exposed from a dynamic library and it's also encoded into the metadata. +pub fn filter_reachable_ids(ccx: &SharedCrateContext) -> NodeSet { + ccx.reachable().iter().map(|x| *x).filter(|id| { + // First, only worry about nodes which have a symbol name + ccx.item_symbols().borrow().contains_key(id) + }).filter(|&id| { + // Next, we want to ignore some FFI functions that are not exposed from + // this crate. Reachable FFI functions can be lumped into two + // categories: + // + // 1. Those that are included statically via a static library + // 2. Those included otherwise (e.g. dynamically or via a framework) + // + // Although our LLVM module is not literally emitting code for the + // statically included symbols, it's an export of our library which + // needs to be passed on to the linker and encoded in the metadata. + // + // As a result, if this id is an FFI item (foreign item) then we only + // let it through if it's included statically. + match ccx.tcx().map.get(id) { + ast_map::NodeForeignItem(..) => { + ccx.sess().cstore.is_statically_included_foreign_item(id) + } + _ => true, + } + }).collect() +} + pub fn trans_crate(tcx: &ty::ctxt, analysis: ty::CrateAnalysis) -> CrateTranslation { let ty::CrateAnalysis { export_map, reachable, name, .. } = analysis; let krate = tcx.map.krate(); @@ -2734,8 +2744,10 @@ pub fn trans_crate(tcx: &ty::ctxt, analysis: ty::CrateAnalysis) -> CrateTranslat } } + let reachable_symbol_ids = filter_reachable_ids(&shared_ccx); + // Translate the metadata. - let metadata = write_metadata(&shared_ccx, krate); + let metadata = write_metadata(&shared_ccx, krate, &reachable_symbol_ids); if shared_ccx.sess().trans_stats() { let stats = shared_ccx.stats(); @@ -2770,31 +2782,31 @@ pub fn trans_crate(tcx: &ty::ctxt, analysis: ty::CrateAnalysis) -> CrateTranslat .map(|ccx| ModuleTranslation { llcx: ccx.llcx(), llmod: ccx.llmod() }) .collect(); - let mut reachable: Vec = shared_ccx.reachable().iter().filter_map(|id| { - shared_ccx.item_symbols().borrow().get(id).map(|s| s.to_string()) - }).collect(); + let sess = shared_ccx.sess(); + let mut reachable_symbols = reachable_symbol_ids.iter().map(|id| { + shared_ccx.item_symbols().borrow()[id].to_string() + }).collect::>(); + if sess.entry_fn.borrow().is_some() { + reachable_symbols.push("main".to_string()); + } // For the purposes of LTO, we add to the reachable set all of the upstream // reachable extern fns. These functions are all part of the public ABI of // the final product, so LTO needs to preserve them. - shared_ccx.sess().cstore.iter_crate_data(|cnum, _| { - let syms = csearch::get_reachable_extern_fns(&shared_ccx.sess().cstore, cnum); - reachable.extend(syms.into_iter().map(|did| { - csearch::get_symbol(&shared_ccx.sess().cstore, did) - })); - }); - - // Make sure that some other crucial symbols are not eliminated from the - // module, including the main function. - reachable.push("main".to_string()); - - // referenced from .eh_frame section on some platforms - reachable.push("rust_eh_personality".to_string()); - // referenced from rt/rust_try.ll - reachable.push("rust_eh_personality_catch".to_string()); + if sess.lto() { + sess.cstore.iter_crate_data(|cnum, _| { + let syms = csearch::get_reachable_ids(&sess.cstore, cnum); + reachable_symbols.extend(syms.into_iter().filter(|did| { + csearch::is_extern_fn(&sess.cstore, *did, shared_ccx.tcx()) + }).map(|did| { + csearch::get_symbol(&sess.cstore, did) + })); + }); + } if codegen_units > 1 { - internalize_symbols(&shared_ccx, &reachable.iter().cloned().collect()); + internalize_symbols(&shared_ccx, + &reachable_symbols.iter().map(|x| &x[..]).collect()); } let metadata_module = ModuleTranslation { @@ -2809,7 +2821,7 @@ pub fn trans_crate(tcx: &ty::ctxt, analysis: ty::CrateAnalysis) -> CrateTranslat metadata_module: metadata_module, link: link_meta, metadata: metadata, - reachable: reachable, + reachable: reachable_symbols, crate_formats: formats, no_builtins: no_builtins, } diff --git a/src/test/auxiliary/issue-25185-1.rs b/src/test/auxiliary/issue-25185-1.rs index b9da39cbbcb..1ec29501b76 100644 --- a/src/test/auxiliary/issue-25185-1.rs +++ b/src/test/auxiliary/issue-25185-1.rs @@ -10,9 +10,12 @@ // no-prefer-dynamic +#![feature(linked_from)] + #![crate_type = "rlib"] #[link(name = "rust_test_helpers", kind = "static")] +#[linked_from = "rust_test_helpers"] extern { pub fn rust_dbg_extern_identity_u32(u: u32) -> u32; } diff --git a/src/test/compile-fail/feature-gate-linked-from.rs b/src/test/compile-fail/feature-gate-linked-from.rs new file mode 100644 index 00000000000..8705684111e --- /dev/null +++ b/src/test/compile-fail/feature-gate-linked-from.rs @@ -0,0 +1,16 @@ +// Copyright 2015 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#[linked_from = "foo"] //~ ERROR experimental feature +extern { + fn foo(); +} + +fn main() {} diff --git a/src/test/run-pass/variadic-ffi.rs b/src/test/run-pass/variadic-ffi.rs index fd70c0409fb..6351cc76a2e 100644 --- a/src/test/run-pass/variadic-ffi.rs +++ b/src/test/run-pass/variadic-ffi.rs @@ -8,6 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +// ignore-msvc -- sprintf isn't a symbol in msvcrt? maybe a #define? #![feature(libc, std_misc)]