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<ast::DefId>
 {
     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<String> {
     }
 }
 
-pub fn get_reachable_extern_fns(cdata: Cmd) -> Vec<ast::DefId> {
+pub fn get_reachable_ids(cdata: Cmd) -> Vec<ast::DefId> {
     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<codemap::FileMap> {
         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<Vec<u8>>,
     // 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<u8> {
+pub fn write_metadata(cx: &SharedCrateContext, krate: &ast::Crate,
+                      reachable: &NodeSet) -> Vec<u8> {
     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<u8> {
     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<u8> {
 
 /// 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<String>) {
+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<String>) {
     }
 }
 
+/// 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<String> = 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::<Vec<_>>();
+    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 <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.
+
+#[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)]