about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2016-12-05 20:53:04 +0000
committerbors <bors@rust-lang.org>2016-12-05 20:53:04 +0000
commitdaf8c1dfce3b448fc581cc319f64632ec22bd0e1 (patch)
tree738b0a50c9e964ded18a0dd406902b8ca17e5b3f
parent06b8d1d3611dc9ebd37dd464b39d5d1565bbcd96 (diff)
parent8ecdc4ee3bde4a7986bafe4010f5358d89a4eeab (diff)
downloadrust-daf8c1dfce3b448fc581cc319f64632ec22bd0e1.tar.gz
rust-daf8c1dfce3b448fc581cc319f64632ec22bd0e1.zip
Auto merge of #38117 - michaelwoerister:hidden-symbols, r=alexcrichton
Improve symbol visibility handling for dynamic libraries.

This will hopefully fix issue https://github.com/rust-lang/rust/issues/37530 and maybe also https://github.com/rust-lang/rust/issues/32887.

I'm relying on @m4b to post some numbers on how awesome the improvement for cdylibs is `:)`

cc @rust-lang/compiler @rust-lang/tools @cuviper @froydnj
r? @alexcrichton
-rw-r--r--src/librustc/middle/cstore.rs7
-rw-r--r--src/librustc_llvm/ffi.rs19
-rw-r--r--src/librustc_metadata/cstore_impl.rs12
-rw-r--r--src/librustc_metadata/decoder.rs4
-rw-r--r--src/librustc_metadata/encoder.rs24
-rw-r--r--src/librustc_metadata/schema.rs2
-rw-r--r--src/librustc_trans/back/linker.rs99
-rw-r--r--src/librustc_trans/back/lto.rs67
-rw-r--r--src/librustc_trans/back/symbol_export.rs193
-rw-r--r--src/librustc_trans/back/write.rs19
-rw-r--r--src/librustc_trans/base.rs226
-rw-r--r--src/librustc_trans/context.rs16
-rw-r--r--src/librustc_trans/debuginfo/utils.rs2
-rw-r--r--src/librustc_trans/declare.rs2
-rw-r--r--src/librustc_trans/lib.rs3
-rw-r--r--src/rustllvm/RustWrapper.cpp42
-rw-r--r--src/test/run-make/sepcomp-inlining/Makefile4
-rw-r--r--src/test/run-make/symbol-visibility/Makefile50
-rw-r--r--src/test/run-make/symbol-visibility/a_cdylib.rs22
-rw-r--r--src/test/run-make/symbol-visibility/a_rust_dylib.rs20
-rw-r--r--src/test/run-make/symbol-visibility/an_executable.rs17
-rw-r--r--src/test/run-make/symbol-visibility/an_rlib.rs16
22 files changed, 623 insertions, 243 deletions
diff --git a/src/librustc/middle/cstore.rs b/src/librustc/middle/cstore.rs
index 822fb4d6770..4357518b332 100644
--- a/src/librustc/middle/cstore.rs
+++ b/src/librustc/middle/cstore.rs
@@ -328,8 +328,9 @@ pub trait CrateStore<'tcx> {
     fn crate_hash(&self, cnum: CrateNum) -> Svh;
     fn crate_disambiguator(&self, cnum: CrateNum) -> Symbol;
     fn plugin_registrar_fn(&self, cnum: CrateNum) -> Option<DefId>;
+    fn derive_registrar_fn(&self, cnum: CrateNum) -> Option<DefId>;
     fn native_libraries(&self, cnum: CrateNum) -> Vec<NativeLibrary>;
-    fn reachable_ids(&self, cnum: CrateNum) -> Vec<DefId>;
+    fn exported_symbols(&self, cnum: CrateNum) -> Vec<DefId>;
     fn is_no_builtins(&self, cnum: CrateNum) -> bool;
 
     // resolve
@@ -491,9 +492,11 @@ impl<'tcx> CrateStore<'tcx> for DummyCrateStore {
                            -> Symbol { bug!("crate_disambiguator") }
     fn plugin_registrar_fn(&self, cnum: CrateNum) -> Option<DefId>
         { bug!("plugin_registrar_fn") }
+    fn derive_registrar_fn(&self, cnum: CrateNum) -> Option<DefId>
+        { bug!("derive_registrar_fn") }
     fn native_libraries(&self, cnum: CrateNum) -> Vec<NativeLibrary>
         { bug!("native_libraries") }
-    fn reachable_ids(&self, cnum: CrateNum) -> Vec<DefId> { bug!("reachable_ids") }
+    fn exported_symbols(&self, cnum: CrateNum) -> Vec<DefId> { bug!("exported_symbols") }
     fn is_no_builtins(&self, cnum: CrateNum) -> bool { bug!("is_no_builtins") }
 
     // resolve
diff --git a/src/librustc_llvm/ffi.rs b/src/librustc_llvm/ffi.rs
index b8f1540ad84..d2592a3acee 100644
--- a/src/librustc_llvm/ffi.rs
+++ b/src/librustc_llvm/ffi.rs
@@ -64,6 +64,15 @@ pub enum Linkage {
     CommonLinkage = 10,
 }
 
+// LLVMRustVisibility
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+#[repr(C)]
+pub enum Visibility {
+    Default = 0,
+    Hidden = 1,
+    Protected = 2,
+}
+
 /// LLVMDiagnosticSeverity
 #[derive(Copy, Clone, Debug)]
 #[repr(C)]
@@ -399,13 +408,6 @@ pub type OperandBundleDefRef = *mut OperandBundleDef_opaque;
 pub type DiagnosticHandler = unsafe extern "C" fn(DiagnosticInfoRef, *mut c_void);
 pub type InlineAsmDiagHandler = unsafe extern "C" fn(SMDiagnosticRef, *const c_void, c_uint);
 
-/// LLVMVisibility
-#[repr(C)]
-pub enum Visibility {
-    Default,
-    Hidden,
-    Protected,
-}
 
 pub mod debuginfo {
     use super::MetadataRef;
@@ -655,7 +657,8 @@ extern "C" {
     pub fn LLVMRustSetLinkage(Global: ValueRef, RustLinkage: Linkage);
     pub fn LLVMGetSection(Global: ValueRef) -> *const c_char;
     pub fn LLVMSetSection(Global: ValueRef, Section: *const c_char);
-    pub fn LLVMSetVisibility(Global: ValueRef, Viz: Visibility);
+    pub fn LLVMRustGetVisibility(Global: ValueRef) -> Visibility;
+    pub fn LLVMRustSetVisibility(Global: ValueRef, Viz: Visibility);
     pub fn LLVMGetAlignment(Global: ValueRef) -> c_uint;
     pub fn LLVMSetAlignment(Global: ValueRef, Bytes: c_uint);
     pub fn LLVMSetDLLStorageClass(V: ValueRef, C: DLLStorageClass);
diff --git a/src/librustc_metadata/cstore_impl.rs b/src/librustc_metadata/cstore_impl.rs
index 3150f74e61e..aca1d2f7612 100644
--- a/src/librustc_metadata/cstore_impl.rs
+++ b/src/librustc_metadata/cstore_impl.rs
@@ -306,14 +306,22 @@ impl<'tcx> CrateStore<'tcx> for cstore::CStore {
         })
     }
 
+    fn derive_registrar_fn(&self, cnum: CrateNum) -> Option<DefId>
+    {
+        self.get_crate_data(cnum).root.macro_derive_registrar.map(|index| DefId {
+            krate: cnum,
+            index: index
+        })
+    }
+
     fn native_libraries(&self, cnum: CrateNum) -> Vec<NativeLibrary>
     {
         self.get_crate_data(cnum).get_native_libraries()
     }
 
-    fn reachable_ids(&self, cnum: CrateNum) -> Vec<DefId>
+    fn exported_symbols(&self, cnum: CrateNum) -> Vec<DefId>
     {
-        self.get_crate_data(cnum).get_reachable_ids()
+        self.get_crate_data(cnum).get_exported_symbols()
     }
 
     fn is_no_builtins(&self, cnum: CrateNum) -> bool {
diff --git a/src/librustc_metadata/decoder.rs b/src/librustc_metadata/decoder.rs
index fe536b69c61..43635eae76c 100644
--- a/src/librustc_metadata/decoder.rs
+++ b/src/librustc_metadata/decoder.rs
@@ -1038,8 +1038,8 @@ impl<'a, 'tcx> CrateMetadata {
         arg_names.decode(self).collect()
     }
 
-    pub fn get_reachable_ids(&self) -> Vec<DefId> {
-        self.root.reachable_ids.decode(self).map(|index| self.local_def_id(index)).collect()
+    pub fn get_exported_symbols(&self) -> Vec<DefId> {
+        self.root.exported_symbols.decode(self).map(|index| self.local_def_id(index)).collect()
     }
 
     pub fn get_macro(&self, id: DefIndex) -> (ast::Name, MacroDef) {
diff --git a/src/librustc_metadata/encoder.rs b/src/librustc_metadata/encoder.rs
index 83904b24de3..01cb0f823e8 100644
--- a/src/librustc_metadata/encoder.rs
+++ b/src/librustc_metadata/encoder.rs
@@ -50,7 +50,7 @@ pub struct EncodeContext<'a, 'tcx: 'a> {
     reexports: &'a def::ExportMap,
     link_meta: &'a LinkMeta,
     cstore: &'a cstore::CStore,
-    reachable: &'a NodeSet,
+    exported_symbols: &'a NodeSet,
 
     lazy_state: LazyState,
     type_shorthands: FxHashMap<Ty<'tcx>, usize>,
@@ -1223,16 +1223,16 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
         self.lazy_seq(all_impls)
     }
 
-    // Encodes all reachable symbols in this crate into the metadata.
+    // Encodes all symbols exported from 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(&mut self) -> LazySeq<DefIndex> {
-        let reachable = self.reachable;
+    fn encode_exported_symbols(&mut self) -> LazySeq<DefIndex> {
+        let exported_symbols = self.exported_symbols;
         let tcx = self.tcx;
-        self.lazy_seq(reachable.iter().map(|&id| tcx.map.local_def_id(id).index))
+        self.lazy_seq(exported_symbols.iter().map(|&id| tcx.map.local_def_id(id).index))
     }
 
     fn encode_dylib_dependency_formats(&mut self) -> LazySeq<Option<LinkagePreference>> {
@@ -1278,10 +1278,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
         let impls = self.encode_impls();
         let impl_bytes = self.position() - i;
 
-        // Encode reachability info.
+        // Encode exported symbols info.
         i = self.position();
-        let reachable_ids = self.encode_reachable();
-        let reachable_bytes = self.position() - i;
+        let exported_symbols = self.encode_exported_symbols();
+        let exported_symbols_bytes = self.position() - i;
 
         // Encode and index the items.
         i = self.position();
@@ -1319,7 +1319,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
             native_libraries: native_libraries,
             codemap: codemap,
             impls: impls,
-            reachable_ids: reachable_ids,
+            exported_symbols: exported_symbols,
             index: index,
         });
 
@@ -1339,7 +1339,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
             println!("          native bytes: {}", native_lib_bytes);
             println!("         codemap bytes: {}", codemap_bytes);
             println!("            impl bytes: {}", impl_bytes);
-            println!("       reachable bytes: {}", reachable_bytes);
+            println!("    exp. symbols bytes: {}", exported_symbols_bytes);
             println!("            item bytes: {}", item_bytes);
             println!("           index bytes: {}", index_bytes);
             println!("            zero bytes: {}", zero_bytes);
@@ -1377,7 +1377,7 @@ pub fn encode_metadata<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
                                  cstore: &cstore::CStore,
                                  reexports: &def::ExportMap,
                                  link_meta: &LinkMeta,
-                                 reachable: &NodeSet)
+                                 exported_symbols: &NodeSet)
                                  -> Vec<u8> {
     let mut cursor = Cursor::new(vec![]);
     cursor.write_all(METADATA_HEADER).unwrap();
@@ -1392,7 +1392,7 @@ pub fn encode_metadata<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
             reexports: reexports,
             link_meta: link_meta,
             cstore: cstore,
-            reachable: reachable,
+            exported_symbols: exported_symbols,
             lazy_state: LazyState::NoNode,
             type_shorthands: Default::default(),
             predicate_shorthands: Default::default(),
diff --git a/src/librustc_metadata/schema.rs b/src/librustc_metadata/schema.rs
index 00c3709435d..f92051cbf19 100644
--- a/src/librustc_metadata/schema.rs
+++ b/src/librustc_metadata/schema.rs
@@ -180,7 +180,7 @@ pub struct CrateRoot {
     pub native_libraries: LazySeq<NativeLibrary>,
     pub codemap: LazySeq<syntax_pos::FileMap>,
     pub impls: LazySeq<TraitImpls>,
-    pub reachable_ids: LazySeq<DefIndex>,
+    pub exported_symbols: LazySeq<DefIndex>,
     pub index: LazySeq<index::Index>,
 }
 
diff --git a/src/librustc_trans/back/linker.rs b/src/librustc_trans/back/linker.rs
index 860903d259f..d59ee5d825d 100644
--- a/src/librustc_trans/back/linker.rs
+++ b/src/librustc_trans/back/linker.rs
@@ -17,11 +17,11 @@ use std::path::{Path, PathBuf};
 use std::process::Command;
 
 use context::SharedCrateContext;
-use monomorphize::Instance;
 
 use back::archive;
+use back::symbol_export::{self, ExportedSymbols};
 use middle::dependency_format::Linkage;
-use rustc::hir::def_id::CrateNum;
+use rustc::hir::def_id::{LOCAL_CRATE, CrateNum};
 use session::Session;
 use session::config::CrateType;
 use session::config;
@@ -34,10 +34,10 @@ pub struct LinkerInfo {
 
 impl<'a, 'tcx> LinkerInfo {
     pub fn new(scx: &SharedCrateContext<'a, 'tcx>,
-               reachable: &[String]) -> LinkerInfo {
+               exports: &ExportedSymbols) -> LinkerInfo {
         LinkerInfo {
             exports: scx.sess().crate_types.borrow().iter().map(|&c| {
-                (c, exported_symbols(scx, reachable, c))
+                (c, exported_symbols(scx, exports, c))
             }).collect(),
         }
     }
@@ -253,46 +253,47 @@ impl<'a> Linker for GnuLinker<'a> {
         let mut arg = OsString::new();
         let path = tmpdir.join("list");
 
-        if self.sess.target.target.options.is_like_solaris {
+        debug!("EXPORTED SYMBOLS:");
+
+        if self.sess.target.target.options.is_like_osx {
+            // Write a plain, newline-separated list of symbols
             let res = (|| -> io::Result<()> {
                 let mut f = BufWriter::new(File::create(&path)?);
-                writeln!(f, "{{\n  global:")?;
                 for sym in self.info.exports[&crate_type].iter() {
-                    writeln!(f, "    {};", sym)?;
+                    debug!("  _{}", sym);
+                    writeln!(f, "_{}", sym)?;
                 }
-                writeln!(f, "\n  local:\n    *;\n}};")?;
                 Ok(())
             })();
             if let Err(e) = res {
-                self.sess.fatal(&format!("failed to write version script: {}", e));
+                self.sess.fatal(&format!("failed to write lib.def file: {}", e));
             }
-
-            arg.push("-Wl,-M,");
-            arg.push(&path);
         } else {
-            let prefix = if self.sess.target.target.options.is_like_osx {
-                "_"
-            } else {
-                ""
-            };
+            // Write an LD version script
             let res = (|| -> io::Result<()> {
                 let mut f = BufWriter::new(File::create(&path)?);
+                writeln!(f, "{{\n  global:")?;
                 for sym in self.info.exports[&crate_type].iter() {
-                    writeln!(f, "{}{}", prefix, sym)?;
+                    debug!("    {};", sym);
+                    writeln!(f, "    {};", sym)?;
                 }
+                writeln!(f, "\n  local:\n    *;\n}};")?;
                 Ok(())
             })();
             if let Err(e) = res {
-                self.sess.fatal(&format!("failed to write lib.def file: {}", e));
-            }
-            if self.sess.target.target.options.is_like_osx {
-                arg.push("-Wl,-exported_symbols_list,");
-            } else {
-                arg.push("-Wl,--retain-symbols-file=");
+                self.sess.fatal(&format!("failed to write version script: {}", e));
             }
-            arg.push(&path);
         }
 
+        if self.sess.target.target.options.is_like_osx {
+            arg.push("-Wl,-exported_symbols_list,");
+        } else if self.sess.target.target.options.is_like_solaris {
+            arg.push("-Wl,-M,");
+        } else {
+            arg.push("-Wl,--version-script=");
+        }
+
+        arg.push(&path);
         self.cmd.arg(arg);
     }
 
@@ -473,43 +474,29 @@ impl<'a> Linker for MsvcLinker<'a> {
 }
 
 fn exported_symbols(scx: &SharedCrateContext,
-                    reachable: &[String],
+                    exported_symbols: &ExportedSymbols,
                     crate_type: CrateType)
                     -> Vec<String> {
-    // See explanation in GnuLinker::export_symbols, for
-    // why we don't ever need dylib symbols on non-MSVC.
-    if crate_type == CrateType::CrateTypeDylib ||
-       crate_type == CrateType::CrateTypeProcMacro {
-        if !scx.sess().target.target.options.is_like_msvc {
-            return vec![];
-        }
-    }
+    let export_threshold = symbol_export::crate_export_threshold(crate_type);
 
-    let mut symbols = reachable.to_vec();
+    let mut symbols = Vec::new();
+    exported_symbols.for_each_exported_symbol(LOCAL_CRATE, export_threshold, |name, _| {
+        symbols.push(name.to_owned());
+    });
 
-    // If we're producing anything other than a dylib then the `reachable` array
-    // above is the exhaustive set of symbols we should be exporting.
-    //
-    // For dylibs, however, we need to 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.
-    if crate_type != CrateType::CrateTypeDylib {
-        return symbols
-    }
-
-    let cstore = &scx.sess().cstore;
     let formats = scx.sess().dependency_formats.borrow();
     let deps = formats[&crate_type].iter();
-    symbols.extend(deps.enumerate().filter_map(|(i, f)| {
-        if *f == Linkage::Static {
-            Some(CrateNum::new(i + 1))
-        } else {
-            None
+
+    for (index, dep_format) in deps.enumerate() {
+        let cnum = CrateNum::new(index + 1);
+        // For each dependency that we are linking to statically ...
+        if *dep_format == Linkage::Static {
+            // ... we add its symbol list to our export list.
+            exported_symbols.for_each_exported_symbol(cnum, export_threshold, |name, _| {
+                symbols.push(name.to_owned());
+            })
         }
-    }).flat_map(|cnum| {
-        cstore.reachable_ids(cnum)
-    }).map(|did| -> String {
-        Instance::mono(scx, did).symbol_name(scx)
-    }));
+    }
+
     symbols
 }
diff --git a/src/librustc_trans/back/lto.rs b/src/librustc_trans/back/lto.rs
index 522864c6ec3..f137bfff034 100644
--- a/src/librustc_trans/back/lto.rs
+++ b/src/librustc_trans/back/lto.rs
@@ -8,14 +8,16 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use super::link;
-use super::write;
+use back::link;
+use back::write;
+use back::symbol_export::{self, ExportedSymbols};
 use rustc::session::{self, config};
 use llvm;
 use llvm::archive_ro::ArchiveRO;
 use llvm::{ModuleRef, TargetMachineRef, True, False};
 use rustc::util::common::time;
 use rustc::util::common::path2cstr;
+use rustc::hir::def_id::LOCAL_CRATE;
 use back::write::{ModuleConfig, with_llvm_pmb};
 
 use libc;
@@ -24,8 +26,23 @@ use flate;
 use std::ffi::CString;
 use std::path::Path;
 
-pub fn run(sess: &session::Session, llmod: ModuleRef,
-           tm: TargetMachineRef, reachable: &[String],
+pub fn crate_type_allows_lto(crate_type: config::CrateType) -> bool {
+    match crate_type {
+        config::CrateTypeExecutable |
+        config::CrateTypeStaticlib  |
+        config::CrateTypeCdylib     => true,
+
+        config::CrateTypeDylib     |
+        config::CrateTypeRlib      |
+        config::CrateTypeMetadata  |
+        config::CrateTypeProcMacro => false,
+    }
+}
+
+pub fn run(sess: &session::Session,
+           llmod: ModuleRef,
+           tm: TargetMachineRef,
+           exported_symbols: &ExportedSymbols,
            config: &ModuleConfig,
            temp_no_opt_bc_filename: &Path) {
     if sess.opts.cg.prefer_dynamic {
@@ -38,17 +55,31 @@ pub fn run(sess: &session::Session, llmod: ModuleRef,
 
     // Make sure we actually can run LTO
     for crate_type in sess.crate_types.borrow().iter() {
-        match *crate_type {
-            config::CrateTypeExecutable |
-            config::CrateTypeCdylib |
-            config::CrateTypeStaticlib => {}
-            _ => {
-                sess.fatal("lto can only be run for executables and \
+        if !crate_type_allows_lto(*crate_type) {
+            sess.fatal("lto can only be run for executables, cdylibs and \
                             static library outputs");
-            }
         }
     }
 
+    let export_threshold =
+        symbol_export::crates_export_threshold(&sess.crate_types.borrow()[..]);
+
+    let symbol_filter = &|&(ref name, level): &(String, _)| {
+        if symbol_export::is_below_threshold(level, export_threshold) {
+            let mut bytes = Vec::with_capacity(name.len() + 1);
+            bytes.extend(name.bytes());
+            Some(CString::new(bytes).unwrap())
+        } else {
+            None
+        }
+    };
+
+    let mut symbol_white_list: Vec<CString> = exported_symbols
+        .exported_symbols(LOCAL_CRATE)
+        .iter()
+        .filter_map(symbol_filter)
+        .collect();
+
     // For each of our upstream dependencies, find the corresponding rlib and
     // load the bitcode from the archive. Then merge it into the current LLVM
     // module that we've got.
@@ -58,6 +89,11 @@ pub fn run(sess: &session::Session, llmod: ModuleRef,
             return;
         }
 
+        symbol_white_list.extend(
+            exported_symbols.exported_symbols(cnum)
+                            .iter()
+                            .filter_map(symbol_filter));
+
         let archive = ArchiveRO::open(&path).expect("wanted an rlib");
         let bytecodes = archive.iter().filter_map(|child| {
             child.ok().and_then(|c| c.name().map(|name| (name, c)))
@@ -118,11 +154,10 @@ pub fn run(sess: &session::Session, llmod: ModuleRef,
         }
     });
 
-    // Internalize everything but the reachable symbols of the current module
-    let cstrs: Vec<CString> = reachable.iter().map(|s| {
-        CString::new(s.clone()).unwrap()
-    }).collect();
-    let arr: Vec<*const libc::c_char> = cstrs.iter().map(|c| c.as_ptr()).collect();
+    // Internalize everything but the exported symbols of the current module
+    let arr: Vec<*const libc::c_char> = symbol_white_list.iter()
+                                                         .map(|c| c.as_ptr())
+                                                         .collect();
     let ptr = arr.as_ptr();
     unsafe {
         llvm::LLVMRustRunRestrictionPass(llmod,
diff --git a/src/librustc_trans/back/symbol_export.rs b/src/librustc_trans/back/symbol_export.rs
new file mode 100644
index 00000000000..f99f543d9b7
--- /dev/null
+++ b/src/librustc_trans/back/symbol_export.rs
@@ -0,0 +1,193 @@
+// Copyright 2016 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use context::SharedCrateContext;
+use monomorphize::Instance;
+use symbol_map::SymbolMap;
+use util::nodemap::FxHashMap;
+use rustc::hir::def_id::{DefId, CrateNum, LOCAL_CRATE};
+use rustc::session::config;
+use syntax::attr;
+use trans_item::TransItem;
+
+/// The SymbolExportLevel of a symbols specifies from which kinds of crates
+/// the symbol will be exported. `C` symbols will be exported from any
+/// kind of crate, including cdylibs which export very few things.
+/// `Rust` will only be exported if the crate produced is a Rust
+/// dylib.
+#[derive(Eq, PartialEq, Debug, Copy, Clone)]
+pub enum SymbolExportLevel {
+    C,
+    Rust,
+}
+
+/// The set of symbols exported from each crate in the crate graph.
+pub struct ExportedSymbols {
+    exports: FxHashMap<CrateNum, Vec<(String, SymbolExportLevel)>>,
+}
+
+impl ExportedSymbols {
+
+    pub fn empty() -> ExportedSymbols {
+        ExportedSymbols {
+            exports: FxHashMap(),
+        }
+    }
+
+    pub fn compute_from<'a, 'tcx>(scx: &SharedCrateContext<'a, 'tcx>,
+                                  symbol_map: &SymbolMap<'tcx>)
+                                  -> ExportedSymbols {
+        let mut local_crate: Vec<_> = scx
+            .exported_symbols()
+            .iter()
+            .map(|&node_id| {
+                scx.tcx().map.local_def_id(node_id)
+            })
+            .map(|def_id| {
+                (symbol_for_def_id(scx, def_id, symbol_map),
+                 export_level(scx, def_id))
+            })
+            .collect();
+
+        if scx.sess().entry_fn.borrow().is_some() {
+            local_crate.push(("main".to_string(), SymbolExportLevel::C));
+        }
+
+        if let Some(id) = scx.sess().derive_registrar_fn.get() {
+            let svh = &scx.link_meta().crate_hash;
+            let def_id = scx.tcx().map.local_def_id(id);
+            let idx = def_id.index;
+            let registrar = scx.sess().generate_derive_registrar_symbol(svh, idx);
+            local_crate.push((registrar, SymbolExportLevel::C));
+        }
+
+        if scx.sess().crate_types.borrow().contains(&config::CrateTypeDylib) {
+            local_crate.push((scx.metadata_symbol_name(),
+                              SymbolExportLevel::Rust));
+        }
+
+        let mut exports = FxHashMap();
+        exports.insert(LOCAL_CRATE, local_crate);
+
+        for cnum in scx.sess().cstore.crates() {
+            debug_assert!(cnum != LOCAL_CRATE);
+
+            if scx.sess().cstore.plugin_registrar_fn(cnum).is_some() ||
+               scx.sess().cstore.derive_registrar_fn(cnum).is_some() {
+                continue;
+            }
+
+            let crate_exports = scx
+                .sess()
+                .cstore
+                .exported_symbols(cnum)
+                .iter()
+                .map(|&def_id| {
+                    debug!("EXTERN-SYMBOL: {:?}", def_id);
+                    let name = Instance::mono(scx, def_id).symbol_name(scx);
+                    (name, export_level(scx, def_id))
+                })
+                .collect();
+
+            exports.insert(cnum, crate_exports);
+        }
+
+        return ExportedSymbols {
+            exports: exports
+        };
+
+        fn export_level(scx: &SharedCrateContext,
+                        sym_def_id: DefId)
+                        -> SymbolExportLevel {
+            let attrs = scx.tcx().get_attrs(sym_def_id);
+            if attr::contains_extern_indicator(scx.sess().diagnostic(), &attrs) {
+                SymbolExportLevel::C
+            } else {
+                SymbolExportLevel::Rust
+            }
+        }
+    }
+
+    pub fn exported_symbols(&self,
+                            cnum: CrateNum)
+                            -> &[(String, SymbolExportLevel)] {
+        match self.exports.get(&cnum) {
+            Some(exports) => &exports[..],
+            None => &[]
+        }
+    }
+
+    pub fn for_each_exported_symbol<F>(&self,
+                                       cnum: CrateNum,
+                                       export_threshold: SymbolExportLevel,
+                                       mut f: F)
+        where F: FnMut(&str, SymbolExportLevel)
+    {
+        for &(ref name, export_level) in self.exported_symbols(cnum) {
+            if is_below_threshold(export_level, export_threshold) {
+                f(&name[..], export_level)
+            }
+        }
+    }
+}
+
+pub fn crate_export_threshold(crate_type: config::CrateType)
+                                     -> SymbolExportLevel {
+    match crate_type {
+        config::CrateTypeExecutable |
+        config::CrateTypeStaticlib  |
+        config::CrateTypeProcMacro  |
+        config::CrateTypeCdylib     => SymbolExportLevel::C,
+        config::CrateTypeRlib       |
+        config::CrateTypeMetadata   |
+        config::CrateTypeDylib      => SymbolExportLevel::Rust,
+    }
+}
+
+pub fn crates_export_threshold(crate_types: &[config::CrateType])
+                                      -> SymbolExportLevel {
+    if crate_types.iter().any(|&crate_type| {
+        crate_export_threshold(crate_type) == SymbolExportLevel::Rust
+    }) {
+        SymbolExportLevel::Rust
+    } else {
+        SymbolExportLevel::C
+    }
+}
+
+pub fn is_below_threshold(level: SymbolExportLevel,
+                          threshold: SymbolExportLevel)
+                          -> bool {
+    if threshold == SymbolExportLevel::Rust {
+        // We export everything from Rust dylibs
+        true
+    } else {
+        level == SymbolExportLevel::C
+    }
+}
+
+fn symbol_for_def_id<'a, 'tcx>(scx: &SharedCrateContext<'a, 'tcx>,
+                               def_id: DefId,
+                               symbol_map: &SymbolMap<'tcx>)
+                               -> String {
+    // Just try to look things up in the symbol map. If nothing's there, we
+    // recompute.
+    if let Some(node_id) = scx.tcx().map.as_local_node_id(def_id) {
+        if let Some(sym) = symbol_map.get(TransItem::Static(node_id)) {
+            return sym.to_owned();
+        }
+    }
+
+    let instance = Instance::mono(scx, def_id);
+
+    symbol_map.get(TransItem::Fn(instance))
+              .map(str::to_owned)
+              .unwrap_or_else(|| instance.symbol_name(scx))
+}
diff --git a/src/librustc_trans/back/write.rs b/src/librustc_trans/back/write.rs
index ae5d02c7e04..ffab0bde7ab 100644
--- a/src/librustc_trans/back/write.rs
+++ b/src/librustc_trans/back/write.rs
@@ -10,6 +10,7 @@
 
 use back::lto;
 use back::link::{get_linker, remove};
+use back::symbol_export::ExportedSymbols;
 use rustc_incremental::{save_trans_partition, in_incr_comp_dir};
 use session::config::{OutputFilenames, OutputTypes, Passes, SomePasses, AllPasses};
 use session::Session;
@@ -328,7 +329,7 @@ impl ModuleConfig {
 struct CodegenContext<'a> {
     // Extra resources used for LTO: (sess, reachable).  This will be `None`
     // when running in a worker thread.
-    lto_ctxt: Option<(&'a Session, &'a [String])>,
+    lto_ctxt: Option<(&'a Session, &'a ExportedSymbols)>,
     // Handler to use for diagnostics produced during codegen.
     handler: &'a Handler,
     // LLVM passes added by plugins.
@@ -343,9 +344,11 @@ struct CodegenContext<'a> {
 }
 
 impl<'a> CodegenContext<'a> {
-    fn new_with_session(sess: &'a Session, reachable: &'a [String]) -> CodegenContext<'a> {
+    fn new_with_session(sess: &'a Session,
+                        exported_symbols: &'a ExportedSymbols)
+                        -> CodegenContext<'a> {
         CodegenContext {
-            lto_ctxt: Some((sess, reachable)),
+            lto_ctxt: Some((sess, exported_symbols)),
             handler: sess.diagnostic(),
             plugin_passes: sess.plugin_llvm_passes.borrow().clone(),
             remark: sess.opts.cg.remark.clone(),
@@ -516,14 +519,14 @@ unsafe fn optimize_and_codegen(cgcx: &CodegenContext,
         llvm::LLVMDisposePassManager(mpm);
 
         match cgcx.lto_ctxt {
-            Some((sess, reachable)) if sess.lto() =>  {
+            Some((sess, exported_symbols)) if sess.lto() =>  {
                 time(sess.time_passes(), "all lto passes", || {
                     let temp_no_opt_bc_filename =
                         output_names.temp_path_ext("no-opt.lto.bc", module_name);
                     lto::run(sess,
                              llmod,
                              tm,
-                             reachable,
+                             exported_symbols,
                              &config,
                              &temp_no_opt_bc_filename);
                 });
@@ -753,7 +756,7 @@ pub fn run_passes(sess: &Session,
     //       potentially create hundreds of them).
     let num_workers = work_items.len() - 1;
     if num_workers == 1 {
-        run_work_singlethreaded(sess, &trans.reachable, work_items);
+        run_work_singlethreaded(sess, &trans.exported_symbols, work_items);
     } else {
         run_work_multithreaded(sess, work_items, num_workers);
     }
@@ -997,9 +1000,9 @@ fn execute_work_item(cgcx: &CodegenContext,
 }
 
 fn run_work_singlethreaded(sess: &Session,
-                           reachable: &[String],
+                           exported_symbols: &ExportedSymbols,
                            work_items: Vec<WorkItem>) {
-    let cgcx = CodegenContext::new_with_session(sess, reachable);
+    let cgcx = CodegenContext::new_with_session(sess, exported_symbols);
 
     // Since we're running single-threaded, we can pass the session to
     // the proc, allowing `optimize_and_codegen` to perform LTO.
diff --git a/src/librustc_trans/base.rs b/src/librustc_trans/base.rs
index 259ef2a780c..a31b61e42c4 100644
--- a/src/librustc_trans/base.rs
+++ b/src/librustc_trans/base.rs
@@ -33,10 +33,10 @@ use super::ModuleTranslation;
 use assert_module_sources;
 use back::link;
 use back::linker::LinkerInfo;
+use back::symbol_export::{self, ExportedSymbols};
 use llvm::{Linkage, ValueRef, Vector, get_param};
 use llvm;
-use rustc::hir::def::Def;
-use rustc::hir::def_id::DefId;
+use rustc::hir::def_id::{DefId, LOCAL_CRATE};
 use middle::lang_items::{LangItem, ExchangeMallocFnLangItem, StartFnLangItem};
 use rustc::ty::subst::Substs;
 use rustc::traits;
@@ -84,7 +84,6 @@ use util::nodemap::{NodeSet, FxHashMap, FxHashSet};
 use arena::TypedArena;
 use libc::c_uint;
 use std::ffi::{CStr, CString};
-use std::borrow::Cow;
 use std::cell::{Cell, RefCell};
 use std::ptr;
 use std::rc::Rc;
@@ -1243,7 +1242,7 @@ fn contains_null(s: &str) -> bool {
 }
 
 fn write_metadata(cx: &SharedCrateContext,
-                  reachable_ids: &NodeSet) -> Vec<u8> {
+                  exported_symbols: &NodeSet) -> Vec<u8> {
     use flate;
 
     #[derive(PartialEq, Eq, PartialOrd, Ord)]
@@ -1275,7 +1274,7 @@ fn write_metadata(cx: &SharedCrateContext,
     let metadata = cstore.encode_metadata(cx.tcx(),
                                           cx.export_map(),
                                           cx.link_meta(),
-                                          reachable_ids);
+                                          exported_symbols);
     if kind == MetadataKind::Uncompressed {
         return metadata;
     }
@@ -1313,16 +1312,23 @@ fn write_metadata(cx: &SharedCrateContext,
 fn internalize_symbols<'a, 'tcx>(sess: &Session,
                                  ccxs: &CrateContextList<'a, 'tcx>,
                                  symbol_map: &SymbolMap<'tcx>,
-                                 reachable: &FxHashSet<&str>) {
+                                 exported_symbols: &ExportedSymbols) {
+    let export_threshold =
+        symbol_export::crates_export_threshold(&sess.crate_types.borrow()[..]);
+
+    let exported_symbols = exported_symbols
+        .exported_symbols(LOCAL_CRATE)
+        .iter()
+        .filter(|&&(_, export_level)| {
+            symbol_export::is_below_threshold(export_level, export_threshold)
+        })
+        .map(|&(ref name, _)| &name[..])
+        .collect::<FxHashSet<&str>>();
+
     let scx = ccxs.shared();
     let tcx = scx.tcx();
 
-    // In incr. comp. mode, we can't necessarily see all refs since we
-    // don't generate LLVM IR for reused modules, so skip this
-    // step. Later we should get smarter.
-    if sess.opts.debugging_opts.incremental.is_some() {
-        return;
-    }
+    let incr_comp = sess.opts.debugging_opts.incremental.is_some();
 
     // 'unsafe' because we are holding on to CStr's from the LLVM module within
     // this block.
@@ -1330,34 +1336,43 @@ fn internalize_symbols<'a, 'tcx>(sess: &Session,
         let mut referenced_somewhere = FxHashSet();
 
         // Collect all symbols that need to stay externally visible because they
-        // are referenced via a declaration in some other codegen unit.
-        for ccx in ccxs.iter_need_trans() {
-            for val in iter_globals(ccx.llmod()).chain(iter_functions(ccx.llmod())) {
-                let linkage = llvm::LLVMRustGetLinkage(val);
-                // We only care about external declarations (not definitions)
-                // and available_externally definitions.
-                let is_available_externally = linkage == llvm::Linkage::AvailableExternallyLinkage;
-                let is_decl = llvm::LLVMIsDeclaration(val) != 0;
-
-                if is_decl || is_available_externally {
-                    let symbol_name = CStr::from_ptr(llvm::LLVMGetValueName(val));
-                    referenced_somewhere.insert(symbol_name);
+        // are referenced via a declaration in some other codegen unit. In
+        // incremental compilation, we don't need to collect. See below for more
+        // information.
+        if !incr_comp {
+            for ccx in ccxs.iter_need_trans() {
+                for val in iter_globals(ccx.llmod()).chain(iter_functions(ccx.llmod())) {
+                    let linkage = llvm::LLVMRustGetLinkage(val);
+                    // We only care about external declarations (not definitions)
+                    // and available_externally definitions.
+                    let is_available_externally =
+                        linkage == llvm::Linkage::AvailableExternallyLinkage;
+                    let is_decl = llvm::LLVMIsDeclaration(val) == llvm::True;
+
+                    if is_decl || is_available_externally {
+                        let symbol_name = CStr::from_ptr(llvm::LLVMGetValueName(val));
+                        referenced_somewhere.insert(symbol_name);
+                    }
                 }
             }
         }
 
         // Also collect all symbols for which we cannot adjust linkage, because
-        // it is fixed by some directive in the source code (e.g. #[no_mangle]).
-        let linkage_fixed_explicitly: FxHashSet<_> = scx
-            .translation_items()
-            .borrow()
-            .iter()
-            .cloned()
-            .filter(|trans_item|{
-                trans_item.explicit_linkage(tcx).is_some()
-            })
-            .map(|trans_item| symbol_map.get_or_compute(scx, trans_item))
-            .collect();
+        // it is fixed by some directive in the source code.
+        let (locally_defined_symbols, linkage_fixed_explicitly) = {
+            let mut locally_defined_symbols = FxHashSet();
+            let mut linkage_fixed_explicitly = FxHashSet();
+
+            for trans_item in scx.translation_items().borrow().iter() {
+                let symbol_name = symbol_map.get_or_compute(scx, *trans_item);
+                if trans_item.explicit_linkage(tcx).is_some() {
+                    linkage_fixed_explicitly.insert(symbol_name.clone());
+                }
+                locally_defined_symbols.insert(symbol_name);
+            }
+
+            (locally_defined_symbols, linkage_fixed_explicitly)
+        };
 
         // Examine each external definition.  If the definition is not used in
         // any other compilation unit, and is not reachable from other crates,
@@ -1369,23 +1384,46 @@ fn internalize_symbols<'a, 'tcx>(sess: &Session,
                 let is_externally_visible = (linkage == llvm::Linkage::ExternalLinkage) ||
                                             (linkage == llvm::Linkage::LinkOnceODRLinkage) ||
                                             (linkage == llvm::Linkage::WeakODRLinkage);
-                let is_definition = llvm::LLVMIsDeclaration(val) == 0;
-
-                // If this is a definition (as opposed to just a declaration)
-                // and externally visible, check if we can internalize it
-                if is_definition && is_externally_visible {
-                    let name_cstr = CStr::from_ptr(llvm::LLVMGetValueName(val));
-                    let name_str = name_cstr.to_str().unwrap();
-                    let name_cow = Cow::Borrowed(name_str);
-
-                    let is_referenced_somewhere = referenced_somewhere.contains(&name_cstr);
-                    let is_reachable = reachable.contains(&name_str);
-                    let has_fixed_linkage = linkage_fixed_explicitly.contains(&name_cow);
-
-                    if !is_referenced_somewhere && !is_reachable && !has_fixed_linkage {
-                        llvm::LLVMRustSetLinkage(val, llvm::Linkage::InternalLinkage);
-                        llvm::LLVMSetDLLStorageClass(val,
-                                                     llvm::DLLStorageClass::Default);
+
+                if !is_externally_visible {
+                    // This symbol is not visible outside of its codegen unit,
+                    // so there is nothing to do for it.
+                    continue;
+                }
+
+                let name_cstr = CStr::from_ptr(llvm::LLVMGetValueName(val));
+                let name_str = name_cstr.to_str().unwrap();
+
+                if exported_symbols.contains(&name_str) {
+                    // This symbol is explicitly exported, so we can't
+                    // mark it as internal or hidden.
+                    continue;
+                }
+
+                let is_declaration = llvm::LLVMIsDeclaration(val) == llvm::True;
+
+                if is_declaration {
+                    if locally_defined_symbols.contains(name_str) {
+                        // Only mark declarations from the current crate as hidden.
+                        // Otherwise we would mark things as hidden that are
+                        // imported from other crates or native libraries.
+                        llvm::LLVMRustSetVisibility(val, llvm::Visibility::Hidden);
+                    }
+                } else {
+                    let has_fixed_linkage = linkage_fixed_explicitly.contains(name_str);
+
+                    if !has_fixed_linkage {
+                        // In incremental compilation mode, we can't be sure that
+                        // we saw all references because we don't know what's in
+                        // cached compilation units, so we always assume that the
+                        // given item has been referenced.
+                        if incr_comp || referenced_somewhere.contains(&name_cstr) {
+                            llvm::LLVMRustSetVisibility(val, llvm::Visibility::Hidden);
+                        } else {
+                            llvm::LLVMRustSetLinkage(val, llvm::Linkage::InternalLinkage);
+                        }
+
+                        llvm::LLVMSetDLLStorageClass(val, llvm::DLLStorageClass::Default);
                         llvm::UnsetComdat(val);
                     }
                 }
@@ -1481,7 +1519,7 @@ fn iter_functions(llmod: llvm::ModuleRef) -> ValueIter {
 ///
 /// 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(tcx: TyCtxt, reachable: NodeSet) -> NodeSet {
+pub fn find_exported_symbols(tcx: TyCtxt, reachable: NodeSet) -> NodeSet {
     reachable.into_iter().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
@@ -1535,7 +1573,7 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     let krate = tcx.map.krate();
 
     let ty::CrateAnalysis { export_map, reachable, name, .. } = analysis;
-    let reachable = filter_reachable_ids(tcx, reachable);
+    let exported_symbols = find_exported_symbols(tcx, reachable);
 
     let check_overflow = if let Some(v) = tcx.sess.opts.debugging_opts.force_overflow_checks {
         v
@@ -1548,11 +1586,11 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     let shared_ccx = SharedCrateContext::new(tcx,
                                              export_map,
                                              link_meta.clone(),
-                                             reachable,
+                                             exported_symbols,
                                              check_overflow);
     // Translate the metadata.
     let metadata = time(tcx.sess.time_passes(), "write metadata", || {
-        write_metadata(&shared_ccx, shared_ccx.reachable())
+        write_metadata(&shared_ccx, shared_ccx.exported_symbols())
     });
 
     let metadata_module = ModuleTranslation {
@@ -1602,13 +1640,13 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     // Skip crate items and just output metadata in -Z no-trans mode.
     if tcx.sess.opts.debugging_opts.no_trans ||
        tcx.sess.crate_types.borrow().iter().all(|ct| ct == &config::CrateTypeMetadata) {
-        let linker_info = LinkerInfo::new(&shared_ccx, &[]);
+        let linker_info = LinkerInfo::new(&shared_ccx, &ExportedSymbols::empty());
         return CrateTranslation {
             modules: modules,
             metadata_module: metadata_module,
             link: link_meta,
             metadata: metadata,
-            reachable: vec![],
+            exported_symbols: ExportedSymbols::empty(),
             no_builtins: no_builtins,
             linker_info: linker_info,
             windows_subsystem: None,
@@ -1688,56 +1726,17 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     }
 
     let sess = shared_ccx.sess();
-    let mut reachable_symbols = shared_ccx.reachable().iter().map(|&id| {
-        let def_id = shared_ccx.tcx().map.local_def_id(id);
-        symbol_for_def_id(def_id, &shared_ccx, &symbol_map)
-    }).collect::<Vec<_>>();
-
-    if sess.entry_fn.borrow().is_some() {
-        reachable_symbols.push("main".to_string());
-    }
-
-    if sess.crate_types.borrow().contains(&config::CrateTypeDylib) {
-        reachable_symbols.push(shared_ccx.metadata_symbol_name());
-    }
 
-    // For the purposes of LTO or when creating a cdylib, 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 we need to
-    // preserve them.
-    //
-    // Note that this happens even if LTO isn't requested or we're not creating
-    // a cdylib. In those cases, though, we're not even reading the
-    // `reachable_symbols` list later on so it should be ok.
-    for cnum in sess.cstore.crates() {
-        let syms = sess.cstore.reachable_ids(cnum);
-        reachable_symbols.extend(syms.into_iter().filter(|&def_id| {
-            let applicable = match sess.cstore.describe_def(def_id) {
-                Some(Def::Static(..)) => true,
-                Some(Def::Fn(_)) => {
-                    shared_ccx.tcx().item_generics(def_id).types.is_empty()
-                }
-                _ => false
-            };
-
-            if applicable {
-                let attrs = shared_ccx.tcx().get_attrs(def_id);
-                attr::contains_extern_indicator(sess.diagnostic(), &attrs)
-            } else {
-                false
-            }
-        }).map(|did| {
-            symbol_for_def_id(did, &shared_ccx, &symbol_map)
-        }));
-    }
+    let exported_symbols = ExportedSymbols::compute_from(&shared_ccx,
+                                                         &symbol_map);
 
+    // Now that we have all symbols that are exported from the CGUs of this
+    // crate, we can run the `internalize_symbols` pass.
     time(shared_ccx.sess().time_passes(), "internalize symbols", || {
         internalize_symbols(sess,
                             &crate_context_list,
                             &symbol_map,
-                            &reachable_symbols.iter()
-                                              .map(|s| &s[..])
-                                              .collect())
+                            &exported_symbols);
     });
 
     if tcx.sess.opts.debugging_opts.print_type_sizes {
@@ -1749,7 +1748,7 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
         create_imps(&crate_context_list);
     }
 
-    let linker_info = LinkerInfo::new(&shared_ccx, &reachable_symbols);
+    let linker_info = LinkerInfo::new(&shared_ccx, &exported_symbols);
 
     let subsystem = attr::first_attr_value_str_by_name(&krate.attrs,
                                                        "windows_subsystem");
@@ -1767,7 +1766,7 @@ pub fn trans_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
         metadata_module: metadata_module,
         link: link_meta,
         metadata: metadata,
-        reachable: reachable_symbols,
+        exported_symbols: exported_symbols,
         no_builtins: no_builtins,
         linker_info: linker_info,
         windows_subsystem: windows_subsystem,
@@ -2107,22 +2106,3 @@ fn collect_and_partition_translation_items<'a, 'tcx>(scx: &SharedCrateContext<'a
 
     (codegen_units, symbol_map)
 }
-
-fn symbol_for_def_id<'a, 'tcx>(def_id: DefId,
-                               scx: &SharedCrateContext<'a, 'tcx>,
-                               symbol_map: &SymbolMap<'tcx>)
-                               -> String {
-    // Just try to look things up in the symbol map. If nothing's there, we
-    // recompute.
-    if let Some(node_id) = scx.tcx().map.as_local_node_id(def_id) {
-        if let Some(sym) = symbol_map.get(TransItem::Static(node_id)) {
-            return sym.to_owned();
-        }
-    }
-
-    let instance = Instance::mono(scx, def_id);
-
-    symbol_map.get(TransItem::Fn(instance))
-              .map(str::to_owned)
-              .unwrap_or_else(|| instance.symbol_name(scx))
-}
diff --git a/src/librustc_trans/context.rs b/src/librustc_trans/context.rs
index c0d7c64bd19..262b8362397 100644
--- a/src/librustc_trans/context.rs
+++ b/src/librustc_trans/context.rs
@@ -67,7 +67,7 @@ pub struct SharedCrateContext<'a, 'tcx: 'a> {
     metadata_llcx: ContextRef,
 
     export_map: ExportMap,
-    reachable: NodeSet,
+    exported_symbols: NodeSet,
     link_meta: LinkMeta,
     tcx: TyCtxt<'a, 'tcx, 'tcx>,
     stats: Stats,
@@ -437,7 +437,7 @@ impl<'b, 'tcx> SharedCrateContext<'b, 'tcx> {
     pub fn new(tcx: TyCtxt<'b, 'tcx, 'tcx>,
                export_map: ExportMap,
                link_meta: LinkMeta,
-               reachable: NodeSet,
+               exported_symbols: NodeSet,
                check_overflow: bool)
                -> SharedCrateContext<'b, 'tcx> {
         let (metadata_llcx, metadata_llmod) = unsafe {
@@ -454,7 +454,7 @@ impl<'b, 'tcx> SharedCrateContext<'b, 'tcx> {
         // 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
         // the `use_dll_storage_attrs` variable below to attach the `dllexport`
-        // attribute to all LLVM functions that are reachable (e.g. they're
+        // attribute to all LLVM functions that are exported e.g. they're
         // already tagged with external linkage). This is suboptimal for a few
         // reasons:
         //
@@ -493,7 +493,7 @@ impl<'b, 'tcx> SharedCrateContext<'b, 'tcx> {
             metadata_llmod: metadata_llmod,
             metadata_llcx: metadata_llcx,
             export_map: export_map,
-            reachable: reachable,
+            exported_symbols: exported_symbols,
             link_meta: link_meta,
             tcx: tcx,
             stats: Stats {
@@ -527,8 +527,8 @@ impl<'b, 'tcx> SharedCrateContext<'b, 'tcx> {
         &self.export_map
     }
 
-    pub fn reachable<'a>(&'a self) -> &'a NodeSet {
-        &self.reachable
+    pub fn exported_symbols<'a>(&'a self) -> &'a NodeSet {
+        &self.exported_symbols
     }
 
     pub fn trait_cache(&self) -> &RefCell<DepTrackingMap<TraitSelectionCache<'tcx>>> {
@@ -768,8 +768,8 @@ impl<'b, 'tcx> CrateContext<'b, 'tcx> {
         &self.shared.export_map
     }
 
-    pub fn reachable<'a>(&'a self) -> &'a NodeSet {
-        &self.shared.reachable
+    pub fn exported_symbols<'a>(&'a self) -> &'a NodeSet {
+        &self.shared.exported_symbols
     }
 
     pub fn link_meta<'a>(&'a self) -> &'a LinkMeta {
diff --git a/src/librustc_trans/debuginfo/utils.rs b/src/librustc_trans/debuginfo/utils.rs
index 7cac9172a9c..3ee2497009f 100644
--- a/src/librustc_trans/debuginfo/utils.rs
+++ b/src/librustc_trans/debuginfo/utils.rs
@@ -34,7 +34,7 @@ pub fn is_node_local_to_unit(cx: &CrateContext, node_id: ast::NodeId) -> bool
     // visible). It might better to use the `exported_items` set from
     // `driver::CrateAnalysis` in the future, but (atm) this set is not
     // available in the translation pass.
-    !cx.reachable().contains(&node_id)
+    !cx.exported_symbols().contains(&node_id)
 }
 
 #[allow(non_snake_case)]
diff --git a/src/librustc_trans/declare.rs b/src/librustc_trans/declare.rs
index 7d6a672077a..eef3b9b1147 100644
--- a/src/librustc_trans/declare.rs
+++ b/src/librustc_trans/declare.rs
@@ -78,7 +78,7 @@ fn declare_raw_fn(ccx: &CrateContext, name: &str, callconv: llvm::CallConv, ty:
     // don't want the symbols to get exported.
     if attr::contains_name(ccx.tcx().map.krate_attrs(), "compiler_builtins") {
         unsafe {
-            llvm::LLVMSetVisibility(llfn, llvm::Visibility::Hidden);
+            llvm::LLVMRustSetVisibility(llfn, llvm::Visibility::Hidden);
         }
     }
 
diff --git a/src/librustc_trans/lib.rs b/src/librustc_trans/lib.rs
index 0e7ead30a93..e2da635b159 100644
--- a/src/librustc_trans/lib.rs
+++ b/src/librustc_trans/lib.rs
@@ -78,6 +78,7 @@ pub mod back {
     pub mod linker;
     pub mod link;
     pub mod lto;
+    pub mod symbol_export;
     pub mod symbol_names;
     pub mod write;
     pub mod msvc;
@@ -169,7 +170,7 @@ pub struct CrateTranslation {
     pub metadata_module: ModuleTranslation,
     pub link: middle::cstore::LinkMeta,
     pub metadata: Vec<u8>,
-    pub reachable: Vec<String>,
+    pub exported_symbols: back::symbol_export::ExportedSymbols,
     pub no_builtins: bool,
     pub windows_subsystem: Option<String>,
     pub linker_info: back::linker::LinkerInfo
diff --git a/src/rustllvm/RustWrapper.cpp b/src/rustllvm/RustWrapper.cpp
index 51859a928c4..6a95b65d5e9 100644
--- a/src/rustllvm/RustWrapper.cpp
+++ b/src/rustllvm/RustWrapper.cpp
@@ -1408,3 +1408,45 @@ extern "C" void LLVMRustSetLinkage(LLVMValueRef V, LLVMRustLinkage RustLinkage)
 extern "C" LLVMContextRef LLVMRustGetValueContext(LLVMValueRef V) {
     return wrap(&unwrap(V)->getContext());
 }
+
+enum class LLVMRustVisibility {
+    Default = 0,
+    Hidden = 1,
+    Protected = 2,
+};
+
+static LLVMRustVisibility to_rust(LLVMVisibility vis) {
+    switch (vis) {
+        case LLVMDefaultVisibility:
+            return LLVMRustVisibility::Default;
+        case LLVMHiddenVisibility:
+            return LLVMRustVisibility::Hidden;
+        case LLVMProtectedVisibility:
+            return LLVMRustVisibility::Protected;
+
+        default:
+            llvm_unreachable("Invalid LLVMRustVisibility value!");
+    }
+}
+
+static LLVMVisibility from_rust(LLVMRustVisibility vis) {
+    switch (vis) {
+        case LLVMRustVisibility::Default:
+            return LLVMDefaultVisibility;
+        case LLVMRustVisibility::Hidden:
+            return LLVMHiddenVisibility;
+        case LLVMRustVisibility::Protected:
+            return LLVMProtectedVisibility;
+
+        default:
+            llvm_unreachable("Invalid LLVMRustVisibility value!");
+    }
+}
+
+extern "C" LLVMRustVisibility LLVMRustGetVisibility(LLVMValueRef V) {
+    return to_rust(LLVMGetVisibility(V));
+}
+
+extern "C" void LLVMRustSetVisibility(LLVMValueRef V, LLVMRustVisibility RustVisibility) {
+    LLVMSetVisibility(V, from_rust(RustVisibility));
+}
diff --git a/src/test/run-make/sepcomp-inlining/Makefile b/src/test/run-make/sepcomp-inlining/Makefile
index ef43b0d97e4..720dfff2c04 100644
--- a/src/test/run-make/sepcomp-inlining/Makefile
+++ b/src/test/run-make/sepcomp-inlining/Makefile
@@ -10,5 +10,5 @@ all:
 	$(RUSTC) foo.rs --emit=llvm-ir -C codegen-units=3
 	[ "$$(cat "$(TMPDIR)"/foo.?.ll | grep -c define\ i32\ .*inlined)" -eq "0" ]
 	[ "$$(cat "$(TMPDIR)"/foo.?.ll | grep -c define\ internal\ i32\ .*inlined)" -eq "2" ]
-	[ "$$(cat "$(TMPDIR)"/foo.?.ll | grep -c define\ i32\ .*normal)" -eq "1" ]
-	[ "$$(cat "$(TMPDIR)"/foo.?.ll | grep -c declare\ i32\ .*normal)" -eq "2" ]
+	[ "$$(cat "$(TMPDIR)"/foo.?.ll | grep -c define\ hidden\ i32\ .*normal)" -eq "1" ]
+	[ "$$(cat "$(TMPDIR)"/foo.?.ll | grep -c declare\ hidden\ i32\ .*normal)" -eq "2" ]
diff --git a/src/test/run-make/symbol-visibility/Makefile b/src/test/run-make/symbol-visibility/Makefile
new file mode 100644
index 00000000000..988c9473f6a
--- /dev/null
+++ b/src/test/run-make/symbol-visibility/Makefile
@@ -0,0 +1,50 @@
+include ../tools.mk
+
+ifdef IS_WINDOWS
+# Do nothing on MSVC.
+# On MINGW the --version-script, --dynamic-list, and --retain-symbol args don't
+# seem to work reliably.
+all:
+	exit 0
+else
+
+NM=nm -D
+DYLIB_EXT=so
+CDYLIB_NAME=liba_cdylib.so
+RDYLIB_NAME=liba_rust_dylib.so
+EXE_NAME=an_executable
+
+ifeq ($(UNAME),Darwin)
+NM=nm -gU
+DYLIB_EXT=dylib
+CDYLIB_NAME=liba_cdylib.dylib
+RDYLIB_NAME=liba_rust_dylib.dylib
+EXE_NAME=an_executable
+endif
+
+all:
+	$(RUSTC) an_rlib.rs
+	$(RUSTC) a_cdylib.rs
+	$(RUSTC) a_rust_dylib.rs
+	$(RUSTC) an_executable.rs
+
+	# Check that a cdylib exports its public #[no_mangle] functions
+	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -c public_c_function_from_cdylib)" -eq "1" ]
+	# Check that a cdylib exports the public #[no_mangle] functions of dependencies
+	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -c public_c_function_from_rlib)" -eq "1" ]
+	# Check that a cdylib DOES NOT export any public Rust functions
+	[ "$$($(NM) $(TMPDIR)/$(CDYLIB_NAME) | grep -c _ZN.*h.*E)" -eq "0" ]
+
+	# Check that a Rust dylib exports its monomorphic functions
+	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -c public_c_function_from_rust_dylib)" -eq "1" ]
+	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -c _ZN.*public_rust_function_from_rust_dylib.*E)" -eq "1" ]
+
+	# Check that a Rust dylib exports the monomorphic functions from its dependencies
+	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -c public_c_function_from_rlib)" -eq "1" ]
+	[ "$$($(NM) $(TMPDIR)/$(RDYLIB_NAME) | grep -c public_rust_function_from_rlib)" -eq "1" ]
+
+	# Check that an executable does not export any dynamic symbols
+	[ "$$($(NM) $(TMPDIR)/$(EXE_NAME) | grep -c public_c_function_from_rlib)" -eq "0" ]
+	[ "$$($(NM) $(TMPDIR)/$(EXE_NAME) | grep -c public_rust_function_from_exe)" -eq "0" ]
+
+endif
diff --git a/src/test/run-make/symbol-visibility/a_cdylib.rs b/src/test/run-make/symbol-visibility/a_cdylib.rs
new file mode 100644
index 00000000000..9a70542c06c
--- /dev/null
+++ b/src/test/run-make/symbol-visibility/a_cdylib.rs
@@ -0,0 +1,22 @@
+// Copyright 2016 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![crate_type="cdylib"]
+
+extern crate an_rlib;
+
+// This should not be exported
+pub fn public_rust_function_from_cdylib() {}
+
+// This should be exported
+#[no_mangle]
+pub extern "C" fn public_c_function_from_cdylib() {
+    an_rlib::public_c_function_from_rlib();
+}
diff --git a/src/test/run-make/symbol-visibility/a_rust_dylib.rs b/src/test/run-make/symbol-visibility/a_rust_dylib.rs
new file mode 100644
index 00000000000..b826211c9a4
--- /dev/null
+++ b/src/test/run-make/symbol-visibility/a_rust_dylib.rs
@@ -0,0 +1,20 @@
+// Copyright 2016 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![crate_type="dylib"]
+
+extern crate an_rlib;
+
+// This should be exported
+pub fn public_rust_function_from_rust_dylib() {}
+
+// This should be exported
+#[no_mangle]
+pub extern "C" fn public_c_function_from_rust_dylib() {}
diff --git a/src/test/run-make/symbol-visibility/an_executable.rs b/src/test/run-make/symbol-visibility/an_executable.rs
new file mode 100644
index 00000000000..73059c5e374
--- /dev/null
+++ b/src/test/run-make/symbol-visibility/an_executable.rs
@@ -0,0 +1,17 @@
+// Copyright 2016 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![crate_type="bin"]
+
+extern crate an_rlib;
+
+pub fn public_rust_function_from_exe() {}
+
+fn main() {}
diff --git a/src/test/run-make/symbol-visibility/an_rlib.rs b/src/test/run-make/symbol-visibility/an_rlib.rs
new file mode 100644
index 00000000000..cd19500d140
--- /dev/null
+++ b/src/test/run-make/symbol-visibility/an_rlib.rs
@@ -0,0 +1,16 @@
+// Copyright 2016 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![crate_type="rlib"]
+
+pub fn public_rust_function_from_rlib() {}
+
+#[no_mangle]
+pub extern "C" fn public_c_function_from_rlib() {}